Reputation: 365
I am trying to configure my middleware pipeline to use 2 different exception handlers to handle the same exception. For example, I'm trying to have both my custom handler and in-built DeveloperExceptionPageMiddleware as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.ConfigureCustomExceptionHandler();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.ConfigureCustomExceptionHandler();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
My objective is to have the custom handler do its own thing (logging, telemetry, etc), and then pass on (next()) to the other in-built handler which displays a page. My custom handler looks like this:
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureCustomExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Use(async (context, next) =>
{
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
//log error / do custom stuff
await next();
}
});
});
}
}
I cannot get CustomExceptionHandler to pass on processing to the next middleware. I get the following page instead:
404 error:
I tried switching around the order, but then the developer exception page takes over and the custom exception handler is not called.
Is what I'm trying to do possible at all?
Update:
The solution was to take Simonare's original suggestion and re-throw the exception in the
Invoke
method. I also had to remove any type of response-meddling by replacing the following inHandleExceptionAsync
method:
context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)code; return context.Response.WriteAsync(result);
with:
return Task.CompletedTask;
Upvotes: 7
Views: 3325
Reputation: 60546
Here's a very simple version of how to use custom exception handling logic WITH the built-in ASP.NET Core error page at the same time:
app.UseExceptionHandler("/Error"); //use standard error page
app.Use(async (context, next) => //simple one-line middleware
{
try
{
await next.Invoke(); //attempt to run further application code
}
catch (Exception ex) //something went wrong
{
//log exception, notify the webmaster, etc.
Log_Exception_And_Send_Email_or_Whatever(ex);
//re-throw the exception so it's caught by the outer "UseExceptionHandler"
throw;
}
});
P.S. Uhmm, I added an explicit language: c#
hint to my answer but syntax highlighting still does not see catch
as a keyword... Interesting.
Upvotes: 3
Reputation: 30545
Instead of calling two different Exception Handling Middleware, you may consider to add logging under your Home/Error
[AllowAnonymous]
public IActionResult Error()
{
//log your error here
return View(new ErrorViewModel
{ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
alternatively, you can use custom Expception Handling Middleware
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IHostingEnvironment env)
{
try
{
await _next(context);
}
catch (Exception ex)
{
if (!context.Response.HasStarted)
await HandleExceptionAsync(context, ex, env);
throw;
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception, IHostingEnvironment env)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
var message = exception.Message;
switch (exception)
{
case NotImplementedException _:
code = HttpStatusCode.NotImplemented;
break;
//other custom exception types can be used here
case CustomApplicationException cae: //example
code = HttpStatusCode.BadRequest;
break;
}
Log.Write(code == HttpStatusCode.InternalServerError ? LogEventLevel.Error : LogEventLevel.Warning, exception, "Exception Occured. HttpStatusCode={0}", code);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return Task.Completed;
}
}
and Simply Register it inside IApplicationBuilder Method
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<ErrorHandlingMiddleware>();
}
Upvotes: 2