Table of Contents

Class HttpResultExtensionsAsync

Namespace
FunctionalDdd
Assembly
FunctionalDdd.Asp.dll

Provides asynchronous extension methods to convert Task/ValueTask-wrapped Result types to ASP.NET Core Minimal API IResult responses. These methods enable clean async patterns in Minimal API endpoints while maintaining Railway Oriented Programming benefits.

public static class HttpResultExtensionsAsync
Inheritance
HttpResultExtensionsAsync
Inherited Members

Remarks

These extensions are async variants of HttpResultExtensions, designed for use with async service methods in Minimal APIs. They support both Task<TResult> and ValueTask<TResult> for maximum flexibility and performance.

Key benefits:

  • Clean async Minimal API endpoint code without manual awaiting
  • Automatic HTTP status code selection based on error type
  • Support for both Task and ValueTask for performance optimization
  • Consistent error handling across async operations
  • Seamless integration with async Railway Oriented Programming chains
  • Reduced boilerplate in endpoint definitions

Usage pattern in async Minimal APIs (with CancellationToken):

app.MapGet("/users/{id}", async (string id, IUserService userService, CancellationToken ct) =>
    await UserId.TryCreate(id)
        .BindAsync(userId => userService.GetUserAsync(userId, ct))
        .MapAsync(user => new UserDto(user))
        .ToHttpResultAsync()
);

Best Practice: Always accept a CancellationToken parameter in async endpoints and pass it through to all async service calls. ASP.NET Core automatically provides request cancellation through this token, enabling proper timeout handling and graceful shutdown.

Methods

ToHttpResultAsync<TValue>(Task<Result<TValue>>)

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

public static Task<IResult> ToHttpResultAsync<TValue>(this Task<Result<TValue>> resultTask)

Parameters

resultTask Task<Result<TValue>>

The task containing the result object to convert.

Returns

Task<IResult>

A task that represents the asynchronous operation, containing:

  • 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

Async GET endpoint with database query and CancellationToken:

app.MapGet("/users/{id}", async (Guid id, IUserRepository repository, CancellationToken ct) =>
    await UserId.TryCreate(id)
        .BindAsync(userId => repository.GetByIdAsync(userId, ct))
        .MapAsync(user => new UserDto(user))
        .ToHttpResultAsync());

// Success: 200 OK with UserDto
// Not found: 404 Not Found with Problem Details
// Validation error: 400 Bad Request

Async POST endpoint with multiple operations and CancellationToken:

app.MapPost("/orders", async (
    CreateOrderRequest request,
    IOrderService orderService,
    IEventBus eventBus,
    CancellationToken ct) =>
    await CustomerId.TryCreate(request.CustomerId)
        .BindAsync(customerId => orderService.GetCustomerAsync(customerId, ct))
        .BindAsync(customer => orderService.CreateOrderAsync(customer, request.Items, ct))
        .TapAsync(order => eventBus.PublishAsync(new OrderCreatedEvent(order.Id), ct))
        .MapAsync(order => new OrderDto(order))
        .ToHttpResultAsync());

// Success: 200 OK with OrderDto
// Validation error: 400 Bad Request
// Customer not found: 404 Not Found
// Domain error: 422 Unprocessable Entity

Async DELETE endpoint returning Unit with CancellationToken:

app.MapDelete("/users/{id}", async (Guid id, IUserRepository repository, CancellationToken ct) =>
    await UserId.TryCreate(id)
        .BindAsync(userId => repository.DeleteAsync(userId, ct))
        .ToHttpResultAsync());

// Success: 204 No Content (automatic for Unit)
// Not found: 404 Not Found

Complex async workflow with validation and side effects:

app.MapPost("/payments", async (
    ProcessPaymentRequest request,
    IPaymentService paymentService,
    INotificationService notificationService,
    CancellationToken ct) =>
    await Amount.TryCreate(request.Amount)
        .Combine(CardNumber.TryCreate(request.CardNumber))
        .BindAsync((amount, card) => 
            paymentService.ProcessPaymentAsync(amount, card, ct))
        .TapAsync(payment => 
            notificationService.SendReceiptAsync(payment, ct))
        .MapAsync(payment => new PaymentDto(payment))
        .ToHttpResultAsync());

// Returns appropriate status codes for validation errors,
// payment failures, or successful processing

Remarks

This is the primary async method for converting domain results to HTTP responses in Minimal APIs. It awaits the result task and delegates to ToHttpResult<TValue>(Result<TValue>).

For performance-critical scenarios where the operation frequently completes synchronously, consider using the ValueTask overload instead.

CancellationToken Best Practice: The endpoint should accept a CancellationToken parameter (ASP.NET Core automatically provides request cancellation) and pass it to all async service calls in the chain.

ToHttpResultAsync<TValue>(ValueTask<Result<TValue>>)

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

public static ValueTask<IResult> ToHttpResultAsync<TValue>(this ValueTask<Result<TValue>> resultTask)

Parameters

resultTask ValueTask<Result<TValue>>

The ValueTask containing the result object to convert.

Returns

ValueTask<IResult>

A ValueTask that represents the asynchronous operation, containing:

  • 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 based on error type if result is failure

Type Parameters

TValue

The type of the value contained in the result.

Examples

Using with cached data that might complete synchronously:

app.MapGet("/users/{id}", async (Guid id, IUserCache cache, CancellationToken ct) =>
    await UserId.TryCreate(id)
        .BindAsync(userId => cache.GetUserAsync(userId, ct)) // Returns ValueTask
        .MapAsync(user => new UserDto(user))
        .ToHttpResultAsync());

// Optimized for frequent cache hits that complete synchronously

High-performance endpoint with ValueTask throughout:

app.MapGet("/metrics/{id}", async (string id, IMetricsService service, CancellationToken ct) =>
    await MetricId.TryCreate(id)
        .BindAsync(metricId => service.GetMetricAsync(metricId, ct)) // ValueTask
        .MapAsync(metric => new MetricDto(metric))
        .ToHttpResultAsync());

// Reduced allocations for high-throughput scenarios

Remarks

This overload is optimized for scenarios where the async operation frequently completes synchronously (e.g., cached results, in-memory operations, ValueTask-returning service methods). ValueTask can reduce allocations in these cases.

Use this when your service methods return ValueTask for performance optimization.