以不同于非 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。
我正在尝试对我的网络 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。