I'm using fast endpoints in a .NET 9.0 web api project and I'm finding myself writing a lot of boiler plate to get the basic functions up and running. I'm writing just my basic database CRUD and I'm wondering if there's a way to reuse handlers instead of copying and pasting code between endpoints?
I currently have this class for getting a Contact object using my database service and Automapper:
public class GetContact(PostgresD db, IMapper mapper, ILogger<GetContact> logger) : EndpointWithoutRequest<ContactDto>
{
public override void Configure()
{
Get("/contact/{id:Guid}");
}
public override async Task HandleAsync(CancellationToken ct)
{
var contact = await db.FindAsync<Contact>(Route<Guid>("id"));
if (contact is null)
{
await SendNotFoundAsync(ct);
}
else
{
Response = mapper.Map<ContactDto>(contact);
}
}
}
Now I need to write my endpoints for other basic objects and it seems like I need to literally copy and paste this class, just changing the type of the Endpoint. Is there a way I could reuse this handler code instead of copypasta?
I've been trying to figure out some kind of route builder class but I keep getting hung up on how to define the handlers as delegates that I pass in, they need access to protected fields inside of the endpoint itself. I'm also really not sure that Fast Endpoints will recognize endpoint classes generated by a builder but that's a future me problem.
Ideally everything inside the HandleAsync()
call would be encapsulated in a delegate, but I can't figure out a way to write the delegate so that it will have access to protected class fields, and is also accessible when I instantiate the class.
I suggest you to create base abstract controller that derives from EndpointWithoutRequest
and overrides only the HandleAsync
method:
public abstract class BaseEndpointWithoutRequest<TEntity, TResponse>(PostgresD db, IMapper mapper, ILogger<GetContact> logger)
: EndpointWithoutRequest<TResponse>
{
public override async Task HandleAsync(CancellationToken ct)
{
var entity = await db.FindAsync<TEntity>(Route<Guid>("id"));
if (entity is null)
{
await SendNotFoundAsync(ct);
}
else
{
Response = mapper.Map<TResponse>(entity);
}
}
}
public class GetContact(PostgresD db, IMapper mapper, ILogger<GetContact> logger)
: BaseEndpointWithoutRequest<Contact, ContactDto>(db, mapper, logger)
{
public override void Configure()
{
Get("/contact/{id:Guid}");
}
}