Explore ASP.NET Core Middleware Implementation Method

Time:2019-10-7

concept

ASP.NET Core Middleware is a component used to process requests and operational responses in application processing pipeline.

Each component:

  • Determine whether the request is passed to the next component in pipeline
  • To do some work before and after the next component of the processing pipeline executes, the HttpContxt object can perform the execution cycle of requests and responses across domains.

Characteristics and Behavior

ASP.NET Core processing pipeline is composed of a series of request delegations, which are called one by one. The flow chart of the MIDDLEWARE pipeline drawn by ourselves is given below.

As can be seen from the figure above, requests go through four middleware processes since they enter the processing pipeline. Each middleware contains a reference to the next next next middleware, and each middleware can decide to participate in some logical processing of Http requests and responses before and after crossing the bat.

Each middleware can also decide not to forward requests to the next delegate, which is called short-circuit of the request pipeline (short-circuit is necessary, and some proprietary middleware, such as StaticFile Middleware, can avoid forwarding requests to other dynamic processes after completing functions).

Source code implementation

Observe the writing and usage of a standard middleware Code:

using System.Threading.Tasks;
using Alyio.AspNetCore.ApiMessages;
using Gridsum.WebDissector.Common;
using Microsoft.AspNetCore.Http;
 
namespace Gridsum.WebDissector
{
  sealed class AuthorizationMiddleware
  {
    Private readonly RequestDelegate_next; // Reference to the next middleware execution delegate
 
    public AuthorizationMiddleware(RequestDelegate next)
    {
      _next = next;
    }
 
    Public Task Invoke (HttpContext context) // Throughout the HttpContext object
    {
      if (context.Request.Path.Value.StartsWith("/api/"))
      {
        return _next(context);
      }
      if (context.User.Identity.IsAuthenticated && context.User().DisallowBrowseWebsite)
      {
        throw new ForbiddenMessage("You are not allow to browse the website.");
      }
      return _next(context);
    }
  }
}
 
 public static IApplicationBuilder UserAuthorization(this IApplicationBuilder app)
 {
   return app.UseMiddleware<AuthorizationMiddleware>();
 }
 // Enabling the middleware, that is, registering the middleware
 app.UserAuthorization();

Standard middleware is so simple and straightforward to use, with a few questions to explore the source implementation

(1) how to complete the middleware transmission: app.UseMiddleware<Authorization> (AuthOption); when we pass the reference, we can automatically inject the middleware constructors into first parameters.

(2) when you write middleware, why do you have to define a specific Invoke/InvokeAsync function?

(3) It is important to set the order of middleware. How to determine the nesting order of middleware?

Consider the behavior of the above standard middleware: Enter the execution delegation Next of the next middleware and define the execution delegation Invoke/Invoke Async of the current middleware;

Each middleware completes the behavior of Func < Request Delegate, Request Delegate >.

Through the parameter next and the execution of the next middleware, Invoke/Invoke Async is entrusted to establish a “chain” relationship.


public delegate Task RequestDelegate(HttpContext context);
// Excerpt from Microsoft. AspNetCore. Builder. UseMiddleware Extensions
    /// <summary>
    /// Adds a middleware type to the application's request pipeline.
    /// </summary>
    /// <typeparam name="TMiddleware">The middleware type.</typeparam>
    /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
    /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
    /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
    {
      return app.UseMiddleware(typeof(TMiddleware), args);
    }
    /// <summary>
    /// Adds a middleware type to the application's request pipeline.
    /// </summary>
    /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
    /// <param name="middleware">The middleware type.</param>
    /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
    /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
    {
      if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
      {
        // IMiddleware doesn't support passing args directly since it's
        // activated from the container
        if (args.Length > 0)
        {
          throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
        }
 
        return UseMiddlewareInterface(app, middleware);
      }
 
      var applicationServices = app.ApplicationServices;
      return app.Use(next =>
      {
        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        // Enforcement delegate name is restricted to Invoke/InvokeAsync
        var invokeMethods = methods.Where(m =>
          string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
          || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
          ).ToArray();
 
        if (invokeMethods.Length > 1)
        {
          throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
        }
 
        if (invokeMethods.Length == 0)
        {
          throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
        }
 
        var methodInfo = invokeMethods[0];
        if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
        {
          throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
        }
 
        var parameters = methodInfo.GetParameters();
        if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
        {
          throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
        }
 
        var ctorArgs = new object[args.Length + 1];
        ctorArgs[0] = next;
        Array.Copy(args, 0, ctorArgs, 1, args.Length);
        // When a middleware instance is formed by reflection, the first parameter of the constructor is specified as the execution delegate of the next Middleware
        var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
        if (parameters.Length == 1)
        {
          return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
        }
        
        // In addition to specifying HttpContext parameters, the current execution delegate can also inject more dependent parameters.  
        var factory = Compile<object>(methodInfo, parameters);
 
        return context =>                 
        {
          var serviceProvider = context.RequestServices ?? applicationServices;
          if (serviceProvider == null)
          {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
          }
 
          return factory(instance, context, serviceProvider);
        };
      });
    }
 
// Excerpt from Microsoft. AspNetCore. Builder. Internal. Application Builder
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
 
publicIApplicationBuilder Use(Func<RequestDelegate,RequestDelegate> middleware)
{
  this._components.Add(middleware);
  return this;
}
 
public RequestDelegate Build()
{
   RequestDelegate app = context =>
   {
     context.Response.StatusCode = 404;
     return Task.CompletedTask;
   };
 
   foreach (var component in _components.Reverse())
  {
    app = component(app);
  }
 
  return app;
}

From the above code, we can see that:

  • The process of registering middleware is actually the process of adding elements to a container of Type= List < Func < Request Delegate, Request Delegate >.
  • Each element in the container corresponds to the behavior delegation Func < Request Delegate, Request Delegate> of each middleware. This behavior delegation contains two key behaviors: input the execution delegate of the next middleware next: Request Delegate, complete the Invoke function of the current middleware: Request Delegate;
  • Chain-based value transfer of middleware before and after implementation by build method

Analyzing Source Code: Answer the above questions:

  1. When using reflection to construct middleware, the first parameter Array [0] is the execution delegation of the next Middleware
  2. Currently, the name of the middleware execution delegate function is limited to: Invoke/InvokeAsync, which supports passing in parameters other than HttpContext.
  3. Add in the _components container in code order, and establish a chain relationship through the execution delegation of the latter Middleware – (pointing) – > the input execution delegation of the former middleware.

Appendix: Use of non-standard Middleware

Short circuit middleware, bifurcated middleware, conditional Middleware

There are some behaviors of pipeline bifurcation or temporary insertion of Middleware in the process of pipeline formation. Some important methods can be used.

  • Use method is a simple way to write registered Middleware
  • The Run method is a convention, and some middleware uses the Run method to complete the end of the pipeline.
  • Map Extension Method: Requests to satisfy specified paths will execute bifurcated pipelines, emphasizing path satisfaction
  • MapWhen method: HttpContext satisfies the condition that the bifurcation pipeline will be executed:
  • UseWhen method: HttpContext inserts middleware if it satisfies the condition

The above is the whole content of this article. I hope it will be helpful to everyone’s study, and I hope you will support developpaer more.