如何使用我的自定义异常中间件捕获 ASP.NET Core (3.1) URL 解析错误?
How can I catch ASP.NET Core (3.1) URL parsing error with my custom exception middleware?
我在 ASP.NET Core 3.1 (WebAPI) 项目中添加了一个自定义异常中间件。中间件非常简单,它捕获特定类型的异常并将适当的消息传递给消费者,同时它为其他所有内容发送通用错误消息。
这里是中间件class:
/// <summary>
/// Middleware which catches controller exceptions and extracts exceptions which are specifically intended for the
/// client and sends them as a regular response (and not 500 server error).
/// </summary>
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (MDEException ex) // we only care about this particular exception
{
// Send exception message as plain message
_logger.Log(LogLevel.Error, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(ex.Message);
}
catch (Exception ex)
{
// Send generic error as plain message
_logger.Log(LogLevel.Error, ex, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
}
}
}
除我向应用程序发送无效的 URL 外,一切正常。在这种情况下,我的中间件甚至都没有被触及。例如,如果我使用这个 URL: api/vm/ziel-standort/stellplatzId=2
我会收到类似这样的错误:
{"errors":{"stellplatzId":["The value 'stellplatzId=2' is not valid."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|d7456c7b-469208468c4ac571."}
这是我的配置方法的样子:
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app">Application builder (injected by framework)</param>
/// <param name="log">Logger (injected by framework)</param>
/// <param name="actionProvider">Action provider for listing routes (injected by framework)</param>
public void Configure(IApplicationBuilder app, ILogger<Startup> log, Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider actionProvider)
{
log.Log(LogLevel.Information, "Configuring application ...");
log.Log(LogLevel.Debug, "Using CorsPolicy");
app.UseCors("CorsPolicy");
log.Log(LogLevel.Debug, "Using Exception middleware (MDEException message pass-through to client)");
app.UseMiddleware<ExceptionMiddleware>();
log.Log(LogLevel.Debug, "Enabling routing");
app.UseRouting();
log.Log(LogLevel.Debug, "Enabling authentication and authorization");
app.UseAuthentication();
app.UseAuthorization();
log.Log(LogLevel.Debug, "Setting up routing for controllers");
app.UseEndpoints(opt =>
{
opt.MapControllers();
});
// .....
}
如何让我的中间件捕捉到这个错误?
您收到模型验证错误,而非异常。要覆盖它,您可以在 Startup
Class 的 ConfigureServices
方法中使用以下代码:
services.AddControllers()
.ConfigureApiBehaviorOptions(o =>
{
o.InvalidModelStateResponseFactory = context =>
{
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
var ex = new Exception(context.ModelState.Values.First().Errors.First().ErrorMessage);
_logger.Log(LogLevel.Error, ex, ex.Message);
context.HttpContext.Response.ContentType = "text/plain";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.HttpContext.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
return new EmptyResult();
};
});
而且,这是基于 Microsoft 文档的异常处理方式:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var ex = context.Features.Get<IExceptionHandlerPathFeature>()?.Error;
if (ex is MDEException) // we only care about this particular exception
{
// Send exception message as plain message
_logger.Log(LogLevel.Error, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(ex.Message);
}
else
{
// Send generic error as plain message
_logger.Log(LogLevel.Error, ex, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
}
});
});
我在 ASP.NET Core 3.1 (WebAPI) 项目中添加了一个自定义异常中间件。中间件非常简单,它捕获特定类型的异常并将适当的消息传递给消费者,同时它为其他所有内容发送通用错误消息。
这里是中间件class:
/// <summary>
/// Middleware which catches controller exceptions and extracts exceptions which are specifically intended for the
/// client and sends them as a regular response (and not 500 server error).
/// </summary>
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (MDEException ex) // we only care about this particular exception
{
// Send exception message as plain message
_logger.Log(LogLevel.Error, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(ex.Message);
}
catch (Exception ex)
{
// Send generic error as plain message
_logger.Log(LogLevel.Error, ex, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
}
}
}
除我向应用程序发送无效的 URL 外,一切正常。在这种情况下,我的中间件甚至都没有被触及。例如,如果我使用这个 URL: api/vm/ziel-standort/stellplatzId=2
我会收到类似这样的错误:
{"errors":{"stellplatzId":["The value 'stellplatzId=2' is not valid."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|d7456c7b-469208468c4ac571."}
这是我的配置方法的样子:
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app">Application builder (injected by framework)</param>
/// <param name="log">Logger (injected by framework)</param>
/// <param name="actionProvider">Action provider for listing routes (injected by framework)</param>
public void Configure(IApplicationBuilder app, ILogger<Startup> log, Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider actionProvider)
{
log.Log(LogLevel.Information, "Configuring application ...");
log.Log(LogLevel.Debug, "Using CorsPolicy");
app.UseCors("CorsPolicy");
log.Log(LogLevel.Debug, "Using Exception middleware (MDEException message pass-through to client)");
app.UseMiddleware<ExceptionMiddleware>();
log.Log(LogLevel.Debug, "Enabling routing");
app.UseRouting();
log.Log(LogLevel.Debug, "Enabling authentication and authorization");
app.UseAuthentication();
app.UseAuthorization();
log.Log(LogLevel.Debug, "Setting up routing for controllers");
app.UseEndpoints(opt =>
{
opt.MapControllers();
});
// .....
}
如何让我的中间件捕捉到这个错误?
您收到模型验证错误,而非异常。要覆盖它,您可以在 Startup
Class 的 ConfigureServices
方法中使用以下代码:
services.AddControllers()
.ConfigureApiBehaviorOptions(o =>
{
o.InvalidModelStateResponseFactory = context =>
{
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
var ex = new Exception(context.ModelState.Values.First().Errors.First().ErrorMessage);
_logger.Log(LogLevel.Error, ex, ex.Message);
context.HttpContext.Response.ContentType = "text/plain";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.HttpContext.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
return new EmptyResult();
};
});
而且,这是基于 Microsoft 文档的异常处理方式:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var ex = context.Features.Get<IExceptionHandlerPathFeature>()?.Error;
if (ex is MDEException) // we only care about this particular exception
{
// Send exception message as plain message
_logger.Log(LogLevel.Error, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(ex.Message);
}
else
{
// Send generic error as plain message
_logger.Log(LogLevel.Error, ex, ex.Message);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Es trat ein unvorhergesehenes Problem auf - bitte kontaktieren Sie Ihren Administrator!");
}
});
});