1. The concept of middleware

The ASP.NET Core process is a pipeline, and the middleware is the component that is assembled into the application pipeline to process requests and responses. Each middleware can:

  • Choose whether to pass the request to the next component in the pipeline.
  • Business logic can be executed before and after the next component in the pipeline is called.

The middleware is an  instance of the public delegate Task RequestDelegate(HttpContext context) , so the essence of the middleware is a method. The parameter of the method is HttpContext, which returns the Task. The incoming HttpContext parameter contains the request and response information, which we can modify in the middleware. The pipeline processing process for middleware is as follows:

DotNet_Core_middleware_and_filters_for_error_logging_0.png

We know that middleware is the component that configures the request processing pipeline, so who is responsible for building the pipeline? The role responsible for building the pipeline is ApplicationBuilder. ApplicationBuilder registers middleware through the Use, Run, Map, and MapWhen methods to build a request pipeline. Let’s take a quick look at these methods.

1 Run

Create a new WebAPI project, modify the Configure method in StartUp as follows, the middleware registered with the Run method can be called the terminal middleware, that is, the middleware is not executed after the middleware is executed.

 

 

        Public  void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            / / The first middleware 
            app.Run ( async (context) =>
            {
                context.Response.ContentType = " text/plain;charset=utf-8 " ;  
                await context.Response.WriteAsync( "The first middleware output hello~ " );
            });
            //The second middleware 
            app.Run( async (context) =>
            {
                Await context.Response.WriteAsync( "The second middleware output hello~ " );
            });
        }

 

 

Running the program, we see that only the first middleware is executed, and the middleware behind it will not execute.

 

2 Use

The parameter of the Use method is a delegate instance. The first parameter of the delegate is HttpContext, which is the request context to be processed. The second parameter next is the next middleware. We can call the next middleware by next.Invoke(). And can do a logical processing of HttpContext before/after calling the next middleware.

 

 

        Public  void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //The first middleware 
            app.Use( async (context, next) =>
            {
                context.Response.ContentType = " text/plain;charset=utf-8 " ; // Prevent Chinese garbled 
                await context.Response.WriteAsync($ " first middleware output hello~{Environment.NewLine} " );

                Await context.Response.WriteAsync($ " Execute before the next middleware execution ===>{Environment.NewLine} " );
                 await next.Invoke();
                 await context.Response.WriteAsync($ " After the middleware is executed Execute <==={Environment.NewLine} " );
            });
            // Second middleware 
            app.Use( async (context,next) =>
            {
                Await context.Response.WriteAsync($ " Second middleware output hello~{Environment.NewLine} " );
            });
        }

 

 

Note that if we do not call the next.Invoke() method, the pipeline will be shorted and all subsequent middleware will not be executed.

3 Map

In the case of simple business, it is sufficient to use a request processing pipeline to process all requests. When the business is complicated, we may consider handing over requests for different services to different pipelines for processing.  A request pipeline branch is created based on a match for a given request path. If the request path begins with a given path, the branch is executed . Look at a chestnut, the request that starts with /userinfo is handled using the user branch pipeline, and the request at the beginning of /product is handled using the product branch pipeline. The code is as follows: Map

 

 

    Public  class Startup
    {
        Public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        Public IConfiguration Configuration { get ; }

        // Dependency injection 
        public  void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
        ///  <summary> 
        /// Configure the user branch pipeline, handle requests starting with url with /userinfo 
         ///  </summary> 
        ///  <param name="app"></param> 
        private  static  void UserinfoConfigure( IApplicationBuilder app)
        {
            app.Use( async (context, next) =>
            {
                Await context.Response.WriteAsync($ " Processing user business, {Environment.NewLine} " );
                 await next.Invoke();
            });
            app.Run( async (context) => { await context.Response.WriteAsync( " User Business Processing Complete ~ " ); });
        }
        ///  <summary> 
        /// Configure the product branch pipeline, handle requests with urls beginning with /product
         ///  </summary> 
        ///  <param name="app"></param> 
        private  static  void ProductConfigure( IApplicationBuilder app)
        {
            app.Use( async (context, next) =>
            {
                Await context.Response.WriteAsync($ " Processing Product Business " );
                 await next.Invoke();
            });
        }
        / / Configure request processing pipeline 
        public  void Configure (IApplicationBuilder app, IHostingEnvironment env)
        {
            / / Prevent Chinese garbled 
            app.Use ( async (context, next) =>
            {
                context.Response.ContentType = " text/plain;charset=utf-8 " ;
                 await next.Invoke();
            });

            app.Map( " /userinfo " , UserinfoConfigure);

            app.Map( " /product " , ProductConfigure);

            app.Run( async context =>
            {
                Await context.Response.WriteAsync( " Main pipe handles other services " );
            });
        }
    }

 

 

The running program execution results are as follows:

 

4 MapWhen

MapWhen and Map thought quite similar, MapWhen based on custom criteria to create the request pipeline branch, and maps the request to a new branch of the pipeline. Look at a chestnut to understand, the next chestnut needs is that the query parameter contains the name of the request to a branch pipe processing, the url containing /userinfo request is handed to the user branch to handle, the code is as follows:

 

 

    Public  class Startup
    {
        Public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        Public IConfiguration Configuration { get ; }

        // Dependency injection 
        public  void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
        ///  <summary> 
        /// Configure the branch pipe, handle the request with /userinfo in the
         url ///  </summary> 
        ///  <param name="app"></param> 
        private  static  void UserinfoConfigure( IApplicationBuilder app)
        {
            app.Use( async (context, next) =>
            {
                Await context.Response.WriteAsync($ " Processing user business, {Environment.NewLine} " );
                 await next.Invoke();
            });
            app.Run( async (context) => { await context.Response.WriteAsync( " User Business Processing Complete ~ " ); });
        }
        ///  <summary> 
        /// Configure the branch pipe, handle the request with the name of the query parameter
         ///  </summary> 
        ///  <param name="app"></param> 
        private  static  void HNameConfigure(IApplicationBuilder app )
        {
            app.Use( async (context, next) =>
            {
                Await context.Response.WriteAsync($ "The query parameter contains name with the value: {context.Request.Query[ " name " ]} " );
                 await next.Invoke();
            });
        }
        / / Configure request processing pipeline 
        public  void Configure (IApplicationBuilder app, IHostingEnvironment env)
        {
            / / Prevent Chinese garbled 
            app.Use ( async (context, next) =>
            {
                context.Response.ContentType = " text/plain;charset=utf-8 " ;
                 await next.Invoke();
            });
          
            app.MapWhen(context => context.Request.Query.ContainsKey( " name " ), HNameConfigure);
            app.MapWhen(context => context.Request.Path.Value.ToString().Contains( " /userinfo " ), UserinfoConfigure);

            app.Run( async context =>
            {
                Await context.Response.WriteAsync( " Main pipe handles other services " );
            });
        }
    }

 

Here we have a basic understanding of middleware, and then through an exception log middleware to understand how to use middleware in development.

 

2 Record the error log using middleware

The log component used here is nlog. First create a WebAPI project, add a custom log processing middleware CostomErrorMiddleware, log the log when the program fails, and print the details of the exception on the page in the development environment, non-development environment. Hide the details, the code is as follows:

 

 

    ///  <summary> 
    /// Custom error handling class
     ///  </summary> 
    public  class CostomErrorMiddleware
    {
        Private  readonly RequestDelegate next;
         private  readonly ILogger logger;
         private IHostingEnvironment environment;
         ///  <summary> 
        /// DI, inject logger and environment variables
         ///  </summary> 
        ///  <param name="next"></ Param> 
        ///  <param name="logger"></param> 
        ///  <param name="environment"></param> 
        public CostomErrorMiddleware(RequestDelegate next, ILogger<CostomErrorMiddleware> logger, IHostingEnvironment environment)
        {
            This .next = next;
             this .logger = logger;
             this .environment = environment;
        }
        ///  <summary> 
        /// Implement the Invoke method
         ///  </summary> 
        ///  <param name="context"></param> 
        ///  <returns></returns> 
        public  async Task Invoke(HttpContext Context)
        {
            Try
            {
                the await next.Invoke (context);
            }
            Catch (Exception ex)
            {
                Await HandleError(context, ex);
            }
        }
        ///  <summary> 
        /// Error message handling
         ///  </summary> 
        ///  <param name="context"></param> 
        ///  <param name="ex"></param> 
        ///  <returns></returns> 
        private  async Task HandleError(HttpContext context, Exception ex)
        {
            context.Response.StatusCode = 500 ;
            context.Response.ContentType = " text / JSON; charset = UTF-. 8; " ;
             String errorMsg $ = " Error message: {ex.Message} {Environment.NewLine} Error Tracking: ex.StackTrace {} " ;
             // whether Whether the error log 
            logger.LogError(errorMsg) is recorded for the development environment ;
             //The browser displays detailed error information in the development environment, and other environment hides the error message 
            if (environment.IsDevelopment())
            {
                the await context.Response.WriteAsync (errorMsg);
            }
            Else
            {
                Await context.Response.WriteAsync( " Sorry, the server has gone wrong " );
            }
        }
    }

 

 

Modify the Configure method in the StartUp class as follows. To inject nlog, you  need to install NLog.Web.AspNetCore first  , and use app.UseMiddleware<CostomErrorMiddleware>() to register our custom middleware. The code is as follows:

 

 

       / / / configuration request pipeline 
        public  void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
        {
            / / Add nlog 
            factory.AddNLog ();
            env.ConfigureNLog( " nlog.config " );
             // generic method to add middleware 
            app.UseMiddleware<CostomErrorMiddleware> ();
            app.UseMvc(); 
        }

 

 

Nlog.config:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="D:\LogDemoOfWebapi\internal-nlog.txt">
  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>
  <targets>
    <target xsi:type="File" name="errorLog" fileName="D:/logs/AT___${shortdate}.log" 
            layout="----------------日志记录开始----------------${newline}【日志时间】:${longdate} ${newline}【日志级别】:${level:uppercase=true}${newline}【异常相关信息】${newline}${message}${newline}${newline}${newline}" />
  </targets>
  <rules>
    <logger name="*" minlevel="Error" writeTo="errorLog" />
  </rules>
</nlog>

View Code

Here the exception handling middleware is registered, modify the ValueController to create an exception to test it, the code is as follows:

 

 

    [Route( " api/[controller] " )]
    [ApiController]
    Public  class ValuesController : ControllerBase
    {
        Private ILogger<ValuesController> _logger;
         public ValuesController(ILogger<ValuesController> logger)
        {
            _logger = logger;
        }
        // GET api/values 
        [HttpGet]
         public ActionResult<IEnumerable< string >> Get()
        {
          
            Return  new  string [] { " value1 " , " value2 " };
        }

        // GET api/values/5 
        [HttpGet( " {id} " )]
         public ActionResult< string > Get( int id)
        {
            Throw  new Exception( " An error has occurred.. " ) ;
             return "value";
        }
    }

 

 

Run the program, access /Values/1 in the development environment, the results are as follows, and these error messages will also be written to the error log via nlog:

DotNet_Core_middleware_and_filters_for_error_logging_21.png

In a non-development environment, access /values/1, which looks like this:

DotNet_Core_middleware_and_filters_for_error_logging_22.png

If we want to use our custom middleware in a form like app.UseMvc(), we need to add an extension method to ApplicationBuilder. First add a static class CostomMiddleware with the following code:

 

 

    ///  <summary> 
    /// Extension method
     ///  </summary> 
    public  static  class CostomMiddleware
    {
        Public  static IApplicationBuilder UseCostomError( this IApplicationBuilder app)
        {
            Return app.UseMiddleware<CostomErrorMiddleware> ();
        }
    }

 

 

Then modify the Configure method to:

 

 

        Public  void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
        {
            / / Add nlog 
            factory.AddNLog ();
            env.ConfigureNLog( " nlog.config " );
             // Use the extension method 
             app.UseCostomError();
            app.UseMvc(); 
        }

 

 

After running the program, the execution effect is the same as before.

 

3 Use the filter to log the error log

Filters should be familiar to everyone. There is not much change in the use of filters in ASP.NET Core. Here is also a chestnut that uses filters to log error logs. Just look at the code. First create a filter with the following code. :

 

 

    ///  <summary> 
    /// Custom error handling filter
     ///  </summary> 
    public  class CustomErrorFilter :Attribute, IExceptionFilter
    {
        Private  readonly ILogger _logger;
         private IHostingEnvironment _environment;

        Public CustomErrorFilter(ILogger<CustomErrorFilter> logger, IHostingEnvironment environment)
        {
            
            _logger = logger;
            _environment = environment;
        }
        Public  void OnException(ExceptionContext context)
        {
            Exception ex = context.Exception;
             string errorMsg = $ " Error Message: {ex.Message}{Environment.NewLine} Error Tracking: {ex.StackTrace} " ;
            ContentResult result = new ContentResult
            {
                ContentType = " text/json;charset=utf-8; " ,
                StatusCode = 500
            };
            / / Record the error log 
            _logger.LogError (errorMsg) regardless of the development environment ;
             / / browser displays detailed error information in the development environment, other environments hide the error message 
            if (_environment.IsDevelopment ())
            {
                result.Content = $ " Error Message: {ex.Message}{Environment.NewLine} Error Tracking: {ex.StackTrace} " ;
            }
            Else
            {
                result.Content = " Sorry, the server has gone wrong " ;
            }
            context.Result = result;
            context.ExceptionHandled = true ;
        }
    }

 

 

Modify the StartUp class, inject nlog, configure the global filter, the code is as follows, where nlog.config is the same as the middleware chestnut:

 

 

    Public  class Startup
    {
        Public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        Public IConfiguration Configuration { get ; }

        // Dependency injection 
        public  void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(
                Configure =>
                {
                    configure.Filters.Add <CustomErrorFilter>(); // Global filter without adding feature headers 
                } // Global filter without adding feature headers 
                ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
             // services.AddScoped<CustomErrorFilter>( ); / / local filter, you need to add a feature header in the Controller / Action [ServiceFilter (typeof (CustomErrorFilter))] 
        }

        / / Configure the pipeline 
        public  void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
        {
            factory.AddNLog();
            env.ConfigureNLog( " nlog.config " );
            app.UseMvc();
        }
    }

 

 

Then modify the ValuesController, set the error and the chestnut of the middle middleware. When running the code access /values/1, it will be displayed in the development environment as follows, and the error message will be written into the error log:

DotNet_Core_middleware_and_filters_for_error_logging_31.png

If you access /values/1 in a production environment, the error details will be written to the error log. The browser displays the following:

DotNet_Core_middleware_and_filters_for_error_logging_32.png

This article introduces the basic use of middleware, and uses the middleware and filter to achieve the record of the exception log. If there is something wrong in the text, I hope everyone can point it out, I will correct it in time.

Reference article

[1]https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0

[2] https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.0

 

Orignal link:https://www.cnblogs.com/wyy1234/p/11373999.html