以不同于非 API 异常的方式处理 API 异常。将代码移动到外部 class 库时出错

Handle API exceptions differently to non API exceptions. Errors when moving code to an external class library

我正在尝试对我的网络 API 控制器和我的非网络 API 控制器进行不同的异常处理。换句话说,我想要这个声明

app.UseExceptionHandler("/Home/Error");

,这是在 Microsoft.AspNetCore.Diagnostics

中找到的扩展

适用于除以 /api 开头的所有 URL。我已经编写了一些代码来执行此操作,并且在将其包含在 Web 项目中时可以正常工作。但是,如果我将此代码移至另一个程序集,则会出现错误。

我已经使用 ASP.NET Core 5 编写了一些代码来演示我的问题。

Program.cs


using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace ExceptionHandling
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}


Startup.cs


using ExceptionHandlingUtils;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace ExceptionHandling
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app)
        {

            app.UseaNonApiExceptionHandler("/Home/Error");  // this is my custom middleware, for implementation see below
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

下面是调用它的自定义中间件和扩展方法。 ExceptionHandlerExtensions 几乎是从 here. And the code for ExceptionHandlerMiddleware.cs is here

复制而来的
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace ExceptionHandling.Middleware
{
    public static class ExceptionHandlerExtensions
    {
        public static IApplicationBuilder UseaNonApiExceptionHandler(this IApplicationBuilder app, string errorHandlingPath)
        {
            return app.UseaNonApiExceptionHandler(new ExceptionHandlerOptions
            {
                ExceptionHandlingPath = new PathString(errorHandlingPath)
            });
        }

        public static IApplicationBuilder UseaNonApiExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options)
        {
            return app.UseMiddleware<NonApiExceptionHandler>(Options.Create(options));
        }
    }
}

NonApiExceptionHandler.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics;
using System.Threading.Tasks;

namespace ExceptionHandling
{
        public class NonApiExceptionHandler
    {
        private readonly ExceptionHandlerMiddleware _exceptionHandlerMiddleware;
        private readonly RequestDelegate _next;

        public NonApiExceptionHandler(
            RequestDelegate next,
            ILoggerFactory loggerFactory,
            IOptions<ExceptionHandlerOptions> options,
            DiagnosticListener diagnosticListener)

        {
            _next = next;
            _exceptionHandlerMiddleware = new ExceptionHandlerMiddleware(next, loggerFactory, options, diagnosticListener);

        }

        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments("/api"))
            {
                await _next(context);
                return;
            }

            await _exceptionHandlerMiddleware.Invoke(context);
        }
    }
}

这是我的控制器

using Microsoft.AspNetCore.Mvc;
using System;

namespace ExceptionHandling.Controllers
{
    public class HomeController: Controller
    {
        public IActionResult Index()
        {
            throw new Exception("an error");
        }

        public IActionResult Error()
        {
            return Content("an error has occured");
        }
    }
}

这是我的网站 API 控制器

using Microsoft.AspNetCore.Mvc;
using System;

namespace ExceptionHandling.Controllers.api
{
    [Route("api/[controller]")]
    [ApiController]
    public class MyApiController: ControllerBase
    {

        public IActionResult Get()
        {
            throw new Exception("an error");
        }
    }
}

以上代码按预期工作。 IE。如果您导航到 / 然后抛出异常,您将被重定向到 /home/error 并显示消息。如果您导航到 /api/myapi,则会抛出异常,但不会将您重定向到 /home/error。

因为这是我的许多网站都需要的功能,所以我试图将这段代码放入另一个程序集中。换句话说,我将我的启动文件更改为这个

using ExceptionHandlingUtils;  //note the namespace change, this is the same code but in a different 
                                //assembly
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace ExceptionHandling
{
    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app)
        {

            app.UseaNonApiExceptionHandler("/Home/Error");          

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

并将 2 个自定义中间件文件移动到一个 class 库,目标为 .NET 5,命名空间为 ExceptionHandlingUtils。该库导入了 nuget 包 Microsoft.AspNetCore.Diagnostics。当我 运行 相同的代码但从单独的程序集调用我的自定义中间件时,我收到以下错误

An error occurred while starting the application.
MissingMethodException: Method not found: 'Void Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware..ctor(Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.Extensions.Logging.ILoggerFactory, Microsoft.Extensions.Options.IOptions`1<Microsoft.AspNetCore.Builder.ExceptionHandlerOptions>, System.Diagnostics.DiagnosticSource)'.
ExceptionHandlingUtils.NonApiExceptionHandler..ctor(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ExceptionHandlerOptions> options, DiagnosticListener diagnosticListener)

MissingMethodException: Method not found: 'Void Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware..ctor(Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.Extensions.Logging.ILoggerFactory, Microsoft.Extensions.Options.IOptions`1<Microsoft.AspNetCore.Builder.ExceptionHandlerOptions>, System.Diagnostics.DiagnosticSource)'.
ExceptionHandlingUtils.NonApiExceptionHandler..ctor(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ExceptionHandlerOptions> options, DiagnosticListener diagnosticListener)
System.RuntimeMethodHandle.InvokeMethod(object target, object[] arguments, Signature sig, bool constructor, bool wrapExceptions)
System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, object[] parameters)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass5_0.<UseMiddleware>b__0(RequestDelegate next)
Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
ExceptionHandling.Program.Main(string[] args) in Program.cs
+
            CreateHostBuilder(args).Build().Run();

我想知道如何修复这个错误。或者还有另一种方法可以从 Microsoft.AspNetCore.Diagnostics 获取 ExceptionHandlerMiddleware 以忽略我的网络 API urls.

我建议使用 UseWhen,而不是创建自定义中间件 class、NonApiExceptionHandler,它会创建 ExceptionHandlerMiddleware 的实例:[=24] =]

Conditionally creates a branch in the request pipeline that is rejoined to the main pipeline.

这是一个使用 UseWhen 的最小示例:

app.UseWhen(
   context => !context.Request.Path.StartsWithSegments("/api"),
   nonApiApp => nonApiApp.UseExceptionHandler("/Home/Error"));

传递给UseWhen的第一个参数表示管道将被分支的条件。在这种情况下,UseExceptionHandler 将影响 /api 开头的请求。

您可以将其包装在扩展方法中,就像您在问题中所做的那样,以便它可以重复使用。这是一个简单的例子:

public static class ExceptionHandlerExtensions
{
    public static IApplicationBuilder UseaNonApiExceptionHandler(
        this IApplicationBuilder app, string errorHandlingPath)
    {
        return app.UseWhen(
            context => !context.Request.Path.StartsWithSegments("/api"),
            nonApiApp => nonApiApp.UseExceptionHandler(errorHandlingPath));
    }

    // ...
}

有关 UseWhen 的更多详细信息,包括它与 MapWhen 的比较,请参阅 Branch the middleware pipeline