I am trying to have different exception handling for my web API controllers and my non web API controllers. In other words I want this statement
app.UseExceptionHandler("/Home/Error");
,which is an extension found in Microsoft.AspNetCore.Diagnostics,
to work for all URLs except those that start with /api. I have written some code that does this and it works when it's included in the web project. However if I move this code to another assembly I get an error.
I have written some code to demo my problem using 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?}");
});
}
}
}
Below is the custom middleware and extension method that calls it. ExceptionHandlerExtensions is pretty much copied from 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);
}
}
}
Here is my controller
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");
}
}
}
Here is my web API controller
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");
}
}
}
The above code works as expected. I.e. if you navigate to / then the exception is thrown and you are redirected to /home/error and the message is displayed. And if you navigate to /api/myapi the exception is thrown but you are not redirected to /home/error.
Because this is functionality I need in many of my websites I have attempted to put this code into another assembly. In other words I changed my startup file to this
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?}");
});
}
}
}
and moved the 2 custom middleware files to a class library targetting .NET 5, with the namespace ExceptionHandlingUtils. The library has the nuget package Microsoft.AspNetCore.Diagnostics imported. When I run the same code but calling my custom middleware from a separate assembly I get the following error
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();
I would like to know how I can fix this error. Alternatively is there another way to get the ExceptionHandlerMiddleware from Microsoft.AspNetCore.Diagnostics to ignore my web API urls.