REST API com C# e AspNet facilmente do que escrevê -la do zero repetidamente em diferentes projetos. 
REST API Controller com CRUD completo contém apenas 20 linhas de código (~ 10 são importações)GET possuem suporte de paginação integrado ;GET possuem classificação e filtro embutidos por parâmetros de consulta;Create , Update e Delete massa) em um nível de interface do controlador &&IModelManager ); Bom suporte interno interno do EntityFramework (consulte a classe EfModelManager ). Consulte o App WeatherControl, que possui 2 projetos de API da Web:Wissance.WeatherControl.WebApi usa EntityFramework ;Wissance.WeatherControl.WebApi.V2 usa EdgeDb .Conceitos -chave:
Controller é uma classe que lida com o recurso HTTP-requests para REST Resource .REST Resource é igual à Entity class / Database TableREST Resource produzem JSON com DTO como saída. Assumimos usar apenas uma classe DTO com todos os métodos REST . Aulas DTO :
OperationResultDto representa resultado da operação que altera os dados em dB;PagedDataDto representa parte (página) dos mesmos objetos (qualquer tipo); Classes Controllers - classes abstratas
BasicReadController ) contém 2 métodos:GET /api/[controller]/?[page={page}&size={size}&sort={sort}&order={order}] para obter PagedDataDto<T> Agora também temos possibilidade de enviar qualquer número de parâmetros de consulta , você só precisa passar a função para EfModelManager ou o seu próprio caminho. Também passamos a classificação (nome da coluna) && Order ( asc ou desc ) para as classes do gerente, EfModelManager permite classificar por qualquer coluna . Swagger para mostrar o uso dos parâmetros de consulta !!!1.6.0 é possível ver todos os parâmetros em Swagger e usá -los.GET /api/[controller]/{id} para obter um objeto por idCRUD Controller ( BasicCrudController ) = Controlador de leitura básica ( BasicReadController ) + Create , Update e Delete operações:POST /api/[controller] - Para nova criação de objetosPUT /api/[controller]/{id} - para editar objeto por idDELETE /api/[controller]/{id} - para excluir objeto por idCRUD completo com operações em massa (operações em vários objetos de uma só vez), classe base - BasicBulkCrudController = Basic Read Controller ( BasicReadController ) + Operações de BulkCreate , BulkUpdate e BulkDelete :POST /api/bulk/[controller] - para criação de novos objetosPUT /api/bulk/[controller] - Para editar objetos que passam em um corpo de solicitaçãoDELETE /api/bulk/[controller]/{idList} - Para excluir vários objetos por id.As classes dos controladores esperam que todas as operações sejam executadas usando as classes do gerente (cada controlador deve ter seu próprio gerente)
Classes de gerentes - classes que implementa a lógica de negócios do aplicativo
IModelManager - Interface que descreve operações básicasEfModelManager - é uma classe abstrata que contém a implementação de operações Get e DeleteEfSoftRemovableModelManager é uma classe abstrata que contém a implementação de operações Get e Delete com modelos removíveis SOFT ( IsDeleted = true Means Model foi removido) Exemplo de quão mais rápido vs não-bulk: 
Elapsed time in Non-Bulk REST API with EF is 0.9759984016418457 secs.
Elapsed time in Bulk API with EF is 0.004002094268798828 secs.
Como resultado, obtivemos quase ~ 250 x API mais rápido.
Existe apenas um requisito : todas as classes de entidade para qualquer armazenamento de persistência que use com controladores e gerentes devem implementar IModelIdentifiable<T> de Wissance.WebApiToolkit.Data.Entity . Se este kit de ferramentas deve ser usado com EntityFramework , você deve derivar seu gerente de recursos do EfModelManager , ele possui métodos internos para:
get many itensget one item by iddelete item by id Exemplo completo é mencionado na Seção 6 (veja abaixo). Mas se você estiver começando a criar uma nova API REST Resource você deve fazer seguintes:
DTO model ( entity ) que implementa a representação IModelSoftRemovable IModelIdentifiable<T> 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 em DTO , ou seja,: public static class BookFactory
{
public static BookDto Create ( BookEntity entity )
{
return new BookDto
{
Id = entity . Id ,
Title = entity . Title ,
Authors = entity . Authors ;
} ;
}
}IModelContext que tenha a BookEntity como uma DbSet e sua classe de implementação que também deriva do 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 como um DbContext via DI , veja a classe de inicializaçãoController e um par de turmas, ou seja, considere aqui CRUD completo [ 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 ;
} Último parâmetro genérico no exemplo acima - EmptyAdditionalFilters é uma classe que contém parâmetros adicionais para pesquisa para ver em swagger, basta especificar uma nova classe que implementa 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 ; }
}Você pode encontrar o Nuget-Package aqui
[ 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 ;
}Apenas 2 classes muito simples ^^ usando webapitoolkit
Considere que gostaríamos de adicionar a pesquisa de métodos ao nosso controlador:
[ 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 ) ;
} Temos um projeto adicional para proteger API com Keycloak OpenId-Connect . Passe IHttpContextAccessor para a classe Manager e verifique algo assim: ClaimsPrincipal principal = _httpContext.HttpContext.User;
Você pode ver nossos artigos sobre o uso do kit de ferramentas: