This CleanCodeJN package streamlines the development of web APIs in .NET applications by providing a robust framework for CRUD operations and facilitating the implementation of complex business logic in a clean and maintainable manner.
$ dotnet add package CleanCodeJN.GenericApisThis CleanCodeJN package streamlines the development of web APIs in .NET applications by providing a robust framework for CRUD operations and facilitating the implementation of complex business logic in a clean and maintainable manner.
Add RegisterRepositoriesCommandsWithAutomapper() to your Program.cs
builder.Services.RegisterRepositoriesCommandsWithAutomapper<MyDbContext>(cfg =>
{
cfg.CreateMap<Customer, CustomerPutDto>().ReverseMap();
cfg.CreateMap<Customer, CustomerPostDto>().ReverseMap();
cfg.CreateMap<Customer, CustomerGetDto>().ReverseMap();
});
Add app.RegisterApis() when using Minimal APIs to your Program.cs
app.RegisterApis();
When using Controllers add this to your Program.cs
builder.Services.AddControllers();
// After Build()
app.MapControllers();
Start writing Minimal Apis by implementing IApi
public class CustomersV1Api : IApi
{
public List<string> Tags => ["Customers Minimal API"];
public string Route => $"api/v1/Customers";
public List<Func<WebApplication, RouteHandlerBuilder>> HttpMethods =>
[
app => app.MapGet<Customer, CustomerGetDto, int>(Route, Tags),
app => app.MapGetById<Customer, CustomerGetDto, int>(Route, Tags),
app => app.MapPut<Customer, CustomerPutDto, CustomerGetDto>(Route, Tags),
app => app.MapPost<Customer, CustomerPostDto, CustomerGetDto>(Route, Tags),
// Or use a custom Command with MapRequest
app => app.MapDeleteRequest(Route, Tags, async (int id, [FromServices] ApiBase api) =>
await api.Handle<Customer, CustomerGetDto>(new SpecificDeleteRequest { Id = id }))
];
}
Extend standard CRUD operations by specific Where() and Include() clauses
public class CustomersV1Api : IApi
{
public List<string> Tags => ["Customers Minimal API"];
public string Route => $"api/v1/Customers";
public List<Func<WebApplication, RouteHandlerBuilder>> HttpMethods =>
[
app => app.MapGet<Customer, CustomerGetDto, int>(Route, Tags, where: x => x.Name.StartsWith("a")),
];
}Or use ApiCrudControllerBase for CRUD operations in controllers
[Tags("Customers Controller based")]
[Route($"api/v2/[controller]")]
public class CustomersController(IMediator commandBus, IMapper mapper)
: ApiCrudControllerBase<Customer, CustomerGetDto, CustomerPostDto, CustomerPutDto, int>(commandBus, mapper)
{
}You can also override your Where and Include clauses
[Tags("Customers Controller based")]
[Route($"api/v2/[controller]")]
public class CustomersController(IMediator commandBus, IMapper mapper)
: ApiCrudControllerBase<Customer, CustomerGetDto, CustomerPostDto, CustomerPutDto, int>(commandBus, mapper)
{
public override Expression<Func<Customer, bool>> GetWhere => x => x.Name.StartsWith("a");
public override List<Expression<Func<Customer, object>>> GetIncludes => [x => x.Invoices];
}Implement your own specific Request:
public class SpecificDeleteRequest : IRequest<BaseResponse<Customer>>
{
public required int Id { get; init; }
}With your own specific Command using CleanCodeJN.Repository
public class SpecificDeleteCommand(IRepository<Customer, int> repository) : IRequestHandler<SpecificDeleteRequest, BaseResponse<Customer>>
{
public async Task<BaseResponse<Customer>> Handle(SpecificDeleteRequest request, CancellationToken cancellationToken)
{
var deletedCustomer = await repository.Delete(request.Id, cancellationToken);
return await BaseResponse<Customer>.Create(deletedCustomer is not null, deletedCustomer);
}
}Use IOSP for complex business logic
Derive from BaseIntegrationCommand:
public class YourIntegrationCommand(ICommandExecutionContext executionContext)
: BaseIntegrationCommand(executionContext), IRequestHandler<YourIntegrationRequest, BaseResponse>Write Extensions on ICommandExecutionContext with Built in Requests or with your own
public static ICommandExecutionContext CustomerGetByIdRequest(
this ICommandExecutionContext executionContext, int customerId)
=> executionContext.WithRequest(
() => new GetByIdRequest<Customer>
{
Id = customerId,
Includes = [x => x.Invoices, x => x.OtherDependentTable],
},
CommandConstants.CustomerGetById);See the how clean your code will look like in the end
public class YourIntegrationCommand(ICommandExecutionContext executionContext)
: BaseIntegrationCommand(executionContext), IRequestHandler<YourIntegrationRequest, BaseResponse>
{
public async Task<BaseResponse> Handle(YourIntegrationRequest request, CancellationToken cancellationToken) =>
await ExecutionContext
.CandidateGetByIdRequest(request.Dto.CandidateId)
.CustomerGetByIdRequest(request.Dto.CustomerIds)
.GetOtherStuffRequest(request.Dto.XYZType)
.PostSomethingRequest(request.Dto)
.SendMailRequest()
.Execute(cancellationToken);
}