A small, opinionated library that lets you map FluentResults Result and Result<T> to HTTP responses declaratively and consistently, without leaking HTTP concerns into your domain layer.
$ dotnet add package FluentResults.HttpMappingFluentResults.HttpMapping is a small, opinionated library that let's you map FluentResults Result and Result<T> to HTTP responses declaratively, consistently, and without leaking HTTP concerns into your domain layer.
When using FluentResults in Web APIs, a common problem quickly appears:
Result objects;Typical symptoms:
if (result.IsFailed) blocks everywhere;FluentResults.HttpMapping solves this by introducing a centralized mapping layer:
Result / Result<T>;This package is intentionally small and strict.
Core principles:
IResult and Results.*;This is not a framework. It’s a thin, predictable mapping layer.
dotnet add package FluentResults.HttpMapping
Configure your mapping rules once at startup:
builder.Services.AddHttpResultMapping(mapper =>
{
mapper
.WhenFailure()
.Problem(p => p
.WithStatus(System.Net.HttpStatusCode.InternalServerError)
.WithTitle("Unexpected failure"));
});
Endpoints simply return Result objects:
app.MapGet("/example", ([FromServices] IHttpResultMapper mapper) =>
{
return mapper.Map(Result.Ok("Hello world"));
});
No if, no branching, no HTTP logic in the endpoint.
Rules are evaluated in order. The first rule that matches produces the HTTP response and no further rules are evaluated. Because of that, more specific rules must come before more generic ones.
First match wins!
mapper
.WhenReason<NotFoundError>()
.Map(_ => Results.StatusCode(StatusCodes.Status404NotFound));
mapper
.WhenReason<ValidationError>()
.Problem(p => p.WithValidationProblem<ValidationError>(
e => e.Errors
));
Produces a standard Problem Details response with validation errors.
Headers are defined parallel to mapping, not inside the body logic:
mapper
.WhenReason<SecurityError>()
.WithHeader("WWW-Authenticate", "Bearer")
.Map(ctx => Results.Json(
new
{
error = "invalid_token",
error_description = ctx.Result.FirstReason<SecurityError>().Message
}
));
Rules can match reasons by metadata keys or values:
mapper
.WhenReasonWithMetadata<Error>("error-codes")
.Problem(p => p
.WithStatus(System.Net.HttpStatusCode.InternalServerError)
.WithTitle("An internal server error occurred.")
.WithDetail(ctx =>
ctx.Result.FirstReasonWithMetadata<Error>("error-codes").Message)
.WithExtension("error-codes", ctx =>
ctx.Result.GetMetadata("error-codes"))
);
Always define a fallback failure rule last:
mapper
.WhenFailure()
.Problem(p => p
.WithStatus(System.Net.HttpStatusCode.InternalServerError)
.WithTitle("Unexpected failure"));
Success handling is built in:
Result<T> → 200 OK with bodyResult → 204 No ContentYou can override this behavior by defining your own success rules:
mapper
.WhenSuccess()
.Map(_ => Results.Ok());
A complete example configuration:
builder.Services.AddHttpResultMapping(mapper =>
{
mapper
.WhenReason<ValidationError>()
.Problem(p => p.WithValidationProblem<ValidationError>(e => e.Errors));
mapper
.WhenReason<SecurityError>()
.WithHeader("WWW-Authenticate", "Bearer")
.Map(ctx => Results.Json(
new
{
error = "invalid_token",
error_description = ctx.Result.FirstReason<SecurityError>().Message
}
));
mapper
.WhenReason<NotFoundError>()
.Map(_ => Results.StatusCode(StatusCodes.Status404NotFound));
mapper
.WhenReasonWithMetadata<Error>("error-codes")
.Problem(p => p
.WithStatus(System.Net.HttpStatusCode.InternalServerError)
.WithTitle("An internal server error occurred.")
.WithDetail(ctx =>
ctx.Result.FirstReasonWithMetadata<Error>("error-codes").Message)
.WithExtension("error-codes", ctx =>
ctx.Result.GetMetadata("error-codes"))
);
mapper
.WhenFailure()
.Problem(p => p
.WithStatus(System.Net.HttpStatusCode.InternalServerError)
.WithTitle("Unexpected failure"));
});
Although this package is primarily designed for Minimal APIs, it can also be used in ASP.NET Core applications that rely on MVC controllers.
The HTTP mapping system always produces an ASP.NET IResult. To return that result from a controller action, you can adapt it to an IActionResult using the provided MVC adapter.
using FluentResults;
using FluentResults.HttpMapping.Execution;
using FluentResults.HttpMapping.MVC;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("example")]
public class ExampleController : ControllerBase
{
private readonly IHttpResultMapper _mapper;
public ExampleController(IHttpResultMapper mapper)
{
_mapper = mapper;
}
[HttpGet("success")]
public IActionResult Success()
{
var result = Result.Ok(new { Message = "Hello from MVC" });
return _mapper.Map(result).ToActionResult();
}
[HttpGet("failure")]
public IActionResult Failure()
{
var result = Result.Fail("Something went wrong");
return _mapper.Map(result).ToActionResult();
}
}
IResultThis ensures the same rules and behavior are shared between Minimal APIs and MVC controllers without duplication or special configuration.
It is a mapping layer — nothing more, nothing less.
FluentResults.HttpMapping gives you:
If you already use FluentResults, this package lets your HTTP layer finally match the same level of clarity.
If this project helped you, you can support it: