Table of Contents

Class HttpResultExtensions

Namespace
Trellis.Asp
Assembly
Trellis.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 9457) 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

ToCreatedAtRouteHttpResult<TValue>(Result<TValue>, string, Func<TValue, RouteValueDictionary>, TrellisAspOptions?)

Converts a Result<TValue> to an IResult that returns 201 Created with a Location header on success, or Problem Details on failure.

public static IResult ToCreatedAtRouteHttpResult<TValue>(this Result<TValue> result, string routeName, Func<TValue, RouteValueDictionary> routeValues, TrellisAspOptions? options = null)

Parameters

result Result<TValue>

The result object to convert.

routeName string

The name of the route to use for generating the Location header URL.

routeValues Func<TValue, RouteValueDictionary>

A function that extracts route values from the result value for URL generation. Return a RouteValueDictionary to remain AOT-compatible.

options TrellisAspOptions

Optional custom error-to-status-code mappings. When null, uses default mappings.

Returns

IResult
  • 201 Created with Location header and value if result is successful
  • 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

POST Minimal API endpoint creating a user:

app.MapPost("/users", (CreateUserRequest request, IUserService service) =>
    EmailAddress.TryCreate(request.Email)
        .Bind(email => service.CreateUser(email))
        .Map(user => new UserDto(user))
        .ToCreatedAtRouteHttpResult(
            routeName: "GetUser",
            routeValues: dto => new RouteValueDictionary(new { id = dto.Id })));

Remarks

This method is ideal for POST endpoints that create a resource and need to return a 201 Created response with a Location header pointing to the GET endpoint for the new resource.

ToCreatedAtRouteHttpResult<TValue, TOut>(Result<TValue>, string, Func<TValue, RouteValueDictionary>, Func<TValue, TOut>, TrellisAspOptions?)

Converts a Result<TValue> to an IResult that returns 201 Created with a Location header on success (with value transformation), or Problem Details on failure.

public static IResult ToCreatedAtRouteHttpResult<TValue, TOut>(this Result<TValue> result, string routeName, Func<TValue, RouteValueDictionary> routeValues, Func<TValue, TOut> map, TrellisAspOptions? options = null)

Parameters

result Result<TValue>

The result object to convert.

routeName string

The name of the route to use for generating the Location header URL.

routeValues Func<TValue, RouteValueDictionary>

A function that extracts route values from the result value for URL generation. Return a RouteValueDictionary to remain AOT-compatible.

map Func<TValue, TOut>

A function that transforms the input value to the output type for the response body.

options TrellisAspOptions

Optional custom error-to-status-code mappings. When null, uses default mappings.

Returns

IResult
  • 201 Created with Location header and mapped value if result is successful
  • 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 input result.

TOut

The type of the value in the response body.

Examples

POST Minimal API endpoint with entity-to-DTO mapping:

app.MapPost("/users", (CreateUserRequest request, IUserService service) =>
    EmailAddress.TryCreate(request.Email)
        .Bind(email => service.CreateUser(email))
        .ToCreatedAtRouteHttpResult(
            routeName: "GetUser",
            routeValues: user => new RouteValueDictionary(new { id = user.Id }),
            map: user => new UserDto(user)));

ToCreatedHttpResult<TIn, TOut>(Result<TIn>, HttpContext, Func<TIn, string>, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>, TrellisAspOptions?)

Converts a Result<TValue> to a 201 Created Minimal API IResult with a URI, representation metadata headers, and a mapping function.

public static IResult ToCreatedHttpResult<TIn, TOut>(this Result<TIn> result, HttpContext httpContext, Func<TIn, string> uriSelector, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map, TrellisAspOptions? options = null)

Parameters

result Result<TIn>
httpContext HttpContext
uriSelector Func<TIn, string>
metadataSelector Func<TIn, RepresentationMetadata>
map Func<TIn, TOut>
options TrellisAspOptions

Returns

IResult

Type Parameters

TIn
TOut

ToHttpResult(Error, TrellisAspOptions?)

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

public static IResult ToHttpResult(this Error error, TrellisAspOptions? options = null)

Parameters

error Error

The domain error to convert.

options TrellisAspOptions

Optional custom error-to-status-code mappings. When null, uses default mappings.

Returns

IResult

An IResult with Problem Details (RFC 9457) response. The HTTP status code is resolved from TrellisAspOptions (configured via AddTrellisAsp). The default mappings are:

Domain Error TypeDefault HTTP 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

Status codes are resolved via TrellisAspOptions, which is configured by calling AddTrellisAsp at startup. Any mapping can be overridden:

builder.Services.AddTrellisAsp(options =>
{
    options.MapError<DomainError>(StatusCodes.Status400BadRequest);
});

If AddTrellisAsp is not called, the default mappings shown above are used.

All responses use Problem Details format (RFC 9457) 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>, long, long, long, TrellisAspOptions?)

Converts a Result<TValue> to a Minimal API IResult that returns 206 Partial Content with a Content-Range header when the result represents a subset, or 200 OK when it represents the complete set.

public static IResult ToHttpResult<TValue>(this Result<TValue> result, long from, long to, long totalLength, TrellisAspOptions? options = null)

Parameters

result Result<TValue>

The result object to convert.

from long

The starting index of the range (zero-indexed, inclusive).

to long

The ending index of the range (zero-indexed, inclusive).

totalLength long

The total number of items available.

options TrellisAspOptions

Optional custom error-to-status-code mappings.

Returns

IResult

206 Partial Content when the range is a subset, 200 OK when complete, or an error response.

Type Parameters

TValue

The type of the value contained in the result.

ToHttpResult<TValue>(Result<TValue>, TrellisAspOptions?)

Converts a Result<TValue> to an IResult with appropriate HTTP status code.

public static IResult ToHttpResult<TValue>(this Result<TValue> result, TrellisAspOptions? options = null)

Parameters

result Result<TValue>

The result object to convert.

options TrellisAspOptions

Optional custom error-to-status-code mappings. When null, uses default mappings.

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 returning 201 Created with Location header:

app.MapPost("/users", (CreateUserRequest request, IUserService userService) =>
    EmailAddress.TryCreate(request.Email)
        .Combine(FirstName.TryCreate(request.FirstName))
        .Bind((email, name) => userService.CreateUser(email, name))
        .ToCreatedAtRouteHttpResult(
            routeName: "GetUser",
            routeValues: user => new RouteValueDictionary(new { id = user.Id }),
            map: user => new UserDto(user)));

// Success: 201 Created with Location: /api/users/{id}
// 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.

ToHttpResult<TIn, TOut>(Result<TIn>, HttpContext, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>, TrellisAspOptions?)

Converts a Result<TValue> to a Minimal API IResult with representation metadata headers (ETag, Last-Modified, Vary, etc.) and conditional request evaluation. For GET/HEAD, evaluates If-None-Match/If-Modified-Since → 304, and If-Match/If-Unmodified-Since → 412.

public static IResult ToHttpResult<TIn, TOut>(this Result<TIn> result, HttpContext httpContext, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map, TrellisAspOptions? options = null)

Parameters

result Result<TIn>
httpContext HttpContext
metadataSelector Func<TIn, RepresentationMetadata>
map Func<TIn, TOut>
options TrellisAspOptions

Returns

IResult

Type Parameters

TIn
TOut

ToHttpResult<TIn, TOut>(Result<TIn>, Func<TIn, ContentRangeHeaderValue>, Func<TIn, TOut>, TrellisAspOptions?)

Converts a Result<TValue> to a Minimal API IResult that returns 206 Partial Content when the lambda-provided ContentRangeHeaderValue indicates a partial range, or 200 OK when the response contains the complete set.

public static IResult ToHttpResult<TIn, TOut>(this Result<TIn> result, Func<TIn, ContentRangeHeaderValue> funcRange, Func<TIn, TOut> funcValue, TrellisAspOptions? options = null)

Parameters

result Result<TIn>

The result object to convert.

funcRange Func<TIn, ContentRangeHeaderValue>

A function that extracts the Content-Range information from the result value.

funcValue Func<TIn, TOut>

A function that maps the result value to the response body.

options TrellisAspOptions

Optional custom error-to-status-code mappings.

Returns

IResult

206 Partial Content when partial, 200 OK when complete, or an error response.

Type Parameters

TIn

The type of the value contained in the input result.

TOut

The type of the value in the response body.

ToUpdatedHttpResult<TIn, TOut>(Result<TIn>, HttpContext, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>, TrellisAspOptions?)

Converts a successful Result<TValue> to an updated response for Minimal APIs with dynamic metadata, honoring RFC 7240 Prefer.

public static IResult ToUpdatedHttpResult<TIn, TOut>(this Result<TIn> result, HttpContext httpContext, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map, TrellisAspOptions? options = null)

Parameters

result Result<TIn>

The result from the update operation.

httpContext HttpContext

The HTTP context (used to read Prefer header and set response headers).

metadataSelector Func<TIn, RepresentationMetadata>

Function to build metadata from the domain value (e.g., extract ETag).

map Func<TIn, TOut>

Function to transform the domain value to a response DTO.

options TrellisAspOptions

Optional custom error-to-status-code mappings.

Returns

IResult

An IResult with appropriate status code and headers.

Type Parameters

TIn

The domain type in the result.

TOut

The mapped output type for the response body.

ToUpdatedHttpResult<TIn, TOut>(Result<TIn>, HttpContext, RepresentationMetadata?, Func<TIn, TOut>, TrellisAspOptions?)

Converts a successful Result<TValue> to an updated response for Minimal APIs, honoring RFC 7240 Prefer. Returns 200 OK with body by default, or 204 No Content when Prefer: return=minimal is present. On failure, returns the appropriate error response.

public static IResult ToUpdatedHttpResult<TIn, TOut>(this Result<TIn> result, HttpContext httpContext, RepresentationMetadata? metadata, Func<TIn, TOut> map, TrellisAspOptions? options = null)

Parameters

result Result<TIn>

The result from the update operation.

httpContext HttpContext

The HTTP context (used to read Prefer header and set response headers).

metadata RepresentationMetadata

Optional representation metadata (ETag, Last-Modified, etc.).

map Func<TIn, TOut>

Function to transform the domain value to a response DTO.

options TrellisAspOptions

Optional custom error-to-status-code mappings.

Returns

IResult

An IResult with appropriate status code and headers.

Type Parameters

TIn

The domain type in the result.

TOut

The mapped output type for the response body.