NetEvolve.Pulse.AspNetCore provides IEndpointRouteBuilder extension methods that map Pulse mediator commands and queries directly to ASP.NET Core Minimal API HTTP endpoints. Eliminate the boilerplate of endpoint lambdas that only forward to IMediator.
MapCommand<TCommand, TResponse>: Maps a command to an HTTP endpoint returning200 OKwith the response. Defaults toPOSTwhen no method is specified; accepts anyCommandHttpMethodvalue.MapCommand<TCommand>: Maps a void command to an HTTP endpoint returning204 No Content. Defaults toPOSTwhen no method is specified; accepts anyCommandHttpMethodvalue.MapQuery<TQuery, TResponse>: Maps a query to aGETendpoint returning200 OKwith the result.CommandHttpMethodenum: Strongly-typed HTTP method selection —Post,Put,Patch,Delete.GETis excluded by design since commands are state-changing operations.- CancellationToken propagation: Automatically propagates the HTTP request cancellation token.
- OpenAPI compatible: Returns typed results (
TypedResults) soWithOpenApi()produces correct response schemas. - DI-based:
IMediatoris resolved from the request scope at runtime — no compile-time dependency onNetEvolve.Pulse.
Install-Package NetEvolve.Pulse.AspNetCoredotnet add package NetEvolve.Pulse.AspNetCore<PackageReference Include="NetEvolve.Pulse.AspNetCore" Version="x.x.x" />using NetEvolve.Pulse;
var builder = WebApplication.CreateBuilder(args);
// Register Pulse and handlers
builder.Services.AddPulse();
builder.Services.AddScoped<ICommandHandler<CreateOrderCommand, OrderResult>, CreateOrderHandler>();
builder.Services.AddScoped<ICommandHandler<UpdateOrderCommand, OrderResult>, UpdateOrderHandler>();
builder.Services.AddScoped<ICommandHandler<DeleteOrderCommand, Void>, DeleteOrderHandler>();
builder.Services.AddScoped<IQueryHandler<GetOrderQuery, OrderDto>, GetOrderHandler>();
var app = builder.Build();
// Map commands and queries — no boilerplate lambdas needed
app.MapCommand<CreateOrderCommand, OrderResult>("/orders"); // POST /orders
app.MapCommand<UpdateOrderCommand, OrderResult>("/orders/{id}", CommandHttpMethod.Put); // PUT /orders/{id}
app.MapCommand<DeleteOrderCommand>("/orders/{id}", CommandHttpMethod.Delete); // DELETE /orders/{id}
app.MapQuery<GetOrderQuery, OrderDto>("/orders/{id}"); // GET /orders/{id}
app.Run();Without this package you would write:
app.MapPost("/orders", async (CreateOrderCommand cmd, IMediator mediator, CancellationToken ct) =>
Results.Ok(await mediator.SendAsync<CreateOrderCommand, OrderResult>(cmd, ct)));
app.MapPut("/orders/{id}", async (UpdateOrderCommand cmd, IMediator mediator, CancellationToken ct) =>
Results.Ok(await mediator.SendAsync<UpdateOrderCommand, OrderResult>(cmd, ct)));
app.MapDelete("/orders/{id}", async ([FromBody] DeleteOrderCommand cmd, IMediator mediator, CancellationToken ct) =>
{
await mediator.SendAsync<DeleteOrderCommand>(cmd, ct);
return Results.NoContent();
});
app.MapGet("/orders/{id}", async ([AsParameters] GetOrderQuery query, IMediator mediator, CancellationToken ct) =>
Results.Ok(await mediator.QueryAsync<GetOrderQuery, OrderDto>(query, ct)));MapCommand<TCommand, TResponse> binds the request body to TCommand, sends it via IMediator.SendAsync, and returns 200 OK with the result. The default HTTP method is POST; use the CommandHttpMethod parameter to choose a different method:
// POST /orders (default)
app.MapCommand<CreateOrderCommand, OrderResult>("/orders");
// PUT /orders/{id}
app.MapCommand<UpdateOrderCommand, OrderResult>("/orders/{id}", CommandHttpMethod.Put);
// PATCH /orders/{id}
app.MapCommand<PatchOrderCommand, OrderResult>("/orders/{id}", CommandHttpMethod.Patch);public record CreateOrderCommand(string Sku, int Quantity) : ICommand<OrderResult>;
public record UpdateOrderCommand(Guid Id, string Sku, int Quantity) : ICommand<OrderResult>;
public record OrderResult(Guid OrderId, string Status);MapCommand<TCommand> binds the request body to TCommand, sends it via IMediator.SendAsync, and returns 204 No Content. The default HTTP method is POST:
// POST /orders/cancel (default)
app.MapCommand<CancelOrderCommand>("/orders/cancel");
// DELETE /orders/{id}
app.MapCommand<DeleteOrderCommand>("/orders/{id}", CommandHttpMethod.Delete);public record CancelOrderCommand(Guid Id) : ICommand;
public record DeleteOrderCommand(Guid Id) : ICommand;MapQuery<TQuery, TResponse> registers a GET endpoint that binds route parameters and query string to TQuery using [AsParameters], executes the query via IMediator.QueryAsync, and returns 200 OK with the result:
app.MapQuery<GetOrderQuery, OrderDto>("/orders/{id}");public record GetOrderQuery(Guid Id) : IQuery<OrderDto>;
public record OrderDto(Guid Id, string Sku, string Status);The CommandHttpMethod enum controls the HTTP method registered for command endpoints. GET is intentionally excluded because commands are state-changing operations — use MapQuery for read-only operations instead:
| Value | HTTP Method | Typical use |
|---|---|---|
Post (default) |
POST |
Create a new resource |
Put |
PUT |
Replace an existing resource |
Patch |
PATCH |
Partially update a resource |
Delete |
DELETE |
Remove a resource |
Note
Passing an undefined enum value throws ArgumentOutOfRangeException.
All methods return RouteHandlerBuilder, so you can chain Minimal API metadata:
app.MapCommand<CreateOrderCommand, OrderResult>("/orders")
.WithName("CreateOrder")
.WithTags("Orders")
.WithOpenApi()
.RequireAuthorization();
app.MapCommand<DeleteOrderCommand>("/orders/{id}", CommandHttpMethod.Delete)
.WithName("DeleteOrder")
.WithTags("Orders")
.WithOpenApi()
.RequireAuthorization();
app.MapQuery<GetOrderQuery, OrderDto>("/orders/{id}")
.WithName("GetOrder")
.WithTags("Orders")
.WithOpenApi()
.RequireAuthorization("ReadOrders");Combine with MapGroup for shared prefixes and metadata:
var orders = app.MapGroup("/orders")
.WithTags("Orders")
.RequireAuthorization();
orders.MapCommand<CreateOrderCommand, OrderResult>("/");
orders.MapCommand<UpdateOrderCommand, OrderResult>("/{id}", CommandHttpMethod.Put);
orders.MapCommand<DeleteOrderCommand>("/{id}", CommandHttpMethod.Delete);
orders.MapQuery<GetOrderQuery, OrderDto>("/{id}");- .NET 8.0, .NET 9.0, or .NET 10.0
- ASP.NET Core (included in the SDK)
NetEvolve.Pulse(or anyIMediatorimplementation) registered in the DI container
- NetEvolve.Pulse - Core CQRS mediator
- NetEvolve.Pulse.Extensibility - Extensibility contracts
- NetEvolve.Pulse.Polly - Polly v8 resilience policies integration
- NetEvolve.Pulse.EntityFramework - Entity Framework Core outbox persistence
- NetEvolve.Pulse.SqlServer - SQL Server ADO.NET outbox persistence
Contributions are welcome! Please read the Contributing Guidelines before submitting a pull request.
- Issues: Report bugs or request features on GitHub Issues
- Documentation: Read the full documentation at https://github.com/dailydevops/pulse
This project is licensed under the MIT License - see the LICENSE file for details.
Note
Made with ❤️ by the NetEvolve Team