Skip to main content

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

dotnet add package UnambitiousFx.Functional.AspNetCore

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 RFC ProblemDetails body.

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 typeHTTP statusBody
ValidationFailure400 Bad RequestProblemDetails
BadRequestFailure400 Bad RequestProblemDetails
NotFoundFailure404 Not FoundProblemDetails
UnauthorizedFailure401 UnauthorizedProblemDetails
UnauthenticatedFailure403 ForbiddenProblemDetails
ConflictFailure409 ConflictProblemDetails
ExceptionalFailure500 Internal Server ErrorProblemDetails
Any other failure500 Internal Server ErrorProblemDetails

:::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 IActionResult builders.
  • Custom Mappers — customize failure-to-status mapping and adapter policy.

See also