Skip to main content

ASP.NET Core Integration

UnambitiousFx.Synapse.AspNetCore provides two invoker wrappers that translate Result<T> to HTTP responses automatically, so endpoint handlers stay free of mapping boilerplate.

Install

dotnet add package UnambitiousFx.Synapse.AspNetCore

Setup

builder.Services.AddSynapse(cfg => { /* ... */ });
builder.Services.AddSynapseAspNetCore();

var app = builder.Build();
app.UseCorrelationId(); // optional — reads/writes X-Correlation-Id header

HTTP request flow

IHttpInvoker — Minimal API

IHttpInvoker wraps IInvoker and converts Result<T> to IResult using IFailureHttpMapper for failures:

Default success mapping — 200 OK

app.MapGet("/tasks/{id}", async (
Guid id,
[FromServices] IHttpInvoker invoker,
CancellationToken ct) =>
await invoker.InvokeAsync(new GetTaskQuery(id), ct));
// Success → 200 OK with the value serialized as JSON
// Failure → mapped by IFailureHttpMapper (default: ProblemDetails)

Custom success mapping

Pass a delegate to control the success response:

app.MapPost("/tasks", async (
[FromBody] CreateTaskCommand cmd,
[FromServices] IHttpInvoker invoker,
CancellationToken ct) =>
await invoker.InvokeAsync(
cmd,
id => Results.Created($"/tasks/{id}", id), // ← custom success mapper
ct));

Fire-and-forget commands

app.MapDelete("/tasks/{id}", async (
Guid id,
[FromServices] IHttpInvoker invoker,
CancellationToken ct) =>
await invoker.InvokeAsync(new DeleteTaskCommand(id), ct));
// Success → 200 OK (no body)
// Failure → ProblemDetails

Streaming

app.MapGet("/tasks/stream", (
[FromServices] IHttpInvoker invoker,
CancellationToken ct) =>
invoker.InvokeStreamAsync(new StreamTasksQuery(), ct));
// Returns IAsyncEnumerable<TaskDto> — ASP.NET Core streams the JSON array

IHttpInvoker.InvokeStreamAsync unwraps Result<TItem> automatically: successful items are yielded, failures are silently skipped.

IMvcInvoker — Controller-based API

Same contract as IHttpInvoker but returns IActionResult for use in MVC controllers:

[ApiController]
[Route("tasks")]
public class TasksController : ControllerBase
{
private readonly IMvcInvoker _invoker;

public TasksController(IMvcInvoker invoker) => _invoker = invoker;

[HttpGet("{id}")]
public Task<IActionResult> Get(Guid id, CancellationToken ct) =>
_invoker.InvokeAsync(new GetTaskQuery(id), ct);

[HttpPost]
public Task<IActionResult> Create([FromBody] CreateTaskCommand cmd, CancellationToken ct) =>
_invoker.InvokeAsync(cmd, id => Created($"/tasks/{id}", id), ct);
}

Failure mapping — IFailureHttpMapper

By default, failures are mapped to RFC 9457 ProblemDetails responses. Replace the mapper with a custom implementation by registering it as a singleton before AddSynapseAspNetCore:

builder.Services.AddSingleton<IFailureHttpMapper, MyFailureHttpMapper>();
builder.Services.AddSynapseAspNetCore();
public class MyFailureHttpMapper : IFailureHttpMapper
{
public IResult Map(IFailure failure)
{
return failure switch
{
NotFoundFailure nf => Results.NotFound(new { nf.Message }),
ValidationFailure vf => Results.UnprocessableEntity(new { vf.Message }),
_ => Results.Problem(failure.ToString())
};
}
}

Correlation ID middleware

UseCorrelationId() reads the incoming X-Correlation-Id header and sets it on IContext.CorrelationId. It also writes the correlation ID back to the response header so callers can correlate requests across service boundaries:

app.UseCorrelationId();

If no X-Correlation-Id header is present, the context uses the ID already generated by IContextFactory.

NativeAOT / Trim compatibility

When publishing with PublishSingleFile=true or PublishAot=true, use WebApplication.CreateSlimBuilder and configure the JSON serializer context so all request/response types are included:

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(opts =>
opts.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

[JsonSerializable(typeof(CreateTaskCommand))]
[JsonSerializable(typeof(Guid))]
[JsonSerializable(typeof(TaskDto))]
internal partial class AppJsonContext : JsonSerializerContext { }

Also use the Source Generator to generate EventDispatcherRegistration, which is required for NativeAOT-safe polymorphic event dispatch.

See also

  • Getting Started — full end-to-end example with Minimal API.
  • StreamingIHttpInvoker.InvokeStreamAsync details.
  • Source Generator — NativeAOT-safe handler registration.
  • ContextIContext.CorrelationId and the UseCorrelationId header propagation.