ApiFluentResults.AspNetCore
2.0.1
dotnet add package ApiFluentResults.AspNetCore --version 2.0.1
NuGet\Install-Package ApiFluentResults.AspNetCore -Version 2.0.1
<PackageReference Include="ApiFluentResults.AspNetCore" Version="2.0.1" />
<PackageVersion Include="ApiFluentResults.AspNetCore" Version="2.0.1" />
<PackageReference Include="ApiFluentResults.AspNetCore" />
paket add ApiFluentResults.AspNetCore --version 2.0.1
#r "nuget: ApiFluentResults.AspNetCore, 2.0.1"
#:package ApiFluentResults.AspNetCore@2.0.1
#addin nuget:?package=ApiFluentResults.AspNetCore&version=2.0.1
#tool nuget:?package=ApiFluentResults.AspNetCore&version=2.0.1
ApiFluentResults
A lightweight solution to map FluentResults to HTTP responses in ASP.NET Core using ProblemDetails, keeping controllers clean and aligned with Clean Architecture and the Result Pattern.
The solution is split into two NuGet packages to support modular and layered architectures.
Packages
ApiFluentResults.Domain
Contains standard and base error types that can be used from any layer (Domain / Application) without depending on ASP.NET Core.
This package is intended to be referenced from core layers in Clean Architecture.
Included errors:
NotFoundErrorConflictErrorValidationErrorUnauthorizedErrorForbiddenErrorDomainErrorBaseFluentError(base class for all errors)
dotnet add package ApiFluentResults.Domain
ApiFluentResults.AspNetCore
Provides ASP.NET Core integration to:
- Automatically map FluentResults errors
- Resolve the correct HTTP status code
- Return standardized ProblemDetails responses
- Support custom domain errors without controller boilerplate
This package depends on ApiFluentResults.Domain.
dotnet add package ApiFluentResults.AspNetCore
Getting Started
Base Controller
Inherit from FluentResultController to enable result mapping:
public class FluentResultController : ControllerBase
{
protected ResultMapper<T> MapResult<T>(Result<T> result) => new(result, this);
protected ResultMapper<object> MapResult(Result result) =>
new(result.ToResult<object>(), this);
}
Controller Usage Example
Public class OrdersController : FluentResultController
{
// ...
[HttpPost("{id}/pay")]
public async Task<IActionResult> PayOrder(
[FromRoute] long id,
[FromBody] List<PayOrderCommand.PaymentDto> payOrders
)
{
var command = new PayOrderCommand(id, payOrders);
var result = await Mediator.Send(command);
return MapResult(result)
.ConflictFor<OrderAlreadyPaidError>()
.Ok();
}
}
No switch statements No if/else chains No HTTP concerns leaking into Application or Domain layers
Supported Success Responses
Ok()
.OkEmpty()
.Created()
.CreatedAtAction()
.NoContent()
.Accepted()
.Status(StatusCode...)
Example:
return MapResult(result)
.Created();
Default Error Mappings
The following error types are mapped automatically:
| Error Type | HTTP Status |
|---|---|
| NotFoundError | 404 |
| ConflictError | 409 |
| ValidationError | 400 |
| UnauthorizedError | 401 |
| ForbiddenError | 403 |
| DomainError | 422 |
Business Rule Validation
You can return multiple validation errors using DomainError or your custom error:
public static Result<Order> Create(
int branchId,
int orderTypeId,
string currencyCode,
string orderNumber,
CustomerSnapshot customerSnapshot,
long? customerId,
decimal? descountAmount = 0,
long? tableSessionId = null
)
{
if (branchId is 0)
return Result.Fail(new DomainError("Branch is required"));
if (orderTypeId is 0)
return Result.Fail(new DomainError("Type order is required"));
.....
return new Order(
branchId,
orderTypeId,
currencyCode,
orderNumber,
customerSnapshot,
customerId,
descountAmount,
tableSessionId
);
}
Custom Errors
You can define your own business errors by inheriting from BaseFluentError, It is not necessary to map the error code,
as by default the status code will be 422 (Unprocessable Entity).:
public class OrderAlreadyPaidError : BaseFluentError
{
public OrderAlreadyPaidError(string orderNumber)
: base(
$"Order {orderNumber} has already been paid.",
"ORDER_ALREADY_PAID"
)
{
Metadata.Add("orderId", orderNumber);
}
}
If you want a different type of error, assign the error to an HTTP status code in the controller:
return MapResult(result)
.ConflictFor<OrderAlreadyPaidError>()
.Ok();
Custom Status Code Mapping
If there is no predefined method for the HTTP status you need, you can use CustomErrorFor:
return MapResult(result)
.CustomErrorFor<OrderAlreadyPaidError>(418)
.Ok();
This allows full control over the returned HTTP status code.
ProblemDetails Response Format
All error responses follow the ProblemDetails standard, with additional support for:
- code (business error code)
- metadata
- errors (validation errors)
Example response:
{
"type": "https://httpstatuses.com/409",
"title": "Conflict",
"status": 409,
"detail": "Order 123 has already been paid.",
"instance": "/api/orders/123/pay",
"code": "ORDER_ALREADY_PAID",
"metadata": {
"orderId": "123"
}
}
Benefits
- Clean and declarative controllers
- Clear separation between domain logic and HTTP concerns
- Fully compatible with FluentResults
- Ideal for Clean, Hexagonal, and Modular Architectures
- Consistent and standardized HTTP error responses
When to Use
- ASP.NET Core APIs
- Applications using the Result Pattern
- Systems with explicit business rules
- Teams looking to reduce controller boilerplate
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ApiFluentResults.Domain (>= 1.1.0)
- FluentResults (>= 4.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
- update readme