REST API с C# и AspNet , чем писать с нуля с нуля в разных проектах. 
REST API Controller с полным CRUD содержит только 20 строк кода (~ 10 импорт)GET встроенную поддержку подкачки ;GET встроенную сортировку и фильтр по параметрам запроса;Create , Update и Delete ) на контроллере && интерфейс.IModelManager ); Хорошая встроенная поддержка EntityFramework (см. EfModelManager Class). См. Приложение WeatherControl, в котором есть 2 веб -проекта API:Wissance.WeatherControl.WebApi использует EntityFramework ;Wissance.WeatherControl.WebApi.V2 использует EdgeDb .Ключевые понятия:
Controller -это класс, который обрабатывает HTTP-requests с REST Resource .REST Resource равен Entity class / Database TableREST Resource производит JSON с DTO в качестве вывода. Мы предполагаем, что использовать только один класс DTO со всеми методами REST . Классы DTO :
OperationResultDto представляет результат операции, которая изменяет данные в DB;PagedDataDto представляет часть (страница) тех же объектов (любой тип); Классы Controllers - абстрактные классы
BasicReadController ) содержит 2 метода:GET /api/[controller]/?[page={page}&size={size}&sort={sort}&order={order}] PagedDataDto<T> EfModelManager ? Мы также передаем сортировку (имя столбца) && порядка ( asc или desc ) к классам менеджера, EfModelManager позволяет сортировать по любому столбцу . Swagger , чтобы показать использование параметров запроса !!!1.6.0 , можно увидеть все параметры в Swagger и использовать их.GET /api/[controller]/{id} чтобы получить один объект по idCRUD ( BasicCrudController ) = Базовый контроллер чтения ( BasicReadController ) + Create , Update и Delete операции:POST /api/[controller] - для создания нового объектаPUT /api/[controller]/{id} - для Edit Object по IDDELETE /api/[controller]/{id} - для объекта DELETE по IDCRUD с объемными операциями (операции по нескольким объектам одновременно), базовый класс - BasicBulkCrudController = Basic Complore Controller ( BasicReadController ) + BulkCreate , BulkUpdate и Operations BulkDelete :POST /api/bulk/[controller] - для создания новых объектовPUT /api/bulk/[controller] - для редактирования объектов, передаваемых в корпусе запросаDELETE /api/bulk/[controller]/{idList} - для удаления нескольких объектов по ID.Классы контроллеров ожидают, что вся операция будет выполнена с использованием классов менеджера (каждый контроллер должен иметь свой собственный менеджер)
Менеджеры классы - классы, которые реализуют бизнес -логику приложения
IModelManager - интерфейс, который описывает основные операцииEfModelManager - IS Abstract Class, который содержит реализацию операций Get и DeleteEfSoftRemovableModelManager - это абстрактный класс, который содержит реализацию операций Get и Delete с помощью мягких съемных моделей (была удалена модель IsDeleted = true Mine Model) Пример того, как быстрая масса против не-бульк: 
Elapsed time in Non-Bulk REST API with EF is 0.9759984016418457 secs.
Elapsed time in Bulk API with EF is 0.004002094268798828 secs.
В результате мы получили почти ~ 250 x более быстрых API .
Существует только одно требование : все классы объектов для любого хранилища настойчивости, которые используют с контроллерами и менеджерами, должны реализовать IModelIdentifiable<T> из Wissance.WebApiToolkit.Data.Entity . Если этот инструментарий должен использоваться с EntityFramework , вы должны вывести менеджер ресурсов от EfModelManager , он имеет встроенные методы для:
get many предметовget one элемент by iddelete элемент by id Полный пример упоминается в разделе 6 (см. Ниже). Но если вы начинаете создавать новый API REST Resource вы должны выполнять следующие действия:
model ( entity ), реализующий IModelIdentifiable<T> и класс DTO для представления ИТ ( для мягкого удаления также добавьте IModelSoftRemovable реализацию), т.е. public class BookEntity : IModelIdentifiable < int >
{
public int Id { get ; set ; }
public string Title { get ; set ; }
public string Authors { get ; set ; } // for simplicity
public DateTimeOffset Created { get ; set ; }
public DateTimeOffset Updated { get ; set ; }
}
public class BookDto
{
public int Id { get ; set ; }
public string Title { get ; set ; }
public string Authors { get ; set ; }
}Model в DTO IE: public static class BookFactory
{
public static BookDto Create ( BookEntity entity )
{
return new BookDto
{
Id = entity . Id ,
Title = entity . Title ,
Authors = entity . Authors ;
} ;
}
}IModelContext , который заставляет вас BookEntity как DbSet и его класс реализации, который также вытекает из DbContext ( EF Abstract Class ): public interface IModelContext
{
DbSet < BookEntity > Books { get ; set ; }
}
public MoidelContext : DbContext < ModelContext > , IModelContext
{
// todo: not mrntioned here constructor, entity mapping and so on
public DbSet < BookEntity > Books { get ; set ; }
}ModelContext в качестве DbContext через DI см. Класс запускаController и пара классов менеджера, то есть рассмотрим здесь полный CRUD [ ApiController ]
public class BookController : BasicCrudController < BookDto , BookEntity , int , EmptyAdditionalFilters >
{
public BookController ( BookManager manager )
{
Manager = manager ; // this is for basic operations
_manager = manager ; // this for extended operations
}
private BookManager _manager ;
}
public class BookManager : EfModelManager < BookEntity , BookDto , int , EmptyAdditionalFilters >
{
public BookManager ( ModelContext modelContext , ILoggerFactory loggerFactory ) : base ( modelContext , BookFactory . Create , loggerFactory )
{
_modelContext = modelContext ;
}
public override async Task < OperationResultDto < StationDto > > CreateAsync ( StationDto data )
{
// todo: implement
}
public override async Task < OperationResultDto < StationDto > > UpdateAsync ( int id , StationDto data )
{
// todo: implement
}
private readonly ModelContext _modelContext ;
} Последний общий параметр в примере выше - EmptyAdditionalFilters - это класс, который содержит дополнительные параметры для поиска в Swagger, просто укажите новый класс, реализующий IReadFilterable IE:
public class BooksFilterable : IReadFilterable
{
public IDictionary < string , string > SelectFilters ( )
{
IDictionary < string , string > additionalFilters = new Dictionary < string , string > ( ) ;
if ( ! string . IsNullOrEmpty ( Title ) )
{
additionalFilters . Add ( FilterParamsNames . TitleParameter , Title ) ;
}
if ( Authors != null && Authors . Length > 0 )
{
additionalFilters . Add ( FilterParamsNames . AuthorsParameter , string . Join ( "," , Authors ) ) ;
}
return additionalFilters ;
}
[ FromQuery ( Name = "title" ) ] public string Title { get ; set ; }
[ FromQuery ( Name = "author" ) ] public string [ ] Authors { get ; set ; }
}Вы могли бы найти здесь Nuget-Package
[ ApiController ]
public class StationController : BasicCrudController < StationDto , StationEntity , int , EmptyAdditionalFilters >
{
public StationController ( StationManager manager )
{
Manager = manager ; // this is for basic operations
_manager = manager ; // this for extended operations
}
private StationManager _manager ;
} public class StationManager : EfModelManager < StationEntity , StationDto , int >
{
public StationManager ( ModelContext modelContext , ILoggerFactory loggerFactory ) : base ( modelContext , StationFactory . Create , loggerFactory )
{
_modelContext = modelContext ;
}
public override async Task < OperationResultDto < StationDto > > CreateAsync ( StationDto data )
{
try
{
StationEntity entity = StationFactory . Create ( data ) ;
await _modelContext . Stations . AddAsync ( entity ) ;
int result = await _modelContext . SaveChangesAsync ( ) ;
if ( result >= 0 )
{
return new OperationResultDto < StationDto > ( true , ( int ) HttpStatusCode . Created , null , StationFactory . Create ( entity ) ) ;
}
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , "An unknown error occurred during station creation" , null ) ;
}
catch ( Exception e )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , $ "An error occurred during station creation: { e . Message } " , null ) ;
}
}
public override async Task < OperationResultDto < StationDto > > UpdateAsync ( int id , StationDto data )
{
try
{
StationEntity entity = StationFactory . Create ( data ) ;
StationEntity existingEntity = await _modelContext . Stations . FirstOrDefaultAsync ( s => s . Id == id ) ;
if ( existingEntity == null )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . NotFound , $ "Station with id: { id } does not exists" , null ) ;
}
// Copy only name, description and positions, create measurements if necessary from MeasurementsManager
existingEntity . Name = entity . Name ;
existingEntity . Description = existingEntity . Description ;
existingEntity . Latitude = existingEntity . Latitude ;
existingEntity . Longitude = existingEntity . Longitude ;
int result = await _modelContext . SaveChangesAsync ( ) ;
if ( result >= 0 )
{
return new OperationResultDto < StationDto > ( true , ( int ) HttpStatusCode . OK , null , StationFactory . Create ( entity ) ) ;
}
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , "An unknown error occurred during station update" , null ) ;
}
catch ( Exception e )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , $ "An error occurred during station update: { e . Message } " , null ) ;
}
}
private readonly ModelContext _modelContext ;
}Всего 2 очень простых класса ^^ с помощью webApitoolkit
Считайте, что мы хотели бы добавить поиск метода в наш контроллер:
[ HttpGet ]
[ Route ( "api/[controller]/search" ) ]
public async Task < PagedDataDto < BookDto > > > SearchAsync ( [ FromQuery ] string query , [ FromQuery ] int page , [ FromQuery ] int size )
{
OperationResultDto < Tuple < IList < BookDto > , long > > result = await Manager . GetAsync ( page , size , query ) ;
if ( result == null )
{
HttpContext . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
}
HttpContext . Response . StatusCode = result . Status ;
return new PagedDataDto < TRes > ( pageNumber , result . Data . Item2 , GetTotalPages ( result . Data . Item2 , pageSize ) , result . Data . Item1 ) ;
} У нас есть дополнительный проект по защите API с помощью Keycloak OpenId-Connect . Передайте IHttpContextAccessor к классу Manager и проверьте что -то подобное: ClaimsPrincipal principal = _httpContext.HttpContext.User;
Вы могли бы увидеть наши статьи об использовании инструментов: