Class HttpResultExtensions
- Namespace
- FunctionalDdd
- Assembly
- FunctionalDdd.Asp.dll
Provides extension methods to convert Result types to ASP.NET Core Minimal API IResult responses. These methods bridge Railway Oriented Programming with ASP.NET Core Minimal APIs.
public static class HttpResultExtensions
- Inheritance
-
HttpResultExtensions
- Inherited Members
Remarks
These extensions enable clean, functional-style Minimal API endpoints by automatically mapping domain Result types to appropriate HTTP responses. They provide the same functionality as ActionResultExtensions but for the Minimal API programming model (ASP.NET Core 6+).
Key features:
- Automatic HTTP status code selection based on error type
- Problem Details (RFC 7807) formatting for errors
- Validation error formatting with field-level details
- Unit result to 204 No Content conversion
- Clean, declarative endpoint definitions
Usage pattern in Minimal APIs:
app.MapGet("/users/{id}", (string id, IUserService userService) =>
UserId.TryCreate(id)
.Bind(userService.GetUser)
.Map(user => new UserDto(user))
.ToHttpResult()
);
Methods
ToHttpResult(Error)
public static IResult ToHttpResult(this Error error)
Parameters
errorErrorThe domain error to convert.
Returns
- IResult
An IResult with Problem Details (RFC 7807) response containing:
Domain Error Type HTTP Status Code ValidationError 400 Bad Request with validation problem details and field errors BadRequestError 400 Bad Request UnauthorizedError 401 Unauthorized ForbiddenError 403 Forbidden NotFoundError 404 Not Found ConflictError 409 Conflict DomainError 422 Unprocessable Entity RateLimitError 429 Too Many Requests UnexpectedError 500 Internal Server Error ServiceUnavailableError 503 Service Unavailable Unknown types 500 Internal Server Error
Examples
Example Problem Details response for a validation error:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"detail": "User data validation failed",
"instance": "/api/users",
"errors": {
"email": ["Email address is invalid", "Email is required"],
"age": ["Age must be 18 or older"]
}
}
Using custom error types in domain logic:
public class UserService
{
public Result<User> GetUser(UserId id)
{
var user = _repository.FindById(id);
if (user == null)
return Error.NotFound($"User {id} not found", $"/users/{id}");
return user;
}
public Result<User> CreateUser(EmailAddress email, FirstName name)
{
if (_repository.ExistsByEmail(email))
return Error.Conflict("User with this email already exists");
var user = User.Create(email, name);
_repository.Add(user);
return user;
}
}
// Minimal API endpoint
app.MapGet("/users/{id}", (string id, UserService service) =>
UserId.TryCreate(id)
.Bind(service.GetUser)
.ToHttpResult());
// Error automatically mapped to 404 Not Found with Problem Details
Remarks
All responses use Problem Details format (RFC 7807) which provides a standard way to communicate errors in HTTP APIs. The format includes:
type: A URI reference identifying the problem typetitle: A short human-readable summarystatus: The HTTP status codedetail: A human-readable explanation (from error.Detail)instance: A URI reference identifying the specific occurrence (from error.Instance)
For ValidationError, the response includes an additional errors object
containing field-level validation messages grouped by field name.
ToHttpResult<TValue>(Result<TValue>)
Converts a Result<TValue> to an IResult with appropriate HTTP status code.
public static IResult ToHttpResult<TValue>(this Result<TValue> result)
Parameters
resultResult<TValue>The result object to convert.
Returns
- IResult
- 200 OK with value if result is successful (except for Unit)
- 204 No Content if result is successful and TValue is Unit
- Appropriate error status code (400-599) based on error type if result is failure
Type Parameters
TValueThe type of the value contained in the result.
Examples
Simple GET endpoint:
app.MapGet("/users/{id}", (Guid id, IUserRepository repository) =>
UserId.TryCreate(id)
.Bind(repository.GetAsync)
.Map(user => new UserDto(user))
.ToHttpResult());
// Success: 200 OK with UserDto
// Not found: 404 Not Found with Problem Details
// Validation error: 400 Bad Request with validation details
POST endpoint with validation:
app.MapPost("/users", (CreateUserRequest request, IUserService userService) =>
EmailAddress.TryCreate(request.Email)
.Combine(FirstName.TryCreate(request.FirstName))
.Bind((email, name) => userService.CreateUser(email, name))
.Map(user => new UserDto(user))
.ToHttpResult());
// Success: 200 OK with UserDto
// Validation error: 400 Bad Request with field-level errors
// Conflict: 409 Conflict if user already exists
DELETE endpoint returning Unit:
app.MapDelete("/users/{id}", (Guid id, IUserRepository repository) =>
UserId.TryCreate(id)
.Bind(repository.DeleteAsync)
.ToHttpResult());
// Success: 204 No Content (automatic for Unit)
// Not found: 404 Not Found
Complex endpoint with multiple operations:
app.MapPost("/orders", async (
CreateOrderRequest request,
IOrderService orderService,
IEventBus eventBus) =>
await CustomerId.TryCreate(request.CustomerId)
.BindAsync(orderService.GetCustomerAsync)
.BindAsync(customer => orderService.CreateOrderAsync(customer, request.Items))
.TapAsync(async order => await eventBus.PublishAsync(new OrderCreatedEvent(order.Id)))
.MapAsync(order => new OrderDto(order))
.ToHttpResultAsync());
Remarks
This is the primary method for converting domain results to HTTP responses in Minimal APIs. It automatically selects the appropriate status code based on the error type.
Special handling for Unit: Since Unit represents "no value", successful Unit results return 204 No Content instead of 200 OK. This is appropriate for operations like DELETE or state-changing operations that don't return data.