Class ActionResultExtensions
Provides extension methods to convert Result types to ASP.NET Core ActionResult responses. These methods bridge Railway Oriented Programming with ASP.NET Core MVC/Web API controllers.
public static class ActionResultExtensions
- Inheritance
-
ActionResultExtensions
- Inherited Members
Remarks
These extensions enable clean, declarative controller code by automatically mapping domain Result types to appropriate HTTP responses. They handle:
- Automatic HTTP status code selection based on error type
- Problem Details (RFC 9457) formatting for errors
- Validation error formatting with ModelState
- Partial content (206) responses with range headers
- Unit result to 204 No Content conversion
Usage pattern in controllers:
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult<UserDto> GetUser(string id) =>
UserId.TryCreate(id)
.Bind(_userService.GetUser)
.Map(user => new UserDto(user))
.ToActionResult(this);
}
Methods
ToActionResultAsync<TIn, TOut>(Task<Result<TIn>>, ControllerBase, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>)
Async Task overload of metadata-selector-aware ToActionResult.
public static Task<ActionResult<TOut>> ToActionResultAsync<TIn, TOut>(this Task<Result<TIn>> resultTask, ControllerBase controller, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map)
Parameters
resultTaskTask<Result<TIn>>controllerControllerBasemetadataSelectorFunc<TIn, RepresentationMetadata>mapFunc<TIn, TOut>
Returns
- Task<ActionResult<TOut>>
Type Parameters
TInTOut
ToActionResultAsync<TIn, TOut>(Task<Result<TIn>>, ControllerBase, RepresentationMetadata, Func<TIn, TOut>)
Async Task overload of metadata-aware ToActionResult.
public static Task<ActionResult<TOut>> ToActionResultAsync<TIn, TOut>(this Task<Result<TIn>> resultTask, ControllerBase controller, RepresentationMetadata metadata, Func<TIn, TOut> map)
Parameters
resultTaskTask<Result<TIn>>controllerControllerBasemetadataRepresentationMetadatamapFunc<TIn, TOut>
Returns
- Task<ActionResult<TOut>>
Type Parameters
TInTOut
ToActionResultAsync<TIn, TOut>(ValueTask<Result<TIn>>, ControllerBase, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>)
Async ValueTask overload of metadata-selector-aware ToActionResult.
public static ValueTask<ActionResult<TOut>> ToActionResultAsync<TIn, TOut>(this ValueTask<Result<TIn>> resultTask, ControllerBase controller, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map)
Parameters
resultTaskValueTask<Result<TIn>>controllerControllerBasemetadataSelectorFunc<TIn, RepresentationMetadata>mapFunc<TIn, TOut>
Returns
- ValueTask<ActionResult<TOut>>
Type Parameters
TInTOut
ToActionResultAsync<TIn, TOut>(ValueTask<Result<TIn>>, ControllerBase, RepresentationMetadata, Func<TIn, TOut>)
Async ValueTask overload of metadata-aware ToActionResult.
public static ValueTask<ActionResult<TOut>> ToActionResultAsync<TIn, TOut>(this ValueTask<Result<TIn>> resultTask, ControllerBase controller, RepresentationMetadata metadata, Func<TIn, TOut> map)
Parameters
resultTaskValueTask<Result<TIn>>controllerControllerBasemetadataRepresentationMetadatamapFunc<TIn, TOut>
Returns
- ValueTask<ActionResult<TOut>>
Type Parameters
TInTOut
ToActionResult<TValue>(Error, ControllerBase)
Converts a domain Error to an ActionResult<TValue> with appropriate HTTP status code and Problem Details format.
public static ActionResult<TValue> ToActionResult<TValue>(this Error error, ControllerBase controllerBase)
Parameters
errorErrorThe domain error to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
Returns
- ActionResult<TValue>
An ActionResult 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 ModelState 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
Type Parameters
TValueThe type of the ActionResult value.
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"],
"age": ["Age must be 18 or older"]
}
}
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 compatible with ASP.NET Core ModelState.
ToActionResult<TValue>(Result<TValue>, ControllerBase)
Converts a Result<TValue> to an ActionResult<TValue> with appropriate HTTP status code.
public static ActionResult<TValue> ToActionResult<TValue>(this Result<TValue> result, ControllerBase controllerBase)
Parameters
resultResult<TValue>The result object to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
Returns
- ActionResult<TValue>
- 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:
[HttpGet("{id}")]
public ActionResult<UserDto> GetUser(Guid id) =>
UserId.TryCreate(id)
.Bind(_repository.GetAsync)
.Map(user => new UserDto(user))
.ToActionResult(this);
// 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:
[HttpPost]
public ActionResult<UserDto> CreateUser(CreateUserRequest request) =>
EmailAddress.TryCreate(request.Email)
.Combine(FirstName.TryCreate(request.FirstName))
.Bind((email, name) => _userService.CreateUser(email, name))
.ToCreatedAtActionResult(this,
actionName: nameof(GetUser),
routeValues: user => 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:
[HttpDelete("{id}")]
public ActionResult<Unit> DeleteUser(Guid id) =>
UserId.TryCreate(id)
.Bind(_repository.DeleteAsync)
.ToActionResult(this);
// Success: 204 No Content (automatic for Unit)
// Not found: 404 Not Found
Remarks
This is the primary method for converting domain results to HTTP responses in controllers. 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.
ToActionResult<TIn, TOut>(Result<TIn>, ControllerBase, Func<TIn, ContentRangeHeaderValue>, Func<TIn, TOut>)
Converts a Result<TValue> to an ActionResult<TValue> with support for partial content responses, using custom functions to extract range information and transform the value.
public static ActionResult<TOut> ToActionResult<TIn, TOut>(this Result<TIn> result, ControllerBase controllerBase, Func<TIn, ContentRangeHeaderValue> funcRange, Func<TIn, TOut> funcValue)
Parameters
resultResult<TIn>The result object to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
funcRangeFunc<TIn, ContentRangeHeaderValue>Function that extracts ContentRangeHeaderValue from the input value.
funcValueFunc<TIn, TOut>Function that transforms the input value to the output type.
Returns
- ActionResult<TOut>
- 206 Partial Content with Content-Range header if the range is a subset
- 200 OK if the range represents the complete set
- Appropriate error status code if result is failure
Type Parameters
TInThe type of the value contained in the input result.
TOutThe type of the value in the output ActionResult.
Examples
Using a result wrapper with pagination metadata:
public record PagedResult<T>(
IEnumerable<T> Items,
long From,
long To,
long TotalCount
);
[HttpGet]
public ActionResult<IEnumerable<UserDto>> GetUsers(
[FromQuery] int page = 0,
[FromQuery] int pageSize = 25)
{
return _userService
.GetPagedUsersAsync(page, pageSize)
.ToActionResult(
this,
funcRange: pagedResult => new ContentRangeHeaderValue(
pagedResult.From,
pagedResult.To,
pagedResult.TotalCount),
funcValue: pagedResult => pagedResult.Items.Select(u => new UserDto(u))
);
}
// Automatically returns 206 Partial Content with proper headers
// for partial results, 200 OK for complete results
Remarks
This overload is useful when the result value contains both the data and range information, and you need to transform the value before returning it.
Common scenarios:
- Returning a subset of properties from a complex result object
- Mapping domain entities to DTOs while preserving pagination info
- Extracting embedded pagination metadata from a wrapper object
ToActionResult<TIn, TOut>(Result<TIn>, ControllerBase, Func<TIn, RepresentationMetadata>, Func<TIn, TOut>)
Converts a Result to an ActionResult, applying dynamically-computed representation metadata headers (ETag, Last-Modified, Vary, Content-Language, Content-Location, Accept-Ranges). Evaluates all conditional request headers with correct RFC 9110 §13.2.2 precedence.
public static ActionResult<TOut> ToActionResult<TIn, TOut>(this Result<TIn> result, ControllerBase controller, Func<TIn, RepresentationMetadata> metadataSelector, Func<TIn, TOut> map)
Parameters
resultResult<TIn>The result to convert.
controllerControllerBaseThe controller context.
metadataSelectorFunc<TIn, RepresentationMetadata>Function to extract metadata from the domain value (e.g., build ETag from aggregate).
mapFunc<TIn, TOut>Function to transform the domain value to a response DTO.
Returns
- ActionResult<TOut>
An ActionResult with metadata headers and conditional request evaluation.
Type Parameters
TInThe domain type in the result.
TOutThe mapped output type for the response body.
Remarks
Use this overload when the metadata depends on the success value (e.g., ETag derived from an aggregate). The selector is not invoked when the result is a failure.
ToActionResult<TIn, TOut>(Result<TIn>, ControllerBase, Func<TIn, TOut>)
Converts a Result<TValue> to an ActionResult<TValue> by applying a mapping function on success, or Problem Details on failure.
public static ActionResult<TOut> ToActionResult<TIn, TOut>(this Result<TIn> result, ControllerBase controllerBase, Func<TIn, TOut> map)
Parameters
resultResult<TIn>The result object to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
mapFunc<TIn, TOut>Function that transforms the input value to the output type.
Returns
- ActionResult<TOut>
- 200 OK with transformed value if result is successful
- Appropriate error status code (400-599) based on error type if result is failure
Type Parameters
TInThe type of the value contained in the input result.
TOutThe type of the value in the output ActionResult.
Examples
GET endpoint mapping domain to DTO:
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(OrderId id)
{
var result = await _sender.Send(new GetOrderByIdQuery(id), ct);
return result.ToActionResult(this, OrderDto.From);
}
Remarks
Use this overload when the Mediator handler returns a domain type and the controller needs to map it to a DTO before returning.
ToActionResult<TIn, TOut>(Result<TIn>, ControllerBase, RepresentationMetadata, Func<TIn, TOut>)
Converts a Result to an ActionResult, applying representation metadata headers (ETag, Last-Modified, Vary, Content-Language, Content-Location, Accept-Ranges). Evaluates all conditional request headers (If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since) with correct RFC 9110 §13.2.2 precedence via ConditionalRequestEvaluator.
public static ActionResult<TOut> ToActionResult<TIn, TOut>(this Result<TIn> result, ControllerBase controller, RepresentationMetadata metadata, Func<TIn, TOut> map)
Parameters
resultResult<TIn>controllerControllerBasemetadataRepresentationMetadatamapFunc<TIn, TOut>
Returns
- ActionResult<TOut>
Type Parameters
TInTOut
ToCreatedAtActionResult<TValue>(Result<TValue>, ControllerBase, string, Func<TValue, object?>, string?)
Converts a Result<TValue> to an ActionResult<TValue> that returns 201 Created with a Location header on success, or Problem Details on failure.
public static ActionResult<TValue> ToCreatedAtActionResult<TValue>(this Result<TValue> result, ControllerBase controllerBase, string actionName, Func<TValue, object?> routeValues, string? controllerName = null)
Parameters
resultResult<TValue>The result object to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
actionNamestringThe name of the action to use for generating the Location header URL.
routeValuesFunc<TValue, object>A function that extracts route values from the result value for URL generation.
controllerNamestringOptional controller name. When null, the current controller is used.
Returns
- ActionResult<TValue>
- 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 endpoint creating a user:
[HttpPost]
public ActionResult<UserDto> CreateUser(CreateUserRequest request) =>
EmailAddress.TryCreate(request.Email)
.Combine(FirstName.TryCreate(request.FirstName))
.Bind((email, name) => _userService.CreateUser(email, name))
.Map(user => new UserDto(user))
.ToCreatedAtActionResult(this,
actionName: nameof(GetUser),
routeValues: dto => new { id = dto.Id });
// Success: 201 Created with Location: /api/users/{id}
// Validation error: 400 Bad Request with Problem Details
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.
ToCreatedAtActionResult<TValue, TOut>(Result<TValue>, ControllerBase, string, Func<TValue, object?>, Func<TValue, TOut>, string?)
Converts a Result<TValue> to an ActionResult<TValue> that returns 201 Created with a Location header on success (with value transformation), or Problem Details on failure.
public static ActionResult<TOut> ToCreatedAtActionResult<TValue, TOut>(this Result<TValue> result, ControllerBase controllerBase, string actionName, Func<TValue, object?> routeValues, Func<TValue, TOut> map, string? controllerName = null)
Parameters
resultResult<TValue>The result object to convert.
controllerBaseControllerBaseThe controller context used to create the ActionResult.
actionNamestringThe name of the action to use for generating the Location header URL.
routeValuesFunc<TValue, object>A function that extracts route values from the result value for URL generation.
mapFunc<TValue, TOut>A function that transforms the input value to the output type for the response body.
controllerNamestringOptional controller name. When null, the current controller is used.
Returns
- ActionResult<TOut>
- 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 output ActionResult.
Examples
POST endpoint creating a user with entity-to-DTO mapping:
[HttpPost]
public ActionResult<UserDto> CreateUser(CreateUserRequest request) =>
EmailAddress.TryCreate(request.Email)
.Combine(FirstName.TryCreate(request.FirstName))
.Bind((email, name) => _userService.CreateUser(email, name))
.ToCreatedAtActionResult(this,
actionName: nameof(GetUser),
routeValues: user => new { id = user.Id },
map: user => new UserDto(user));
// Success: 201 Created with Location: /api/users/{id} and UserDto body
// Validation error: 400 Bad Request with Problem Details
Remarks
Use this overload when the domain entity needs to be transformed (e.g., to a DTO) before including it in the response body.