官术网_书友最值得收藏!

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); }); });
You will need to add a usingreference for the Microsoft.AspNetCore.Httpnamespace in order to use the WriteAsync method.

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!

Fallback pages are a simpler alternative to catch-all routes.

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!

主站蜘蛛池模板: 聂荣县| 泽普县| 金昌市| 德格县| 克什克腾旗| 河池市| 长岛县| 阳新县| 凌云县| 甘南县| 新野县| 德惠市| 阿拉善右旗| 阜新市| 庆安县| 永安市| 馆陶县| 新干县| 望江县| 安庆市| 昌宁县| 广灵县| 桐乡市| 高密市| 临汾市| 华容县| 如皋市| 双桥区| 甘孜县| 北京市| 阳信县| 四川省| 昌乐县| 九台市| 墨竹工卡县| 延长县| 龙海市| 永康市| 通化市| 土默特右旗| 紫云|