Series is introduced

Five Minutes of DotNet is a blog series that uses your fragmented time to learn and enrich your.NET knowledge. It covers all aspects of the.net architecture that might be involved, such as C# details, AspnetCore,.net knowledge in microservices, and so on. 5min+ does not mean more than 5 minutes, “+” is an increase in knowledge. So, it allows you to spend less than 5 minutes to improve your knowledge base.

The body of the

When it comes to global exceptions in AspNet Core, everyone is familiar with them. Because these things are used so frequently, good exception handling helps developers locate problems faster and gives users a better user experience.

For example, when you visit a web page, suddenly, it meow error! You did not read wrong, it gave an error!! It then displays an error page like this:

How do you feel in front of your computer screen right now? (I want to whip out that legendary level 95 epic sword!)

However, if we deal with this anomaly a little bit, such as using our Tencent dad’s method, change the skin:

The user will immediately think: “Oh, mistakes are mistakes, and it’s hard for programmers to make mistakes.”

This shows!! How important it is to catch and handle global exceptions.

Global processing in AspNet Core

IAsyncExceptionFilter

So how do we catch and handle exceptions in AspNet Core? Many students may know: IExceptionFilter. This filter is one of the oldest filters in AspNet, dating back to the early days. It allows us to catch errors in AspNet Core controllers. However, for using IExceptionFilter, I actually prefer to consider its asynchronous version: IAsyncExceptionFilter. (Don’t ask why, ask is love offering).

So how does this filter work? The following uses IAsyncExceptionFilter as an example, which is the same for the synchronous version:

public class MyCustomerExceptionFilter : IAsyncExceptionFilter
{
    public Task OnExceptionAsync(ExceptionContext context)
    {
        if (context.ExceptionHandled == false)
        {
            string msg = context.Exception.Message;
            context.Result = new ContentResult
            {
                Content = msg,
                StatusCode = StatusCodes.Status200OK,
                ContentType = "text/html; charset=utf-8"
            };
        }
        context.ExceptionHandled = true; // The exception has been handled

        returnTask.CompletedTask; }}Copy the code

We have created a custom exception filter, the code is very simple, is still Http return 200 status code after the error. The error message is returned to the client.

Then we need to tell MVC in startup. cs that we have added this new filter:

services.AddControllers(options => options.Filters.Add(new MyCustomerExceptionFilter()));
Copy the code

And then it’s over. So easy? Take a look at the results:

 [HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    throw new Exception("has error!");
}
Copy the code

If this filter is not added, we get a response with an Http status code of 500. This is a bit of an overkill for non-fatal accidental operations, and not very user-friendly for front-end users (typing a wrong character and being told that the site crashed and your Highness Joe appeared).

We catch the exception, do some special handling, and then we’re nice. (Returns 200 and tells the user that he or she mistyped a character, etc.)

In the code above, you see a line context.ExceptionHandled = true; . Attention!! This is critical, and when you are done with the exception, remember to change this property to true to indicate that the exception has been handled. If you don’t change it, heh heh 🤪. What’s the result? Please see the following ↓

Middleware processing exception

Due to the layered nature of the AspNet Core pipeline, we have the opportunity to implement global exception catching in the pipeline. Try creating a new middleware:

public class MyExceptionMiddleware
{
    private readonly RequestDelegate _next;
    public MyExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            httpContext.Response.ContentType = "application/problem+json";

            var title = "An error occured: " + ex.Message;
            var details = ex.ToString();

            var problem = new ProblemDetails
            {
                Status = 200,
                Title = title,
                Detail = details
            };

            //Serialize the problem details object to the Response as JSON (using System.Text.Json)
            var stream = httpContext.Response.Body;
            awaitJsonSerializer.SerializeAsync(stream, problem); }}}Copy the code

Then in startup. cs, register the pipe:

app.UseMiddleware<MyExceptionMiddleware>();
Copy the code

Take a look at the results:

It’s the same flavor, the same formula. Cool!

The order in which the pipe is added determines the order in which it is executed, so if you want to extend the scope of exception catching, you can place the pipe in the first line of Configure. But!!!!! Don’t you notice that the default AspNet Core project already has an exception handling in the first line? I * &&… &.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
}
Copy the code

This line of code you’ll see when you initialize a new AspNetCore project, or maybe you only have the top half, depending on the template. But that doesn’t matter, it’s all about catching and handling exceptions. About the extension UseDeveloperExceptionPage let’s not say, it means: for a development mode, once an error page will jump to the error stack. The second UseExceptionHandler is interesting because, from its name, it must be an error interceptor. So how does it differ from our custom exception handling pipe?

“There must be a default if you don’t specify it!” Yes, it is the default error handling. So, it is actually a middleware, to look for it called ExceptionHandlerMiddleware. When using the UseExceptionHandler method, we can optionally fill in various parameters. At the top of the code, for example, fill in “/ Error parameters, said when an exception will be directed to the corresponding path is defined here as:” http://localhost:5001/Error “. Of course, you can specify any page you want, such as the beautiful Lord Joe page. 😝

And specify ExceptionHandlerOptions parameters, the method of the parameters is an important parameter of ExceptionHandlerMiddleware middleware:

Parameter names instructions
ExceptionHandlingPath The redirected path, such as “/Error”, is actually the parameter specified
ExceptionHandler Error interception handlers

ExceptionHandler allows us to specify our own exception handling within ExceptionHandlerMiddleware logic. The parameter is of type RequestDelegate, which looks familiar. Therefore UseExceptionHandler provides a convenient method and can let us in ExceptionHandlerMiddleware and new custom error interception pipeline as a handler:

//in Configure()
app.UseExceptionHandler(appbuilder => appbuilder.Use(ExceptionHandlerDemo));

/ / the content will be in AspNetCore pipeline to return the result to ExceptionHandlerMiddleware, if middleware catch exceptions to the call
private async Task ExceptionHandlerDemo(HttpContext httpContext, Func<Task> next)
{
    / / the information provided by the ExceptionHandlerMiddleware middleware, containing the ExceptionHandlerMiddleware middleware caught exception information.
    var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
    varex = exceptionDetails? .Error;if(ex ! =null)
    {
        httpContext.Response.ContentType = "application/problem+json";

        var title = "An error occured: " + ex.Message;
        var details = ex.ToString();

        var problem = new ProblemDetails
        {
            Status = 500,
            Title = title,
            Detail = details
        };

        var stream = httpContext.Response.Body;
        awaitJsonSerializer.SerializeAsync(stream, problem); }}Copy the code

Pipes VS filters

So what’s the difference between these two methods? Answer: Intercept range.

As the content between MVC middleware, IExceptionFilter requires MVC to submit the error information to it for processing after discovering the error, so its error processing scope is limited to MVC middleware. So, if we need to catch some previous MVC middleware errors, we can’t catch them. For ExceptionHandlerMiddleware middleware is very simple, as the first middleware, all in it after all the mistakes it can capture.

So it seems that IExceptionFilter is useless? No, using filters is a good choice if you want to catch and handle MVC exceptions quickly, or if you only care about exceptions between controllers.

Remember when we said context.ExceptionHandled = true; . If the exception is marked as already handled in IExceptionFilter, the first exception processing middleware considers that there is no error and will not enter the processing logic. Therefore, if we do not change this property to true, it is very likely that the interception result will be overwritten.

Finally, secretly say: creation is not easy, point a recommendation…..