Class HttpResultExtensions
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
resultResult<TValue>The result object to convert.
routeNamestringThe name of the route to use for generating the Location header URL.
routeValuesFunc<TValue, RouteValueDictionary>A function that extracts route values from the result value for URL generation. Return a RouteValueDictionary to remain AOT-compatible.
optionsTrellisAspOptionsOptional 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
TValueThe 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
resultResult<TValue>The result object to convert.
routeNamestringThe name of the route to use for generating the Location header URL.
routeValuesFunc<TValue, RouteValueDictionary>A function that extracts route values from the result value for URL generation. Return a RouteValueDictionary to remain AOT-compatible.
mapFunc<TValue, TOut>A function that transforms the input value to the output type for the response body.
optionsTrellisAspOptionsOptional 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
TValueThe type of the value contained in the input result.
TOutThe 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
resultResult<TIn>httpContextHttpContexturiSelectorFunc<TIn, string>metadataSelectorFunc<TIn, RepresentationMetadata>mapFunc<TIn, TOut>optionsTrellisAspOptions
Returns
Type Parameters
TInTOut
ToHttpResult(Error, TrellisAspOptions?)
public static IResult ToHttpResult(this Error error, TrellisAspOptions? options = null)
Parameters
errorErrorThe domain error to convert.
optionsTrellisAspOptionsOptional 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 Type Default 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
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 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>, 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
resultResult<TValue>The result object to convert.
fromlongThe starting index of the range (zero-indexed, inclusive).
tolongThe ending index of the range (zero-indexed, inclusive).
totalLengthlongThe total number of items available.
optionsTrellisAspOptionsOptional 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
TValueThe 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
resultResult<TValue>The result object to convert.
optionsTrellisAspOptionsOptional 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
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 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
resultResult<TIn>httpContextHttpContextmetadataSelectorFunc<TIn, RepresentationMetadata>mapFunc<TIn, TOut>optionsTrellisAspOptions
Returns
Type Parameters
TInTOut
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
resultResult<TIn>The result object to convert.
funcRangeFunc<TIn, ContentRangeHeaderValue>A function that extracts the Content-Range information from the result value.
funcValueFunc<TIn, TOut>A function that maps the result value to the response body.
optionsTrellisAspOptionsOptional custom error-to-status-code mappings.
Returns
- IResult
206 Partial Content when partial, 200 OK when complete, or an error response.
Type Parameters
TInThe type of the value contained in the input result.
TOutThe 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
resultResult<TIn>The result from the update operation.
httpContextHttpContextThe HTTP context (used to read Prefer header and set response headers).
metadataSelectorFunc<TIn, RepresentationMetadata>Function to build metadata from the domain value (e.g., extract ETag).
mapFunc<TIn, TOut>Function to transform the domain value to a response DTO.
optionsTrellisAspOptionsOptional custom error-to-status-code mappings.
Returns
- IResult
An IResult with appropriate status code and headers.
Type Parameters
TInThe domain type in the result.
TOutThe 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
resultResult<TIn>The result from the update operation.
httpContextHttpContextThe HTTP context (used to read Prefer header and set response headers).
metadataRepresentationMetadataOptional representation metadata (ETag, Last-Modified, etc.).
mapFunc<TIn, TOut>Function to transform the domain value to a response DTO.
optionsTrellisAspOptionsOptional custom error-to-status-code mappings.
Returns
- IResult
An IResult with appropriate status code and headers.
Type Parameters
TInThe domain type in the result.
TOutThe mapped output type for the response body.