Middleware.md - brainchildservices/curriculum GitHub Wiki

SLIDE-1

ASP.NET Core Middleware

SLIDE-2

Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component:

  • Chooses whether to pass the request to the next component in the pipeline.
  • Can perform work before and after the next component in the pipeline.

Request delegates are used to build the request pipeline. The request delegates handle each HTTP request.

Request delegates are configured using Run, Map, and Use extension methods. An individual request delegate can be specified in-line as an anonymous method (called in-line middleware), or it can be defined in a reusable class. These reusable classes and in-line anonymous methods are middleware, also called middleware components. Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline. When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request.

Migrate HTTP handlers and modules to ASP.NET Core middleware explains the difference between request pipelines in ASP.NET Core and ASP.NET 4.x and provides additional middleware samples.

SLIDE-3

Create a middleware pipeline with IApplicationBuilder

The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other. The following diagram demonstrates the concept. The thread of execution follows the black arrows.

image

Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.

SLIDE-4

The simplest possible ASP.NET Core app sets up a single request delegate that handles all requests. This case doesn't include an actual request pipeline. Instead, a single anonymous function is called in response to every HTTP request.

  public class Startup
  {
      public void Configure(IApplicationBuilder app)
      {
          app.Run(async context =>
          {
              await context.Response.WriteAsync("Hello, World!");
         });
      }
  }

SLIDE-4(DOWNWARDS)

Chain multiple request delegates together with Use. The next parameter represents the next delegate in the pipeline. You can short-circuit the pipeline by not calling the next parameter. You can typically perform actions both before and after the next delegate, as the following example demonstrates:

  public class Startup
  {
      public void Configure(IApplicationBuilder app)
      {
          app.Use(async (context, next) =>
          {
              // Do work that doesn't write to the Response.
              await next.Invoke();
              // Do logging or other work that doesn't write to the Response.
          });

          app.Run(async context =>
          {
              await context.Response.WriteAsync("Hello from 2nd delegate.");
          });
      }
  }

SLIDE-4(DOWNWARDS)

When a delegate doesn't pass a request to the next delegate, it's called short-circuiting the request pipeline. Short-circuiting is often desirable because it avoids unnecessary work. For example, Static File Middleware can act as a terminal middleware by processing a request for a static file and short-circuiting the rest of the pipeline. Middleware added to the pipeline before the middleware that terminates further processing still processes code after their next.Invoke statements. However, see the following warning about attempting to write to a response that has already been sent.

Warning

Don't call next.Invoke after the response has been sent to the client. Changes to HttpResponse after the response has started throw an exception. For example, setting headers and a status code throw an exception. Writing to the response body after calling next:

  • May cause a protocol violation. For example, writing more than the stated Content-Length.
  • May corrupt the body format. For example, writing an HTML footer to a CSS file.

HasStarted is a useful hint to indicate if headers have been sent or the body has been written to.

SLIDE-4(DOWNWARDS)

Run delegates don't receive a next parameter. The first Run delegate is always terminal and terminates the pipeline. Run is a convention. Some middleware components may expose Run[Middleware] methods that run at the end of the pipeline:

  public class Startup
  {
      public void Configure(IApplicationBuilder app)
      {
          app.Use(async (context, next) =>
          {
              // Do work that doesn't write to the Response.
              await next.Invoke();
              // Do logging or other work that doesn't write to the Response.
          });

          app.Run(async context =>
          {
              await context.Response.WriteAsync("Hello from 2nd delegate.");
          });
      }
  }

In the preceding example, the Run delegate writes "Hello from 2nd delegate." to the response and then terminates the pipeline. If another Use or Run delegate is added after the Run delegate, it's not called.

SLIDE-5

Middleware order

The following diagram shows the complete request processing pipeline for ASP.NET Core MVC and Razor Pages apps. You can see how, in a typical app, existing middlewares are ordered and where custom middlewares are added. You have full control over how to reorder existing middlewares or inject new custom middlewares as necessary for your scenarios.

image

SLIDE-5(DOWNWARDS)

The Endpoint middleware in the preceding diagram executes the filter pipeline for the corresponding app type—MVC or Razor Pages.

image

The order that middleware components are added in the Startup.Configure method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.

SLIDE-6

The following Startup.Configure method adds security-related middleware components in the typical recommended order:

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();
          app.UseDatabaseErrorPage();
      }
      else
      {
          app.UseExceptionHandler("/Error");
          app.UseHsts();
      }

      app.UseHttpsRedirection();
      app.UseStaticFiles();
      // app.UseCookiePolicy();

      app.UseRouting();
      // app.UseRequestLocalization();
      // app.UseCors();

      app.UseAuthentication();
      app.UseAuthorization();
      // app.UseSession();
      // app.UseResponseCompression();
      // app.UseResponseCaching();

      app.UseEndpoints(endpoints =>
      {
          endpoints.MapRazorPages();
          endpoints.MapControllerRoute(
              name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");
      });
  }

SLIDE-6(DOWNWARDS)

In the preceding code:

  • Middleware that is not added when creating a new web app with individual users accounts is commented out.
  • Not every middleware needs to go in this exact order, but many do. For example:
    • UseCors, UseAuthentication, and UseAuthorization must go in the order shown.
    • UseCors currently must go before UseResponseCaching due to This Bug.

In some scenarios, middleware will have different ordering. For example, caching and compression ordering is scenario specific, and there's multiple valid orderings. For example:

  app.UseResponseCaching();
  app.UseResponseCompression();

With the preceding code, CPU could be saved by caching the compressed response, but you might end up caching multiple representations of a resource using different compression algorithms such as gzip or brotli.

The following ordering combines static files to allow caching compressed static files:

  app.UseResponseCaching();
  app.UseResponseCompression();
  app.UseStaticFiles();

SLIDE-7

The following Startup.Configure method adds middleware components for common app scenarios:

  1. Exception/error handling

    • When the app runs in the Development environment:
      • Developer Exception Page Middleware (UseDeveloperExceptionPage) reports app runtime errors.
      • Database Error Page Middleware reports database runtime errors.
    • When the app runs in the Production environment:
      • Exception Handler Middleware (UseExceptionHandler) catches exceptions thrown in the following middlewares.
      • HTTP Strict Transport Security Protocol (HSTS) Middleware (UseHsts) adds the Strict-Transport-Security header.
  2. HTTPS Redirection Middleware (UseHttpsRedirection) redirects HTTP requests to HTTPS.

  3. Static File Middleware (UseStaticFiles) returns static files and short-circuits further request processing.

  4. Cookie Policy Middleware (UseCookiePolicy) conforms the app to the EU General Data Protection Regulation (GDPR) regulations.

  5. Routing Middleware (UseRouting) to route requests.

  6. Authentication Middleware (UseAuthentication) attempts to authenticate the user before they're allowed access to secure resources.

  7. Authorization Middleware (UseAuthorization) authorizes a user to access secure resources.

  8. Session Middleware (UseSession) establishes and maintains session state. If the app uses session state, call Session Middleware after Cookie Policy Middleware and before MVC Middleware.

  9. Endpoint Routing Middleware (UseEndpoints with MapRazorPages) to add Razor Pages endpoints to the request pipeline.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
           app.UseExceptionHandler("/Error");
           app.UseHsts();
       }
    
       app.UseHttpsRedirection();
       app.UseStaticFiles();
       app.UseCookiePolicy();
       app.UseRouting();
       app.UseAuthentication();
       app.UseAuthorization();
       app.UseSession();
    
       app.UseEndpoints(endpoints =>
       {
           endpoints.MapRazorPages();
       });
    

    }

SLIDE-7(DOWNWARDS)

  • In the preceding example code, each middleware extension method is exposed on IApplicationBuilder through the Microsoft.AspNetCore.Builder namespace.

  • UseExceptionHandler is the first middleware component added to the pipeline. Therefore, the Exception Handler Middleware catches any exceptions that occur in later calls.

  • Static File Middleware is called early in the pipeline so that it can handle requests and short-circuit without going through the remaining components. The Static File Middleware provides no authorization checks. Any files served by Static File Middleware, including those under wwwroot, are publicly available. For an approach to secure static files, see Static files in ASP.NET Core.

  • If the request isn't handled by the Static File Middleware, it's passed on to the Authentication Middleware (UseAuthentication), which performs authentication. Authentication doesn't short-circuit unauthenticated requests. Although Authentication Middleware authenticates requests, authorization (and rejection) occurs only after MVC selects a specific Razor Page or MVC controller and action.

  • The following example demonstrates a middleware order where requests for static files are handled by Static File Middleware before Response Compression Middleware. Static files aren't compressed with this middleware order. The Razor Pages responses can be compressed.

    public void Configure(IApplicationBuilder app) { // Static files aren't compressed by Static File Middleware. app.UseStaticFiles();

       app.UseRouting();
    
       app.UseResponseCompression();
    
       app.UseEndpoints(endpoints =>
       {
           endpoints.MapRazorPages();
       });
    

    }

For Single Page Applications (SPAs), the SPA middleware UseSpaStaticFiles usually comes last in the middleware pipeline. The SPA middleware comes last:

  • To allow all other middlewares to respond to matching requests first.
  • To allow SPAs with client-side routing to run for all routes that are unrecognized by the server app.