Table of Contents

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)

Converts a domain Error to an IResult with appropriate HTTP status code and Problem Details format.

public static IResult ToHttpResult(this Error error)

Parameters

error Error

The domain error to convert.

Returns

IResult

An IResult with Problem Details (RFC 7807) response containing:

Domain Error TypeHTTP Status Code
ValidationError400 Bad Request with validation problem details and field errors
BadRequestError400 Bad Request
UnauthorizedError401 Unauthorized
ForbiddenError403 Forbidden
NotFoundError404 Not Found
ConflictError409 Conflict
DomainError422 Unprocessable Entity
RateLimitError429 Too Many Requests
UnexpectedError500 Internal Server Error
ServiceUnavailableError503 Service Unavailable
Unknown types500 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 type
  • title: A short human-readable summary
  • status: The HTTP status code
  • detail: 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

result Result<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

TValue

The 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.