I have been creating .NET webapps for more than 10 years. I have created a lot of complex applications and a lot of CRUD APIs.
CRUD APIs by their nature are straightforward, but in every project you need to write the same boilerplate code to create, update, delete and read entities from the database.
A few years ago, I was looking for ready free solutions that will allow me to ship CRUD APIs faster. And I couldn't find the perfect solution.
So I created my own.
In today's blog post, I will show you tools that allowed me to be more productive when creating CRUD APIs in .NET.
I will show you examples of how to use these tools in an N-Tier (Layered) and Vertical Slice Architecture.
On my website: to become a better developer.
for fast and efficient development of CRUD APIs in .NET.
Have a look at the documentation on the
Within a few minutes, we have created a fully functional CRUD API for
Ticketentity that has the following endpoints:
- Get entity by id
- get all entities
- create entity
- update entity
- partial entity update
- delete entity
- create many entities
- update many entities
- delete many entities
Source generation for WebApi models in Modern Libraries
You're not limited to only using DTO models.
You can also use DBO models and expose them from the APIs, in case you need such an option:
CODEoptions.AddService<Ticket, Ticket, int>();
options.AddController<CreateTicketRequest, UpdateTicketRequest, Ticket, Ticket, int>("api/tickets");
I have created a Nuget package with source generators for WebApi models:
CODEdotnet add package Modern.Controllers.SourceGenerators
You can add this attribute to the public contract model, and the source generator will create a
CreateTicketRequestandUpdateTicketRequestmodels:
CODE[WebApiEntityRequest(CreateRequestName = "CreateTicketRequest",
UpdateRequestName = "UpdateTicketRequest")]
public record TicketDto
{
[IgnoreCreateRequest]
public required int Id { get; init; }
public required string Number { get; init; }
public required string Description { get; init; }
public required string Status { get; init; }
public required decimal Price { get; init; }
}
Source generator uses all the properties for Create and Update requests.
You can additionally specify what properties should be excluded from the requests by using theIgnoreCreateRequestorIgnoreUpdateRequest.
Customizing Repositories, Services and Controllers
At its core, Modern libraries follow the classic N-Tier (Layered) approach:
- Repository
- Service
- Controller
Each layer can be overridden and further extended.
Modern Repository supports the following operations.
Modern Repository has
Wheremethod that you can use to filter data by a given condition.
However, you may need to create your own methods.
You can create your own repository interface that inherits from
IModernRepository<TEntity, TId>.
And create an implementation that inherits fromModernEfCoreRepository<TDbContext, TEntity, TId>.
Here is an example:
CODEpublic interface ICustomTicketRepository: IModernRepository<Ticket, int>
{
Task<List<Ticket>> GetTicketsByDateAsync(DateTime date);
}
public class CustomTicketRepository
: ModernEfCoreRepository<EfCoreDbContext, Ticket, int>, ICustomTicketRepository
{
public CustomTicketRepository(
EfCoreDbContext dbContext,
IOptions<EfCoreRepositoryConfiguration> configuration)
: base(dbContext, configuration)
{
}
public async Task<List<Ticket>> GetTicketsByDateAsync(DateTime date)
{
return await DbContext.Tickets
.Where(x => x.PurchasedAtUtc >= date)
.ToListAsync();
}
}
In the same manner you can create your own service interface that inherits from
IModernService<TEntityDto, TEntityDbo, TId>.
And create an implementation that inherits fromModernService<TEntityDto, TEntityDbo, TId>.
CODEpublic interface ICustomTicketService : IModernService<TicketDto, Ticket, int>
{
Task<List<Ticket>> GetTicketsByDateAsync(DateTime date);
}
public class CustomTicketService : ModernService<TicketDto, Ticket, int>, ICustomTicketService
{
private readonly ICustomTicketRepository _repository;
public CustomTicketService(
ICustomTicketRepository repository,
ILogger<CustomTicketService> logger)
: base(repository, logger)
{
_repository = repository;
}
public async Task<List<Ticket>> GetTicketsByDateAsync(DateTime date)
{
return await _repository.GetTicketsByDateAsync(date);
}
}
The same goes with Controllers, you can create a custom Controller that inherits from
ModernController<TCreateRequest, TUpdateRequest, TEntityDto, TEntityDbo, TId>:
CODEpublic record GetTicketsByDateRequest(DateTime Date);
[ApiController]
[Route("/api/custom-tickets")]
public class CustomTicketController
: ModernController<CreateTicketRequest, UpdateTicketRequest, TicketDto, Ticket, int>
{
private readonly ICustomTicketService _service;
public CustomTicketController(ICustomTicketService service) : base(service)
{
_service = service;
}
[HttpGet("get-by-date")]
public async Task<IActionResult> GetTicketsByDate(
[Required, FromBody] GetTicketsByDateRequest request)
{
var entities = await _service.GetTicketsByDateAsync(request.Date).ConfigureAwait(false);
return Ok(entities);
}
}
After creating your own implementations, you need to register them as Concrete implementations:
CODEbuilder.Services
.AddModern()
.AddRepositoriesEfCore(options =>
{
options.AddRepository<EfCoreDbContext, Ticket, int>();
options.AddConcreteRepository<ICustomTicketRepository, CustomTicketRepository>();
})
.AddServices(options =>
{
options.AddService<TicketDto, Ticket, int>();
options.AddConcreteService<ICustomTicketService, CustomTicketService>();
})
.AddControllers(options =>
{
options.AddController<CreateTicketRequest, UpdateTicketRequest, TicketDto, Ticket, int>("api/tickets");
options.AddController<CustomTicketController>();
});
In all previous examples, we were using our own customized implementations of a Repository and Service.
What if you don't need to create your own version of Repository or Service and use just a base version from the package?
The first option is to use the base interface of
IModernServiceorIModernRepositoryfrom the Modern package:
CODE// Use IModernService<...> instead of ICustomTicketService
IModernService<TicketDto, Ticket, int> service
// Use IModernRepository<...> instead of ICustomTicketRepository
IModernRepository<Ticket, int> repository
If you dislike such long types, you can create your own interface and implementation that inherit from the base types and make them empty.
But this can be tedious.
If this is the case, you can use the following packages with source generators:
CODEdotnet add package Modern.Repositories.EFCore.SourceGenerators
dotnet add package Modern.Services.DataStore.SourceGenerators
You can need to use a marker empty class and specify the
ModernEfCoreRepositoryattribute:
CODE[ModernEfCoreRepository(typeof(EfCoreDbContext), typeof(Ticket), typeof(int))]
public class IServiceMarker;
To autogenerate service, you need to add a
ModernServiceattribute to the Dto class:
CODE[ModernService(typeof(Ticket))]
public class TicketDto
{
}
NOTE: source generator only supports classes for Dto at the moment.
As a result, source generators will create the following classes:
or even combination of Vertical Slices and and the documentation on the I share .NET and Architecture best practices.
the source code for this blog post for free.
SOCIAL SHARE CARD GENERATOR