ChatGPT解决这个技术问题 Extra ChatGPT

ASP.NET Core Web API 异常处理

在使用常规 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 代码中的噪音太大了。我永远不会向任何人推荐它。

A
Andrei

快速简单的异常处理

只需在 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")] 中的路径应该相同,以允许控制器处理从异常处理程序中间件重定向的异常。

这是 Microsoft 官方文档的 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<IExceptionHandlerFeature>() 什么?

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


我一直在试图让自定义中间件在今天工作,它的工作方式基本相同(我正在使用它来管理请求的工作单元/事务)。我面临的问题是中间件中没有捕获“下一个”中引发的异常。可以想象,这是有问题的。我在做什么错/错过了什么?任何指示或建议?
@brappleye3 - 我想出了问题所在。我只是在 Startup.cs 类的错误位置注册中间件。我将 app.UseMiddleware<ErrorHandlingMiddleware>(); 移到 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 绰绰有余。如果您觉得这个答案不够好,请随时贡献。
I
Ilya Chernomordik

有一个内置的中间件:

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 响应。
S
Steve Dunn

您最好的选择是使用中间件来实现您正在寻找的日志记录。您希望将异常日志记录放在一个中间件中,然后在另一个中间件中处理向用户显示的错误页面。这允许逻辑分离并遵循 Microsoft 使用 2 个中间件组件制定的设计。这是 Microsoft 文档的一个很好的链接: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 响应......
s
spottedmahn

广为接受的答案对我帮助很大,但我想在我的中间件中传递 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 状态和中间件中的自定义消息

捕获(HttpStatusCodeException ex)

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

私有 Task HandleExceptionAsync(HttpContext context, HttpStatusCodeException 异常) 方法

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

任务 HandleExceptionAsync(HttpContext 上下文,异常异常)

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


扩展方法放在哪里?不幸的是,在 void Configure(IapplicationBuilder app)startup.cs 中,我收到错误 IApplicationBuilder does not contain a definition for ConfigureCustomExceptionMiddleware。我添加了参考,其中 CustomExceptionMiddleware.cs 是。
您不想使用异常,因为它们会减慢您的 api。例外非常昂贵。
@Inaie,不能这么说......但似乎你从来没有遇到过任何例外......伟大的工作
你确定要使用“throw ex;”吗?而不是“扔;” ?
@LeszekP,我认为两者都可以,尽管我还没有测试过
I
Ihar Yakimush

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

Community.AspNetCore.ExceptionHandling.NewtonsoftJson 用于 ASP.NET Core 2.0

Community.AspNetCore.ExceptionHandling.Mvc 用于 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();
}

C
CountZero

首先,感谢 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;
        }
    }
}

如果您想检查当前的源代码并通过这种方法添加内容,请参阅 github.com/dotnet/aspnetcore/blob/master/src/Middleware/…
E
Edward Brey

首先,将 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 编码的错误信息放入消息正文中。


r
r.pedrosa

通过添加您自己的“异常处理中间件”,当发生错误时,很难重用一些好的 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();
        }
    }
}

注意:

NotFoundException 是一个自定义异常,您需要做的就是 throw new NotFoundException(null);或 throw new ArgumentException("Invalid argument.");您不应向客户提供敏感的错误信息。服务错误是一种安全风险。


我这样做是为了返回与 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 我自己...
w
ws_

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

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

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

非常感谢您的回答!!!你救了我!!我正在实现 IActionFilter 接口,但它没有捕获所有异常。将其更改为 IExceptionFilter 对我有用。非常感谢!
A
Alex Klaus

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

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

PS 请注意,the official guideline 在 .NET v3 之前和从那时起(截至目前的 v5)依赖于 IExceptionHandlerPathFeature - 在 IExceptionHandlerFeature 上。

PSS 如果您要从域层抛出异常以将它们转换为 4xx 代码,我建议使用 khellang's ProblemDetailsMiddleware 或返回可稍后转换为 IActionResultIResultDomainResult。后一个选项可帮助您在没有异常开销的情况下获得相同的结果。


我喜欢这个,因为它很简单而且似乎可以工作——只需添加上面的代码,你就有了一个即时的全局异常处理程序。注意:如果您正在使用 app.UseDeveloperExceptionPage(),请不要忘记将其删除以使此解决方案和类似解决方案正常工作。
但是,我注意到从主线程以外的线程抛出异常处理程序时没有调用。因此,对于这种情况,我在新线程中使用简单的 try/catch 作为解决方法(以记录异常)。也许有更好的方法。
刚刚对其进行了测试,它确实处理了从其他线程抛出的异常(并且我检查了 Thread.CurrentThread.ManagedThreadId 的这个声明)。您的案例更有可能有另一个因果关系(例如异常映射中间件)。另外,请注意此SO post中强调的中间件的注册顺序。
s
spottedmahn

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

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

v
vidmartin

如果要为特定控制器设置自定义异常处理行为,可以通过覆盖控制器 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;
            }
        }  
    }