And*_*rei 230 c# exception asp.net-core
在使用常规ASP.NET Web API多年后,我开始使用ASP.NET Core作为我的新REST API项目.我没有看到在ASP.NET Core Web API中处理异常的好方法.我试图实现异常处理过滤器/属性:
public class ErrorHandlingFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
HandleExceptionAsync(context);
context.ExceptionHandled = true;
}
private static void HandleExceptionAsync(ExceptionContext context)
{
var exception = context.Exception;
if (exception is MyNotFoundException)
SetExceptionResult(context, exception, HttpStatusCode.NotFound);
else if (exception is MyUnauthorizedException)
SetExceptionResult(context, exception, HttpStatusCode.Unauthorized);
else if (exception is MyException)
SetExceptionResult(context, exception, HttpStatusCode.BadRequest);
else
SetExceptionResult(context, exception, HttpStatusCode.InternalServerError);
}
private static void SetExceptionResult(
ExceptionContext context,
Exception exception,
HttpStatusCode code)
{
context.Result = new JsonResult(new ApiResponse(exception))
{
StatusCode = (int)code
};
}
}
Run Code Online (Sandbox Code Playgroud)
这是我的启动过滤器注册:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilter());
options.Filters.Add(new ErrorHandlingFilter());
});
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是,当我AuthorizationFilter
的异常发生时,它没有被处理ErrorHandlingFilter
.我希望它能够像旧的ASP.NET Web API一样被捕获.
那么如何捕获所有应用程序异常以及Action Filters的任何异常?
And*_*rei 463
经过多次使用不同异常处理方法的实验后,我最终使用了中间件.它最适合我的ASP.NET Core Web API应用程序.它处理应用程序异常以及来自过滤器的异常,我可以完全控制异常处理并创建响应json.这是我的异常处理中间件:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
if (ex is MyNotFoundException) code = HttpStatusCode.NotFound;
else if (ex is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
else if (ex is MyException) code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Run Code Online (Sandbox Code Playgroud)
在课堂上的MVC之前注册Startup
:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
Run Code Online (Sandbox Code Playgroud)
以下是异常响应的示例:
{ "error": "Authentication token is not valid." }
Run Code Online (Sandbox Code Playgroud)
您可以添加堆栈跟踪,异常类型名称,错误代码或任何您想要的内容.非常灵活.希望这对你来说是一个很好的起点!
Ily*_*dik 30
Latest Asp.Net Core
(at least from 2.2, probably earlier) has a built-in middleware that makes it a bit easier compared to the implementation in the accepted answer:
app.UseExceptionHandler(a => a.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}));
Run Code Online (Sandbox Code Playgroud)
It should do pretty much the same, just a bit less code to write. Remember to add it before UseMvc
as order is important.
Ash*_*Lee 27
您最好的选择是使用中间件来实现您正在寻找的日志记录.您希望将异常日志记录放在一个中间件中,然后处理在不同中间件中向用户显示的错误页面.这允许逻辑分离并遵循微软已经用2个中间件组件设计的设计.这是微软文档的一个很好的链接:ASP.Net Core中的错误处理
对于您的具体示例,您可能希望使用StatusCodePage中间件中的一个扩展,或者像这样使用自己的扩展.
您可以在此处找到用于记录异常的示例:ExceptionHandlerMiddleware.cs
public void Configure(IApplicationBuilder app)
{
// app.UseErrorPage(ErrorPageOptions.ShowAll);
// app.UseStatusCodePages();
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}");
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}");
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
app.UseStatusCodePagesWithReExecute("/Errors/{0}"); // I use this version
// Exception handling logging below
app.UseExceptionHandler();
}
Run Code Online (Sandbox Code Playgroud)
如果你不喜欢那个特定的实现,那么你也可以使用ELM Middleware,这里有一些例子:Elm异常中间件
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePagesWithReExecute("/Errors/{0}");
// Exception handling logging below
app.UseElmCapture();
app.UseElmPage();
}
Run Code Online (Sandbox Code Playgroud)
如果这不能满足您的需求,您可以通过查看ExceptionHandlerMiddleware和ElmMiddleware的实现来总是推出自己的中间件组件,以掌握构建自己的概念.
在StatusCodePages中间件下面添加异常处理中间件,但最重要的是在其他所有中间件组件上添加.这样你的Exception中间件将捕获异常,记录它,然后允许请求进入StatusCodePage中间件,它将向用户显示友好的错误页面.
Iha*_*ush 18
要为每个异常类型配置异常处理行为,您可以使用NuGet包中的中间件:
ASP.NET Core 2.0
ASP.NET Core 2.1+
.代码示例:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddExceptionHandlingPolicies(options =>
{
options.For<InitializationException>().Rethrow();
options.For<SomeTransientException>().Retry(ro => ro.MaxRetryCount = 2).NextPolicy();
options.For<SomeBadRequestException>()
.Response(e => 400)
.Headers((h, e) => h["X-MyCustomHeader"] = e.Message)
.WithBody((req,sw, exception) =>
{
byte[] array = Encoding.UTF8.GetBytes(exception.ToString());
return sw.WriteAsync(array, 0, array.Length);
})
.NextPolicy();
// Ensure that all exception types are handled by adding handler for generic exception at the end.
options.For<Exception>()
.Log(lo =>
{
lo.EventIdFactory = (c, e) => new EventId(123, "UnhandlerException");
lo.Category = (context, exception) => "MyCategory";
})
.Response(null, ResponseAlreadyStartedBehaviour.GoToNextHandler)
.ClearCacheHeaders()
.WithObjectResult((r, e) => new { msg = e.Message, path = r.Path })
.Handled();
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandlingPolicies();
app.UseMvc();
}
Run Code Online (Sandbox Code Playgroud)
Arj*_*jun 17
很好的答案对我有很大的帮助,但我想在我的中间件中传递HttpStatusCode来管理运行时的错误状态代码.
根据这个链接我有一些想法做同样的事情.所以我将Andrei Answer与此合并.所以我的最终代码如下:
1.基类
public class ErrorDetails
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Run Code Online (Sandbox Code Playgroud)
2.自定义异常类类型
public class HttpStatusCodeException : Exception
{
public HttpStatusCode StatusCode { get; set; }
public string ContentType { get; set; } = @"text/plain";
public HttpStatusCodeException(HttpStatusCode statusCode)
{
this.StatusCode = statusCode;
}
public HttpStatusCodeException(HttpStatusCode statusCode, string message) : base(message)
{
this.StatusCode = statusCode;
}
public HttpStatusCodeException(HttpStatusCode statusCode, Exception inner) : this(statusCode, inner.ToString()) { }
public HttpStatusCodeException(HttpStatusCode statusCode, JObject errorObject) : this(statusCode, errorObject.ToString())
{
this.ContentType = @"application/json";
}
}
Run Code Online (Sandbox Code Playgroud)
3.自定义异常中间件
public class CustomExceptionMiddleware
{
private readonly RequestDelegate next;
public CustomExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (HttpStatusCodeException ex)
{
await HandleExceptionAsync(context, ex);
}
catch (Exception exceptionObj)
{
await HandleExceptionAsync(context, exceptionObj);
}
}
private Task HandleExceptionAsync(HttpContext context, HttpStatusCodeException exception)
{
string result = null;
context.Response.ContentType = "application/json";
if (exception is HttpStatusCodeException)
{
result = new ErrorDetails() { Message = exception.Message, StatusCode = (int)exception.StatusCode }.ToString();
context.Response.StatusCode = (int)exception.StatusCode;
}
else
{
result = new ErrorDetails() { Message = "Runtime Error", StatusCode = (int)HttpStatusCode.BadRequest }.ToString();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
return context.Response.WriteAsync(result);
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
string result = new ErrorDetails() { Message = exception.Message, StatusCode = (int)HttpStatusCode.InternalServerError }.ToString();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return context.Response.WriteAsync(result);
}
}
Run Code Online (Sandbox Code Playgroud)
4.扩展方法
public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<CustomExceptionMiddleware>();
}
Run Code Online (Sandbox Code Playgroud)
5.在startup.cs中配置Method
app.ConfigureCustomExceptionMiddleware();
app.UseMvc();
Run Code Online (Sandbox Code Playgroud)
现在我的帐户控制器中的登录方法:
try
{
IRepository<UserMaster> obj = new Repository<UserMaster>(_objHeaderCapture, Constants.Tables.UserMaster);
var Result = obj.Get().AsQueryable().Where(sb => sb.EmailId.ToLower() == objData.UserName.ToLower() && sb.Password == objData.Password.ToEncrypt() && sb.Status == (int)StatusType.Active).FirstOrDefault();
if (Result != null)//User Found
return Result;
else// Not Found
throw new HttpStatusCodeException(HttpStatusCode.NotFound, "Please check username or password");
}
catch (Exception ex)
{
throw ex;
}
Run Code Online (Sandbox Code Playgroud)
上面你可以看到我是否找不到用户然后提出HttpStatusCodeException,其中我已经通过HttpStatusCode.NotFound状态和自定义消息
在中间件
catch(HttpStatusCodeException ex)
将被调用阻止将控制传递给
private Task HandleExceptionAsync(HttpContext context,HttpStatusCodeException exception)方法
.
但如果我之前遇到运行时错误怎么办?为此,我使用了try catch块抛出异常,并将在catch(Exception exceptionObj)块中捕获并将控制传递给
任务HandleExceptionAsync(HttpContext上下文,异常异常)
方法.
我使用了一个ErrorDetails类来实现一致性.
Cou*_*ero 16
首先,感谢Andrei,因为我的解决方案基于他的例子.
我包括我的,因为它是一个更完整的样本,可能会节省读者一些时间.
Andrei的方法的局限在于不处理日志记录,捕获可能有用的请求变量和内容协商(无论客户端请求什么,它总是返回JSON - XML /纯文本等).
我的方法是使用ObjectResult,它允许我们使用烘焙到MVC的功能.
此代码还可以防止缓存响应.
错误响应的修饰方式可以由XML序列化程序序列化.
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate next;
private readonly IActionResultExecutor<ObjectResult> executor;
private readonly ILogger logger;
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
public ExceptionHandlerMiddleware(RequestDelegate next, IActionResultExecutor<ObjectResult> executor, ILoggerFactory loggerFactory)
{
this.next = next;
this.executor = executor;
logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, $"An unhandled exception has occurred while executing the request. Url: {context.Request.GetDisplayUrl()}. Request Data: " + GetRequestData(context));
if (context.Response.HasStarted)
{
throw;
}
var routeData = context.GetRouteData() ?? new RouteData();
ClearCacheHeaders(context.Response);
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
var result = new ObjectResult(new ErrorResponse("Error processing request. Server error."))
{
StatusCode = (int) HttpStatusCode.InternalServerError,
};
await executor.ExecuteAsync(actionContext, result);
}
}
private static string GetRequestData(HttpContext context)
{
var sb = new StringBuilder();
if (context.Request.HasFormContentType && context.Request.Form.Any())
{
sb.Append("Form variables:");
foreach (var x in context.Request.Form)
{
sb.AppendFormat("Key={0}, Value={1}<br/>", x.Key, x.Value);
}
}
sb.AppendLine("Method: " + context.Request.Method);
return sb.ToString();
}
private static void ClearCacheHeaders(HttpResponse response)
{
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
}
[DataContract(Name= "ErrorResponse")]
public class ErrorResponse
{
[DataMember(Name = "Message")]
public string Message { get; set; }
public ErrorResponse(string message)
{
Message = message;
}
}
}
Run Code Online (Sandbox Code Playgroud)
首先,配置ASP.NET Core 2 Startup
以重新执行错误页面,以查找来自Web服务器的任何错误以及任何未处理的异常.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) {
// Debug config here...
} else {
app.UseStatusCodePagesWithReExecute("/Error");
app.UseExceptionHandler("/Error");
}
// More config...
}
Run Code Online (Sandbox Code Playgroud)
接下来,定义一个异常类型,使您可以使用HTTP状态代码抛出错误.
public class HttpException : Exception
{
public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; }
public HttpStatusCode StatusCode { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)
最后,在错误页面的控制器中,根据错误原因以及最终用户是否直接看到响应来自定义响应.此代码假定所有API URL都以/api/
.
[AllowAnonymous]
public IActionResult Error()
{
// Gets the status code from the exception or web server.
var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ?
httpEx.StatusCode : (HttpStatusCode)Response.StatusCode;
// For API errors, responds with just the status code (no page).
if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal))
return StatusCode((int)statusCode);
// Creates a view model for a user-friendly error page.
string text = null;
switch (statusCode) {
case HttpStatusCode.NotFound: text = "Page not found."; break;
// Add more as desired.
}
return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText = text });
}
Run Code Online (Sandbox Code Playgroud)
ASP.NET Core将记录您要调试的错误详细信息,因此您可能希望向(可能不受信任的)请求者提供状态代码.如果您想显示更多信息,可以增强HttpException
提供它.对于API的错误,您可以通过更换放JSON编码错误信息在邮件正文中return StatusCode...
使用return Json...
.
以下是Microsoft 的官方指南,涵盖所有 .NET 版本的 WebAPI 和 MVC 案例。
对于 Web API,它建议重定向到专用控制器端点以返回ProblemDetails。由于它可能会导致OpenAPI 规范中不应直接调用的端点潜在暴露,因此我建议采用一个更简单的解决方案:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseExceptionHandler(a => a.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>().Error;
var problem = new ProblemDetails { Title = "Critical Error"};
if (error != null)
{
if (env.IsDevelopment())
{
problem.Title = error.Message;
problem.Detail = error.StackTrace;
}
else
problem.Detail = error.Message;
}
await context.Response.WriteAsJsonAsync(problem);
}));
...
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们利用标准中间件返回自定义详细信息(带有开发模式的堆栈跟踪)并避免创建“内部”端点。
PS 请注意,官方指南依赖IExceptionHandlerPathFeature
于 .NET v3 之前和此后(目前直到 v5) - IExceptionHandlerFeature
.
PSS 如果您从 Domain 层抛出异常以将其转换为 4xx 代码,我建议使用khellang 的 ProblemDetailsMiddleware或返回DomainResult,稍后可以将其转换为IActionResult
或IResult
。后一个选项可帮助您获得相同的结果,而不会产生异常开销。
归档时间: |
|
查看次数: |
155582 次 |
最近记录: |