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 uses build-in Command-Abstraction to use basic CRUD operations with the ability to customize complex business logic by the power of the the Integration/Operation Segregation Principle.
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() to your Program.cs:
app.RegisterApis();
Start writing Apis by implementing IApi:
public class CustomersV1Api : IApi
{
public List<string> Tags => ["Customers V1"];
public string Route => $"api/v1/customers";
public List<Func<WebApplication, RouteHandlerBuilder>> HttpMethods =>
[
app => app.MapGet<Customer, CustomerGetDto>(Route, Tags),
app => app.MapGetById<Customer, CustomerGetDto>(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 V1"];
public string Route => $"api/v1/customers";
public List<Func<WebApplication, RouteHandlerBuilder>> HttpMethods =>
[
app => app.MapGet<Customer, CustomerGetDto>(Route, Tags, where: x => x.Name.StartsWith("a")),
];
}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(IIntRepository<Customer> 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);
}