.NET Core Web应用:如何创建Task包装器/中间件并复用异常处理逻辑?
Great question! Dealing with repetitive try-catch blocks and boilerplate logging across every action is such a common pain point in .NET Core apps. Let’s break down the cleanest approaches to solve this—including handling those tricky return type issues you mentioned.
For .NET Core Web APIs/MVC apps, an async action filter is the most targeted approach. It hooks directly into the action execution pipeline, lets you handle exceptions globally, and simplifies return type management by wrapping responses in a consistent format.
1. Create the Exception Handling Filter
Implement IAsyncActionFilter to intercept action execution, catch exceptions, log them, and standardize responses:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; public class ExceptionHandlingFilter : IAsyncActionFilter { private readonly ILogger<ExceptionHandlingFilter> _logger; public ExceptionHandlingFilter(ILogger<ExceptionHandlingFilter> logger) { _logger = logger; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { try { // Run the action and get its result var resultContext = await next(); // Optional: Wrap successful responses in a consistent DTO if (resultContext.Result is ObjectResult objectResult) { resultContext.Result = new ObjectResult(new ApiResponse<object> { Success = true, Data = objectResult.Value, Message = "Operation completed successfully" }); } } catch (Exception ex) { // Log the full exception details _logger.LogError(ex, "Unhandled exception in action {ActionName}", context.ActionDescriptor.DisplayName); // Return a standardized error response var errorResponse = new ApiResponse<object> { Success = false, Message = ex.Message, ErrorCode = "INTERNAL_SERVER_ERROR" }; context.Result = new ObjectResult(errorResponse) { StatusCode = StatusCodes.Status500InternalServerError }; } } } // Generic API response DTO to unify return types public class ApiResponse<T> { public bool Success { get; set; } public T Data { get; set; } public string Message { get; set; } public string ErrorCode { get; set; } }
2. Register the Filter Globally
Add the filter to your app's service configuration so it applies to all actions automatically:
// In Program.cs builder.Services.AddControllers(options => { // Add the filter to the global filter collection options.Filters.Add<ExceptionHandlingFilter>(); });
If you need a reusable wrapper for any async task (not just actions), create a generic extension method to handle exceptions and logging:
using Microsoft.Extensions.Logging; public static class TaskWrapperExtensions { private static readonly ILogger<TaskWrapperExtensions> _logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<TaskWrapperExtensions>(); // Wrapper for tasks with no return value public static async Task MyExceptionHandledAsyncTask(this Task task) { try { await task; } catch (Exception ex) { _logger.LogError(ex, "Async task failed"); // Optionally rethrow with a custom exception or handle silently throw new CustomOperationException("Task execution failed", ex); } } // Wrapper for tasks with a return type (handles any generic T) public static async Task<T> MyExceptionHandledAsyncTask<T>(this Task<T> task) { try { return await task; } catch (Exception ex) { _logger.LogError(ex, "Async task with return type {Type} failed", typeof(T).Name); // Return a default value or error-wrapped object based on your needs return default; } } }
Usage in an Action:
[HttpGet] public async Task<IActionResult> FetchUserData(int userId) { var userData = await _userService.GetUserByIdAsync(userId).MyExceptionHandledAsyncTask(); return Ok(new ApiResponse<UserDto> { Success = true, Data = userData }); }
For a global, pipeline-wide exception handler (less action-specific but good for broad error handling), use a custom middleware:
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ExceptionHandlingMiddleware> _logger; public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "Unhandled exception during request processing"); await HandleExceptionAsync(context, ex); } } private static async Task HandleExceptionAsync(HttpContext context, Exception ex) { context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; var errorResponse = new ApiResponse<object> { Success = false, Message = "An unexpected error occurred", ErrorCode = "INTERNAL_ERROR" }; await context.Response.WriteAsJsonAsync(errorResponse); } }
Register the Middleware:
// In Program.cs (place it early in the pipeline, before routing) app.UseMiddleware<ExceptionHandlingMiddleware>();
- Action Filter: Best for Web APIs/MVC—gives you access to action context (like parameters, action name) for precise logging, and standardizes return types effortlessly.
- Task Wrapper: Use when you need to reuse exception handling across non-action async methods (e.g., service layer code).
- Middleware: Ideal for catching unhandled exceptions that slip past action filters, but lacks action-specific context.
内容的提问来源于stack exchange,提问作者ferflores

