Endpoint Mapping
This page explains how to map asynchronous endpoints using AsyncEndpoints extension methods and how HTTP requests are processed through the async pipeline.
Overview
AsyncEndpoints provides several extension methods to map HTTP endpoints that process requests asynchronously. These methods separate the immediate response from the background processing, allowing your application to remain responsive during long-running operations.
Supported HTTP Methods
The library supports multiple HTTP methods through dedicated mapping methods:
POST Endpoints
With Request Body
app.MapAsyncPost<TRequest>(string jobName, string pattern, Func<HttpContext, TRequest, CancellationToken, Task<IResult?>?>? handler = null)
Example:
app.MapAsyncPost<DataRequest>("ProcessData", "/api/process-data");
Without Request Body
app.MapAsyncPost(string jobName, string pattern, Func<HttpContext, NoBodyRequest, CancellationToken, Task<IResult?>?>? handler = null)
Example:
app.MapAsyncPost("GenerateReport", "/api/generate-report");
PUT Endpoints
With Request Body
app.MapAsyncPut<TRequest>(string jobName, string pattern, Func<HttpContext, TRequest, CancellationToken, Task<IResult?>?>? handler = null)
Example:
app.MapAsyncPut<UpdateRequest>("UpdateData", "/api/update-data");
Without Request Body
app.MapAsyncPut(string jobName, string pattern, Func<HttpContext, NoBodyRequest, CancellationToken, Task<IResult?>?>? handler = null)
PATCH Endpoints
With Request Body
app.MapAsyncPatch<TRequest>(string jobName, string pattern, Func<HttpContext, TRequest, CancellationToken, Task<IResult?>?>? handler = null)
Without Request Body
app.MapAsyncPatch(string jobName, string pattern, Func<HttpContext, NoBodyRequest, CancellationToken, Task<IResult?>?>? handler = null)
DELETE Endpoints
With Request Body
app.MapAsyncDelete<TRequest>(string jobName, string pattern, Func<HttpContext, TRequest, CancellationToken, Task<IResult?>?>? handler = null)
Without Request Body
app.MapAsyncDelete(string jobName, string pattern, Func<HttpContext, NoBodyRequest, CancellationToken, Task<IResult?>?>? handler = null)
Job Status Endpoint
app.MapAsyncGetJobDetails(string pattern = "/jobs/{jobId:guid}")
This endpoint allows clients to check job status using the job ID returned from the initial request.
Parameter Mapping
Route Parameters
Route parameters are automatically captured and preserved in the job context:
app.MapAsyncPost<DataRequest>("ProcessData", "/api/process-data/{id}/action");
// In your handler, access route parameters:
public class ProcessDataHandler(ILogger<ProcessDataHandler> logger)
: IAsyncEndpointRequestHandler<DataRequest, ProcessResult>
{
public async Task<MethodResult<ProcessResult>> HandleAsync(AsyncContext<DataRequest> context, CancellationToken token)
{
var routeParams = context.RouteParams;
var id = routeParams["id"]; // Access the captured route parameter
// Process with the route parameter value
return MethodResult<ProcessResult>.Success(new ProcessResult());
}
}
Query Parameters
Query parameters are also preserved in the job context:
// Request: /api/process-data?format=json&priority=high
public async Task<MethodResult<ProcessResult>> HandleAsync(AsyncContext<DataRequest> context, CancellationToken token)
{
var queryParams = context.QueryParams;
var format = queryParams.FirstOrDefault(q => q.Key == "format").Value?.FirstOrDefault();
var priority = queryParams.FirstOrDefault(q => q.Key == "priority").Value?.FirstOrDefault();
// Process with query parameter values
return MethodResult<ProcessResult>.Success(new ProcessResult());
}
HTTP Headers
HTTP headers are preserved and accessible in the handler:
public async Task<MethodResult<ProcessResult>> HandleAsync(AsyncContext<DataRequest> context, CancellationToken token)
{
var headers = context.Headers;
var authorization = headers.GetValueOrDefault("Authorization", new List<string?>());
// Process with header information
return MethodResult<ProcessResult>.Success(new ProcessResult());
}
Custom Validation Middleware
You can add custom validation before jobs are queued by providing a custom handler function:
app.MapAsyncPost<DataRequest>("ProcessData", "/api/process-data",
async (HttpContext context, DataRequest request, CancellationToken token) =>
{
// Validate request before queuing
if (string.IsNullOrWhiteSpace(request.Data))
{
return Results.BadRequest("Data field is required");
}
if (request.ProcessingPriority < 1 || request.ProcessingPriority > 5)
{
return Results.BadRequest("Priority must be between 1 and 5");
}
// Return null to continue queuing the job
return null;
});
In this pattern:
- Return a non-null
IResultto terminate the request (validation failure) - Return
nullto continue with job queuing - The validation occurs synchronously before the job is submitted to the background queue
Example with Multiple Endpoints
// POST endpoint with request body
app.MapAsyncPost<CreateRequest>("CreateResource", "/api/resources");
// PUT endpoint with request body
app.MapAsyncPut<UpdateRequest>("UpdateResource", "/api/resources/{id}");
// PATCH endpoint without request body
app.MapAsyncPatch("RefreshResource", "/api/resources/{id}/refresh");
// DELETE endpoint with request body
app.MapAsyncDelete<DeleteRequest>("DeleteResource", "/api/resources/{id}");
// Job status endpoint
app.MapAsyncGetJobDetails("/jobs/{jobId:guid}");
Error Handling During Mapping
The mapping methods include built-in error handling:
- Serialization Errors: Failed request serialization returns appropriate HTTP error responses
- Handler Registration: Invalid job names result in clear error messages
- Route Conflicts: Standard ASP.NET Core routing behavior applies
Best Practices
Use Descriptive Job Names
Choose clear, unique job names that identify the specific operation:
// Good
app.MapAsyncPost<ProcessPaymentRequest>("ProcessPayment", "/api/payments/process");
app.MapAsyncPost<GenerateReportRequest>("GenerateMonthlyReport", "/api/reports/monthly");
// Avoid
app.MapAsyncPost<Request>("Job1", "/api/jobs/1");
app.MapAsyncPost<Request>("HandleRequest", "/api/process");
Validate Critical Parameters Early
Use validation middleware to catch common errors before queuing:
app.MapAsyncPost<DataRequest>("ProcessData", "/api/process-data",
async (HttpContext context, DataRequest request, CancellationToken token) =>
{
// Validate critical parameters that would cause early failure
if (string.IsNullOrWhiteSpace(request.Data))
{
return Results.BadRequest("Data field is required");
}
// Additional validation as needed
return null;
});
Consider Security Implications
Use appropriate authentication and authorization middleware:
app.MapAsyncPost<DataRequest>("ProcessData", "/api/process-data")
.RequireAuthorization("AsyncDataProcessor"); // Apply authorization policy
The endpoint mapping functionality provides a clean, intuitive API for creating async endpoints while preserving the full HTTP context for background processing.