ASP.NET Core Integration
UnambitiousFx.Functional.AspNetCore (v2.0.0) turns the functional types from UnambitiousFx.Functional into HTTP responses. Keep your domain and application services returning Result, Result<T>, and Maybe<T>, then adapt the outcome to HTTP at the API boundary — for both Minimal APIs (IResult) and MVC controllers (IActionResult).
The conversion is driven by fluent builders. A single failure-to-status mapper (IFailureHttpMapper) centralizes how each failure type becomes an HTTP status code and body, so transport concerns stay out of your business logic.
Install
- .NET CLI
- PackageReference
dotnet add package UnambitiousFx.Functional.AspNetCore
<PackageReference Include="UnambitiousFx.Functional.AspNetCore" Version="2.0.0" />
How it works
- Success branches are mapped to a status code (200/201/202/204) with the value serialized as the body.
- Failure branches are passed to the configured
IFailureHttpMapper, which produces a status code and (by default) an RFCProblemDetailsbody.
Registration (optional)
The builders work with no setup at all — they fall back to DefaultFailureHttpMapper when you don't pass a mapper. To register a shared mapper (and custom mappings) in DI, call AddResultHttp:
using UnambitiousFx.Functional.AspNetCore;
builder.Services.AddResultHttp();
AddResultHttp registers the resolved IFailureHttpMapper and a ResultHttpAdapterPolicy as singletons. Pass a configuration delegate to add custom mappings — see Custom Mappers. Inject the mapper into endpoints/controllers and pass it to the builder when you want DI-managed behavior:
app.MapGet("/users/{id:guid}", async (
Guid id,
IUserService service,
IFailureHttpMapper mapper) =>
await service.GetUserAsync(id).AsHttpBuilder(mapper));
Minimal API — quick look
Namespace: UnambitiousFx.Functional.AspNetCore.Http
using UnambitiousFx.Functional.AspNetCore.Http;
// Result<T> → 200 OK with the value, failures mapped automatically
app.MapGet("/users/{id:guid}", async (Guid id, IUserService service) =>
await service.GetUserAsync(id).AsHttpBuilder());
// Maybe<T> → 200 on Some, 404 on None
app.MapGet("/profiles/{id:guid}", async (Guid id, IProfileService service) =>
await service.FindProfileAsync(id).AsHttpBuilder());
// 201 Created with a Location header
app.MapPost("/users", async (CreateUserRequest request, IUserService service) =>
await service.CreateAsync(request)
.AsHttpBuilder()
.AsCreated(user => $"/users/{user.Id}"));
The builder is awaitable directly — await someResult.AsHttpBuilder() yields the IResult.
MVC — quick look
Namespace: UnambitiousFx.Functional.AspNetCore.Mvc
using UnambitiousFx.Functional.AspNetCore.Mvc;
[ApiController]
[Route("api/users")]
public sealed class UsersController : ControllerBase
{
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id, [FromServices] IUserService service)
=> await service.GetUserAsync(id).AsActionResultBuilder();
}
Default failure mapping
DefaultFailureHttpMapper maps the standard failure types from UnambitiousFx.Functional.Failures:
| Failure type | HTTP status | Body |
|---|---|---|
ValidationFailure | 400 Bad Request | ProblemDetails |
BadRequestFailure | 400 Bad Request | ProblemDetails |
NotFoundFailure | 404 Not Found | ProblemDetails |
UnauthorizedFailure | 401 Unauthorized | ProblemDetails |
UnauthenticatedFailure | 403 Forbidden | ProblemDetails |
ConflictFailure | 409 Conflict | ProblemDetails |
ExceptionalFailure | 500 Internal Server Error | ProblemDetails |
| Any other failure | 500 Internal Server Error | ProblemDetails |
:::note Naming vs. status
The default mapper maps UnauthorizedFailure to 401 and UnauthenticatedFailure to 403. If your API contract expects the conventional 401/403 split, override these with a custom mapper.
:::
Learn more
- HTTP Mapping (Minimal API) — the full builder API, status-code control, headers, Problem Details shape, and Minimal API patterns.
- MVC Patterns — controller usage and
IActionResultbuilders. - Custom Mappers — customize failure-to-status mapping and adapter policy.