Table of Contents

Class ActionResultExtensions

Namespace
FunctionalDdd
Assembly
FunctionalDdd.Asp.dll

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 7807) 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

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

error Error

The domain error to convert.

controllerBase ControllerBase

The controller context used to create the ActionResult.

Returns

ActionResult<TValue>

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

Domain Error TypeHTTP Status Code
ValidationError400 Bad Request with validation problem details and ModelState
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

Type Parameters

TValue

The 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

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

result Result<TValue>

The result object to convert.

controllerBase ControllerBase

The 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

TValue

The 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 created resource:

[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))
        .ToActionResult(this);

// 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:

[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<TValue>(Result<TValue>, ControllerBase, long, long, long)

Converts a Result<TValue> to an ActionResult<TValue> with support for partial content responses (HTTP 206).

public static ActionResult<TValue> ToActionResult<TValue>(this Result<TValue> result, ControllerBase controllerBase, long from, long to, long length)

Parameters

result Result<TValue>

The result object to convert.

controllerBase ControllerBase

The controller context used to create the ActionResult.

from long

The starting index of the range (inclusive, 0-based).

to long

The ending index of the range (inclusive, 0-based).

length long

The total number of items available.

Returns

ActionResult<TValue>
  • 206 Partial Content with Content-Range header if the range is a subset of the total
  • 200 OK if the range represents the complete set
  • Appropriate error status code if result is failure

Type Parameters

TValue

The type of the value contained in the result.

Examples

Paginated list endpoint:

[HttpGet]
public ActionResult<IEnumerable<UserDto>> GetUsers(
    [FromQuery] int page = 0,
    [FromQuery] int pageSize = 25)
{
    var from = page * pageSize;
    var to = from + pageSize - 1;

    return _userService
        .GetUsersAsync(from, pageSize)
        .Map(result => (
            Users: result.Items,
            TotalCount: result.TotalCount
        ))
        .Map(x => x.Users)
        .ToActionResult(this, from, to, totalCount);
}

// Response for page 0 (25 items out of 100):
// Status: 206 Partial Content
// Content-Range: items 0-24/100

// Response for single page (all items):
// Status: 200 OK
// No Content-Range header

Remarks

This method is useful for implementing pagination or range requests where clients request a subset of a larger collection. The Content-Range header indicates:

  • Which items are included in this response
  • The total number of items available

The Content-Range header format is: items {from}-{to}/{length} Example: Content-Range: items 0-24/100

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

result Result<TIn>

The result object to convert.

controllerBase ControllerBase

The controller context used to create the ActionResult.

funcRange Func<TIn, ContentRangeHeaderValue>

Function that extracts ContentRangeHeaderValue from the input value.

funcValue Func<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

TIn

The type of the value contained in the input result.

TOut

The 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