【问题标题】:ASP.NET Core Web API exception handlingASP.NET Core Web API 异常处理
【发布时间】:2016-12-02 11:23:15
【问题描述】:

在使用常规 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
        };
    }
}

这是我的启动过滤器注册:

services.AddMvc(options =>
{
    options.Filters.Add(new AuthorizationFilter());
    options.Filters.Add(new ErrorHandlingFilter());
});

我遇到的问题是,当我的AuthorizationFilter 发生异常时,ErrorHandlingFilter 没有处理它。我期待它会像使用旧的 ASP.NET Web API 一样被捕获。

那么我怎样才能捕获所有应用程序异常以及来自操作过滤器的任何异常?

【问题讨论】:

  • 你试过UseExceptionHandler中间件吗?
  • 作为一种选择,尝试在不抛出异常的情况下处理NotFound。像 github.com/AKlaus/DomainResult 这样的 NuGet 包在这里会有所帮助。
  • @AlexKlaus 代码中的噪音太大了。我永远不会向任何人推荐它。

标签: c# exception asp.net-core


【解决方案1】:

快速简便的解决方案。

只需在 ASP.NET 路由之前将这个中间件添加到您的中间件注册中。

app.UseExceptionHandler(c => c.Run(async context =>
{
    var exception = context.Features
        .Get<IExceptionHandlerPathFeature>()
        .Error;
    var response = new { error = exception.Message };
    await context.Response.WriteAsJsonAsync(response);
}));
app.UseMvc(); // or .UseRouting() or .UseEndpoints()

为日志记录和其他目的启用依赖注入。

第 1 步。 在您的启动中,注册您的异常处理路由:

// It should be one of your very first registrations
app.UseExceptionHandler("/error"); // Add this
app.UseEndpoints(endpoints => endpoints.MapControllers());

第 2 步。 创建将处理所有异常并产生错误响应的控制器:

[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorsController : ControllerBase
{
    [Route("error")]
    public MyErrorResponse Error()
    {
        var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
        var exception = context.Error; // Your exception
        var code = 500; // Internal Server Error by default

        if      (exception is MyNotFoundException) code = 404; // Not Found
        else if (exception is MyUnauthException)   code = 401; // Unauthorized
        else if (exception is MyException)         code = 400; // Bad Request

        Response.StatusCode = code; // You can use HttpStatusCode enum instead

        return new MyErrorResponse(exception); // Your error model
    }
}

一些重要的注意事项和观察:

  • 您可以将您的依赖项注入到 Controller 的构造函数中。
  • 需要[ApiExplorerSettings(IgnoreApi = true)]。否则,它可能会破坏你的 Swashbuckle 招摇
  • 同样,app.UseExceptionHandler("/error"); 必须是 Startup Configure(...) 方法中的顶级注册之一。将它放在方法的顶部可能是安全的。
  • app.UseExceptionHandler("/error") 和控制器 [Route("error")] 中的路径应该相同,以允许控制器处理从异常处理程序中间件重定向的异常。

这是微软官方文档的link


响应模型的想法。

实现您自己的响应模型和异常。 这个例子只是一个很好的起点。每个服务都需要以自己的方式处理异常。使用所描述的方法,您可以完全灵活地控制异常处理并从您的服务返回正确的响应。

错误响应模型的一个例子(只是给你一些想法):

public class MyErrorResponse
{
    public string Type { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }

    public MyErrorResponse(Exception ex)
    {
        Type = ex.GetType().Name;
        Message = ex.Message;
        StackTrace = ex.ToString();
    }
}

对于更简单的服务,您可能希望实现如下所示的 http 状态代码异常:

public class HttpStatusException : Exception
{
    public HttpStatusCode Status { get; private set; }

    public HttpStatusException(HttpStatusCode status, string msg) : base(msg)
    {
        Status = status;
    }
}

这可以通过这种方式从任何地方抛出:

throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");

那么您的处理代码可以简化为:

if (exception is HttpStatusException httpException)
{
    code = (int) httpException.Status;
}

HttpContext.Features.Get&lt;IExceptionHandlerFeature&gt;()WAT?

ASP.NET Core 开发人员接受了中间件的概念,其中不同方面的功能(例如 Auth、MVC、Swagger 等)被分离并在请求处理管道中按顺序执行。每个中间件都可以访问请求上下文,并且可以在需要时写入响应。如果以与 MVC 异常相同的方式处理来自非 MVC 中间件的错误很重要,那么从 MVC 中取出异常处理是有意义的,我发现这在现实世界的应用程序中很常见。所以因为内置的异常处理中间件不是 MVC 的一部分,MVC 本身对此一无所知,反之亦然,异常处理中间件并不真正知道异常来自哪里,当然它知道它发生在某个地方请求执行的管道。但两者可能都需要相互“连接”。因此,当任何地方都没有捕获到异常时,异常处理中间件会捕获它并重新运行管道以获取在其中注册的路由。如果您愿意,这就是您可以使用一致的content negotiation 或其他一些中间件将异常处理“传递”回 MVC 的方式。异常本身是从公共中间件上下文中提取的。看起来很有趣,但完成了工作:)。

【讨论】:

  • 今天我一直在努力让自定义中间件工作,它的工作方式基本相同(我正在使用它来管理请求的工作单元/事务)。我面临的问题是中间件中没有捕获“下一个”中引发的异常。可以想象,这是有问题的。我在做什么错/错过了什么?有什么指点或建议吗?
  • @brappleye3 - 我发现了问题所在。我只是在 Startup.cs 类的错误位置注册中间件。我将app.UseMiddleware&lt;ErrorHandlingMiddleware&gt;(); 移动到app.UseStaticFiles(); 之前。现在似乎正确捕获了异常。这让我相信app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); app.UseBrowserLink(); 做一些内部魔法中间件黑客,以使中间件排序正确。
  • 我同意自定义中间件可能非常有用,但会质疑在 NotFound、Unauthorized 和 BadRequest 情况下使用异常。为什么不简单地设置状态代码(使用 NotFound() 等),然后在您的自定义中间件中或通过 UseStatusCodePagesWithReExecute 处理它?请参阅devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api 了解更多信息
  • 这很糟糕,因为它总是序列化为 JSON,完全忽略了内容协商。
  • @Konrad 有效点。这就是为什么我说这个例子是你可以开始的地方,而不是最终结果。对于 99% 的 API,JSON 绰绰有余。如果您觉得这个答案不够好,请随时贡献。
【解决方案2】:

有一个内置的中间件:

ASP.NET Core 5版本:

app.UseExceptionHandler(a => a.Run(async context =>
{
    var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
    var exception = exceptionHandlerPathFeature.Error;
    
    await context.Response.WriteAsJsonAsync(new { error = exception.Message });
}));

旧版本(它们没有WriteAsJsonAsync 扩展名):

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);
}));

它应该做的几乎相同,只是要编写的代码少一点。

重要提示:记得在MapControllers\UseMvc(或.Net Core 3 中的UseRouting)之前添加它,因为顺序很重要。

【讨论】:

  • 它是否支持将 DI 作为处理程序的参数,还是必须在处理程序中使用服务定位器模式?
  • 请查看已接受的答案。通过这种方法,您可以使用 DI,并且可以完全控制 API 响应。
【解决方案3】:

您最好的选择是使用中间件来实现您正在寻找的日志记录。您希望将异常日志记录放在一个中间件中,然后在另一个中间件中处理向用户显示的错误页面。这允许逻辑分离并遵循 Microsoft 使用 2 个中间件组件制定的设计。这是微软文档的一个很好的链接:Error Handling in ASP.Net Core

对于您的具体示例,您可能希望使用 StatusCodePage middleware 中的扩展名之一,或者像 this 一样滚动您自己的扩展名。

您可以在此处找到记录异常的示例: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();
}

如果你不喜欢那个具体的实现,那么你也可以使用ELM Middleware,这里有一些例子:Elm Exception Middleware

public void Configure(IApplicationBuilder app)
{
    app.UseStatusCodePagesWithReExecute("/Errors/{0}");
    // Exception handling logging below
    app.UseElmCapture();
    app.UseElmPage();
}

如果这不能满足您的需求,您始终可以通过查看 ExceptionHandlerMiddleware 和 ElmMiddleware 的实现来推出自己的中间件组件,以掌握构建自己的概念。

在 StatusCodePages 中间件下方但在所有其他中间件组件上方添加异常处理中间件非常重要。这样,您的 Exception 中间件将捕获异常,记录它,然后允许请求继续到 StatusCodePage 中间件,该中间件将向用户显示友好的错误页面。

【讨论】:

  • 注意 Elm 不会持久化日志,建议使用 Serilog 或 NLog 提供序列化。见ELM logs disappears. Can we persist it to a file or DB?
  • 链接已断开。
  • @AshleyLee,我怀疑UseStatusCodePages 在 Web API 服务实现中是否有用。根本没有视图或 HTML,只有 JSON 响应...
【解决方案4】:

广为接受的答案对我帮助很大,但我想在我的中间件中传递 HttpStatusCode 以在运行时管理错误状态代码。

根据this link 我有一些想法来做同样的事情。因此,我将 Andrei Answer 与此合并。所以我的最终代码如下:


1.基类

public class ErrorDetails
{
    public int StatusCode { get; set; }
    public string Message { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}


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";
    }

}


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);
    }
}


4.扩展方法

public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app)
{
    app.UseMiddleware<CustomExceptionMiddleware>();
}

5. 在startup.cs中配置Method

app.ConfigureCustomExceptionMiddleware();
app.UseMvc();

现在我在帐户控制器中的登录方法:

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;
}

您可以在上面看到我是否还没有找到用户然后引发 HttpStatusCodeException 我已在其中传递了 HttpStatusCode.NotFound 状态和自定义消息
在中间件中

catch (HttpStatusCodeException ex)

blocked 将被调用,它将控制权传递给

私有任务 HandleExceptionAsync(HttpContext 上下文, HttpStatusCodeException 异常)方法


但是如果我之前遇到运行时错误怎么办?为此,我使用了抛出异常的 try catch 块,并将在 catch (Exception exceptionObj) 块中捕获并将控制权传递给

Task HandleExceptionAsync(HttpContext context, Exception异常)

方法。

为了统一,我使用了一个 ErrorDetails 类。

【讨论】:

  • 扩展方法放在哪里?不幸的是,在void Configure(IapplicationBuilder app) 中的startup.cs 中,我收到错误IApplicationBuilder does not contain a definition for ConfigureCustomExceptionMiddleware。我添加了引用,CustomExceptionMiddleware.cs 在哪里。
  • 您不想使用异常,因为它们会减慢您的 API。例外非常昂贵。
  • @Inaie,不能这么说......但似乎你从来没有遇到过任何异常需要处理......很棒的工作
  • 你确定要用“throw ex;”吗?而不是“扔;” ?
  • @LeszekP,我认为两者都可以,虽然我还没有测试过
【解决方案5】:

要为每个异常类型配置异常处理行为,您可以使用 NuGet 包中的中间件:

代码示例:

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();
}

【讨论】:

    【解决方案6】:

    首先,感谢 Andrei,因为我的解决方案基于他的示例。

    我将我的示例包括在内,因为它是一个更完整的示例,可能会为读者节省一些时间。

    Andrei 方法的局限性在于它不处理日志记录、捕获潜在有用的请求变量和内容协商(无论客户端请求什么 - XML / 纯文本等,它都将始终返回 JSON)。

    我的方法是使用 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;
            }
        }
    }
    

    【讨论】:

    【解决方案7】:

    首先,将 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...
    }
    

    接下来,定义一个异常类型,让您使用 HTTP 状态代码抛出错误。

    public class HttpException : Exception
    {
        public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; }
        public HttpStatusCode StatusCode { get; private set; }
    }
    

    最后,在错误页面的控制器中,根据错误原因以及最终用户是否会直接看到响应来自定义响应。此代码假定所有 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 });
    }
    

    ASP.NET Core 将记录错误详细信息以供您调试,因此您可能只想向(可能不受信任的)请求者提供状态代码。如果您想显示更多信息,您可以增强 HttpException 以提供它。对于 API 错误,您可以通过将 return StatusCode... 替换为 return Json... 将 JSON 编码的错误信息放入消息正文中。

    【讨论】:

      【解决方案8】:

      通过添加您自己的“异常处理中间件”,难以重用一些好的built-in logic of Exception Handler,例如在发生错误时向客户端发送“符合 RFC 7807 的有效负载”。

      我所做的是在 Startup.cs 类之外扩展 built-in Exception handler 以处理自定义异常或覆盖现有异常的行为。例如,一个 ArgumentException 并转换为 BadRequest 而不改变其他异常的默认行为:

      Startup.cs上添加:

      app.UseExceptionHandler("/error");
      

      并用类似这样的方式扩展ErrorController.cs

      using System;
      using Microsoft.AspNetCore.Diagnostics;
      using Microsoft.AspNetCore.Hosting;
      using Microsoft.AspNetCore.Mvc;
      using Microsoft.Extensions.Hosting;
      
      namespace Api.Controllers
      {
          [ApiController]
          [ApiExplorerSettings(IgnoreApi = true)]
          [AllowAnonymous]
          public class ErrorController : ControllerBase
          {
              [Route("/error")]
              public IActionResult Error(
                  [FromServices] IWebHostEnvironment webHostEnvironment)
              {
                  var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
                  var exceptionType = context.Error.GetType();
                  
                  if (exceptionType == typeof(ArgumentException)
                      || exceptionType == typeof(ArgumentNullException)
                      || exceptionType == typeof(ArgumentOutOfRangeException))
                  {
                      if (webHostEnvironment.IsDevelopment())
                      {
                          return ValidationProblem(
                              context.Error.StackTrace,
                              title: context.Error.Message);
                      }
      
                      return ValidationProblem(context.Error.Message);
                  }
      
                  if (exceptionType == typeof(NotFoundException))
                  {
                      return NotFound(context.Error.Message);
                  }
      
                  if (webHostEnvironment.IsDevelopment())
                  {
                      return Problem(
                          context.Error.StackTrace,
                          title: context.Error.Message
                          );
                  }
                  
                  return Problem();
              }
          }
      }
      

      注意:

      1. NotFoundException 是一个自定义异常,您需要做的就是 throw new NotFoundException(null);throw new ArgumentException("Invalid argument.");
      2. You should not serve sensitive error information to clients. Serving errors is a security risk

      【讨论】:

      • 我这样做是为了返回与 netcore 相同的结构: var result = JsonSerializer.Serialize(new { errorCode = error.ErrorCode, errorDescription = error.ErrorDescription, });但是,它存在一些问题,例如TraceId
      • @IlyaChernomordik 我猜你要返回 result 变量?正如您在我的代码中看到的,我返回了一个内置的BaseController.ValidationProblem 或 BaseController.Problem。 HTTP 400 响应 ``` { "type": "tools.ietf.org/html/rfc7231#section-6.5.1", "title": "发生了一个或多个验证错误。", "status": 400, "detail": "不允许文件扩展名。", “traceId”:“|79eb7d85-40b4e4f64c19c86f。”,“错误”:{} } ``
      • 是的,我知道。自己生成它并拥有例如它是一种痛苦。 TraceId 正确,它们在版本之间另外更改。所以没有办法在中间件中使用ValidationProblem。我对标头的自定义验证有同样的问题:我想以完全相同的方式返回响应,但由于它不直接用作参数,我不能使用属性验证,并且在中间件中我必须“模拟" ValidationProblem json 我自己...
      【解决方案9】:

      使用中间件或 IExceptionHandlerPathFeature 很好。 eshop还有另一种方法

      创建一个异常过滤器并注册它

      public class HttpGlobalExceptionFilter : IExceptionFilter
      {
        public void OnException(ExceptionContext context)
        {...}
      }
      
      services.AddMvc(options =>
      {
        options.Filters.Add(typeof(HttpGlobalExceptionFilter));
      })
      

      【讨论】:

        【解决方案10】:

        这里是the official guideline from Microsoft,涵盖所有 .NET 版本的 WebAPI 和 MVC 案例。

        对于 Web API,它建议重定向到专用控制器端点以返回 ProblemDetails。由于它可能会导致在OpenAPI spec 中潜在地暴露不打算直接调用的端点,我建议使用更简单的解决方案:

        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);
            }));
            ...
        }
        

        在这种情况下,我们利用标准中间件返回自定义详细信息(带有开发模式的堆栈跟踪)并避免创建“内部”端点。

        附:请注意,the official guideline 在 .NET v3 之前和之后(目前为 v5)依赖于 IExceptionHandlerPathFeature - 在 IExceptionHandlerFeature 上。

        【讨论】:

          【解决方案11】:

          处理任何特定方法的异常的简单方法是:

          using Microsoft.AspNetCore.Http;
          ...
          
          public ActionResult MyAPIMethod()
          {
              try
              {
                 var myObject = ... something;
          
                 return Json(myObject);
              }
              catch (Exception ex)
              {
                  Log.Error($"Error: {ex.Message}");
                  return StatusCode(StatusCodes.Status500InternalServerError);
              }         
          }
          

          【讨论】:

            【解决方案12】:

            如果您想为特定控制器设置自定义异常处理行为,可以通过覆盖控制器 OnActionExecuted 方法来实现。

            记得将 ExceptionHandled 属性设置为 true 以禁用默认异常处理行为。

            这是我正在编写的 api 的示例,我想在其中捕获特定类型的异常并返回 json 格式的结果:

                private static readonly Type[] API_CATCH_EXCEPTIONS = new Type[]
                {
                    typeof(InvalidOperationException),
                    typeof(ValidationException)           
                };
            
                public override void OnActionExecuted(ActionExecutedContext context)
                {
                    base.OnActionExecuted(context);
            
                    if (context.Exception != null)
                    {
                        var exType = context.Exception.GetType();
                        if (API_CATCH_EXCEPTIONS.Any(type => exType == type || exType.IsSubclassOf(type)))
                        {
                            context.Result = Problem(detail: context.Exception.Message);
                            context.ExceptionHandled = true;
                        }
                    }  
                }
            

            【讨论】:

              猜你喜欢
              • 2023-02-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-05-04
              • 1970-01-01
              • 2014-06-12
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多