- Modern Web Development with ASP.NET Core 3
- Ricardo Peres
- 992字
- 2021-06-18 18:36:00
Error handling in routing
What do we do with errors—exceptions caught during the processing of a request, for example, when a resource is not found? You can use routing for this. Here, we will present a few strategies:
- Routing
- Adding a catch-all route
- Showing developer error pages
- Using the status code pages middleware
We will learn about these in the following sections.
Routing errors to controller routes
You can force a specific controller's action to be called when an error occurs by callingUseExceptionHandler:
app.UseExceptionHandler("/Home/Error");
What you put in this view (Error) is entirely up to you, mind you.
You can even do something more interesting, that is, register middleware to execute upon the occurrence of an error, as follows:
app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var errorFeature = context.Features.Get<IException
HandlerPathFeature>(); var exception = errorFeature.Error; //you may want to check what
//the exception is
var path = errorFeature.Path;
await context.Response.WriteAsync("Error: " + exception.Message); }); });
The IExceptionHandlerPathFeature feature allows you to retrieve the exception that occurred and the request path. Using this approach, you have to generate the output yourself; that is, you do not have the benefit of having an MVC view.
Next, we will how we can show user-friendly error pages.
Using developer exception pages
For running in development mode, you are likely to want a page that shows developer-related information, in which case, you should callUseDeveloperExceptionPageinstead:
app.UseDeveloperExceptionPage();
This will show the exception message, including all request properties and the stack trace, based on a default template that also contains environment variables. It is normally only used for the Development environment, as it may contain sensitive information that could potentially be used by an attacker.
Since .NET Core 3, it is possible to tweak the output of this, by means of an IDeveloperPageExceptionFilter implementation. We register one in the Dependency Injection container and either provide our own output in the HandleExceptionAsync method or just return the default implementation:
services.AddSingleton<IDeveloperPageExceptionFilter, CustomDeveloperPageExceptionFilter>();
This method is very simple: it receives an error context and a delegate that points to the next exception filter in the pipeline, which is normally the one that produces the default error page:
class CustomDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
public async Task HandleExceptionAsync(ErrorContext
errorContext, Func<ErrorContext, Task> next)
{
if (errorContext.Exception is DbException)
{
await errorContext.HttpContext.Response.WriteAsync("Error
connecting to the DB");
}
else
{
await next(errorContext);
}
}
}
This simple example has conditional logic that depends on the exception and either sends a custom text or just delegates to the default handler.
Using a catch-all route
You can add a catch-all route by adding an action method with a route that will always match if no other does (like the fallback page in the Fallback endpoints section). For example, we can use routing attributes as follows:
[HttpGet("{*url}", Order = int.MaxValue)]
public IActionResult CatchAll()
{
this.Response.StatusCode = StatusCodes.Status404NotFound;
return this.View();
}
The same, of course, can be achieved with fluent configuration, in the Configure method:
app.UseEndpoints(endpoints =>
{
//default routes go here
endpoints.MapControllerRoute(
name: "CatchAll",
pattern: "{*url}",
defaults: new { controller = "CatchAll", action = "CatchAll" }
);
});
Here, all you need to do is add a nice view with a friendly error message! Be aware that the other actions in the same controller also need to have routes specified; otherwise, the default route will become CatchAll!
Using status code pages middleware
Let's see now how we can respond to errors with HTTP status codes, the standard way of returning high-level responses to the client.
Status code pages
A different option is to add code in response to a particular HTTP status code between 400Bad Request and 599Network Connect Time Out that does not have a body (has not been handled), and we do that through UseStatusCodePages:
app.UseStatusCodePages(async context => { context.HttpContext.Response.ContentType ="text/plain";
var statusCode = context.HttpContext.Response.StatusCode;
await context.HttpContext.Response.WriteAsync("HTTP status code: "+ statusCode); });
The method adds a middleware component to the pipeline that is responsible for, after an exception occurs, doing two things:
- Filling the Error property on the IStatusCodePagesFeature feature
- Handling the execution from there
Here's a different overload, doing essentially the same as the last one:
app.UseStatusCodePages("text/plain", "Error status code: {0}");
Here's something for automatically redirecting to a route (with an HTTP code of 302Found)with a particular status code as a route value:
app.UseStatusCodePagesWithRedirects("/error/{0}");
This one, instead, re-executes the pipeline without issuing a redirect, thus making it faster:
app.UseStatusCodePagesWithReExecute("/error/{0}");
All of the execution associated with specific status codes can be disabled through theIStatusCodePagesFeature feature:
var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();
statusCodePagesFeature.Enabled = false;
Routing to specific status code pages
You can add an action such as this to a controller to have it respond to a request of "error/404" (just replace the error code with whatever you want):
[Route("error/404")]
public IActionResult Error404()
{
this.Response.StatusCode = StatusCodes.Status404NotFound;
return this.View();
}
Now, either add an Error404 view or instead call a generic view, passing it the 404 status code, perhaps through the view bag. Again, this route can be configured fluently, as follows:
endpoints.MapControllerRoute(
name: "Error404",
pattern: "error/404",
defaults: new { controller = "CatchAll", action = "Error404" }
);
This, of course, needs to be used either with UseStatusCodePagesWithRedirects or UseStatusCodePagesWithReExecute.
Any status code
To catch all errors in a single method, do the following:
[Route("error/{statusCode:int}")]
public IActionResult Error(int statusCode)
{
this.Response.StatusCode = statusCode;
this.ViewBag.StatusCode = statusCode;
return this.View();
}
Here, we are calling a generic view called Error (inferred from the action name), so we need to pass it the originating status code, which we do through the view bag, as follows:
endpoints.MapControllerRoute(
name: "Error",
pattern: "error/{statusCode:int}",
defaults: new { controller = "CatchAll", action = "Error" }
);
For a request of /error/<statusCode>, we are directed to the CatchAllControllercontroller and Erroraction.Again, this requiresUseStatusCodePagesWithRedirectsorUseStatusCodePagesWithReExecute.
Here we presented different ways to handle errors, either based on an exception or on a status code. Pick the one that suits you best!
- Effective C#:改善C#代碼的50個有效方法(原書第3版)
- Building a Game with Unity and Blender
- 三維圖形化C++趣味編程
- 游戲程序設計教程
- Redis Essentials
- Rust Essentials(Second Edition)
- INSTANT Passbook App Development for iOS How-to
- Python面試通關寶典
- MySQL數據庫應用實戰教程(慕課版)
- C# 7.0本質論
- Android初級應用開發
- SCRATCH編程課:我的游戲我做主
- Java語言程序設計實用教程(第2版)
- ASP.NET Core 2 High Performance(Second Edition)
- Qt編程快速入門