Use Vulthil.SharedKernel.Api to standardize API endpoint composition.
- Endpoint/controller base abstractions
- API-layer extension methods and conventions
- Keep transport concerns in API layer
- Translate
Resultvalues to HTTP responses centrally - Reuse endpoint conventions across services
public sealed class GetUserEndpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapGet("/users/{id:guid}", async (Guid id, ISender sender) =>
{
var result = await sender.SendAsync(new GetUserQuery(id));
return result.ToIResult();
});
}
}ToIResult() returns Results<Ok<T>, ValidationProblem, NotFound, Conflict, ProblemHttpResult>, so OpenAPI automatically documents all possible response types. At runtime every failure — including NotFound and Conflict — is returned as a problem response (RFC 7807) with the status mapped from the ErrorType and the error's description as detail; the NotFound/Conflict union members exist for the OpenAPI documentation. See Result Pattern — Mapping to HTTP Responses.
// In Program.cs
builder.Services.AddEndpoints(typeof(Program).Assembly);
builder.Services.AddOpenApiServices();
var app = builder.Build();
app.MapEndpoints();
app.MapOpenApiEndpoints();Derive from BaseController for the standard [ApiController]/route conventions and a Logger property resolved for the concrete controller type (no constructor logger argument required). Controllers can return typed results directly for OpenAPI documentation:
public sealed class UsersController(ISender sender) : BaseController
{
[HttpGet("{id:guid}")]
public async Task<Results<Ok<UserDto>, ValidationProblem, NotFound, Conflict, ProblemHttpResult>> Get(Guid id)
{
var result = await sender.SendAsync(new GetUserQuery(id));
return result.ToIResult();
}
}Or use IActionResult with model-state error translation:
public sealed class UsersController(ISender sender) : BaseController
{
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id)
{
var result = await sender.SendAsync(new GetUserQuery(id));
return result.ToActionResult(this);
}
}