Tag Archives: Middleware

Middleware in ASP .NET Core 3.1

By Shahed C on March 30, 2020

This is the thirteenth of a new series of posts on ASP .NET Core 3.1 for 2020. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2020, titled ASP .NET Core A-Z! To differentiate from the 2019 series, the 2020 series will mostly focus on a growing single codebase (NetLearner!) instead of new unrelated code snippets week.

Previous post:

NetLearner on GitHub:

In this Article:

M is for Middleware in ASP .NET Core

If you’ve been following my blog series (or if you’ve done any work with ASP .NET Core at all), you’ve already worked with the Middleware pipeline. When you create a new project using one of the built-in templates, your project is already supplied with a few calls to add/configure middleware services and then use them. This is accomplished by adding the calls to the Startup.cs configuration methods.

ASP .NET Core 3.1  Middleware Pipeline
ASP .NET Core 3.1 Middleware Pipeline

The above diagram illustrates the typical order of middleware layers in an ASP .NET Core web application. The order is very important, so it is necessary to understand the placement of each request delegate in the pipeline.

  • Exception Handling
  • HTTPS Redirection
  • Static Files
  • Cookie Policy
  • Routing
  • Authentication
  • Authorization
  • Session
  • Endpoint Routing

How It Works

When an HTTP request comes in, the first request delegate handles that request. It can either pass the request down to the next in line or short-circuit the pipeline by preventing the request from propagating further. This is use very useful across multiple scenarios, e.g. serving static files without the need for authentication, handling exceptions before anything else, etc.

The returned response travels back in the reverse direction back through the pipeline. This allows each component to run code both times: when the request arrives and also when the response is on its way out.

Here’s what the Configure() method may look like, in a template-generated Startup.cs file:

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

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

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseSession(); // manually added

    app.UseEndpoints(endpoints =>
    {
        // map MVC routes, Razor Pages, Blazor Hub
    });
}

You may add calls to UseCookiePolicy() and UseSession()  manually, as these aren’t included in the template-generated project. The endpoint configuration will vary depending on the type of project template you start with: MVC, Razor Pages or Blazor, which you can mix and match. You may also enable authentication and HTTPS when creating the template.

Adding a new project in VS2019

In order to configure the use of the Session middleware, you may add the following code in your ConfigureServices() method:

public void ConfigureServices(IServiceCollection services)
{
...
    services.AddDistributedMemoryCache();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromSeconds(10);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
...
}

The calls to AddDistributedMemoryCache() and AddSession() ensure that we have enabled a (memory cache) backing store for the session and then prepared the Session middleware for use.

Built-In Middleware

The information below explains how the built-in middleware works, and why the order is important. The UseXYZ() methods are merely extension methods that are prefixed with the word “Use” as a useful convention, making it easy to discover Middleware components when typing code. Keep this in mind mind when developing custom middleware.

Exception Handling:

  • UseDeveloperExceptionPage() & UseDatabaseErrorPage(): used in development to catch run-time exceptions
  • UseExceptionHandler(): used in production for run-time exceptions

Calling these methods first ensures that exceptions are caught in any of the middleware components that follow. For more information, check out the detailed post on Handling Errors in ASP .NET Core, earlier in this series.

HSTS & HTTPS Redirection:

  • UseHsts(): used in production to enable HSTS (HTTP Strict Transport Security Protocol) and enforce HTTPS.
  • UseHttpsRedirection(): forces HTTP calls to automatically redirect to equivalent HTTPS addresses.

Calling these methods next ensure that HTTPS can be enforced before resources are served from a web browser. For more information, check out the detailed post on Protocols in ASP .NET Core: HTTPS and HTTP/2.

Static Files:

  • UseStaticFiles(): used to enable static files, such as HTML, JavaScript, CSS and graphics files. Called early on to avoid the need for authentication, session or MVC middleware.

Calling this before authentication ensures that static files can be served quickly without unnecessarily triggering authentication middleware. For more information, check out the detailed post on JavaScript, CSS, HTML & Other Static Files in ASP .NET Core.

Cookie Policy:

  • UseCookiePolicy(): used to enforce cookie policy and display GDPR-friendly messaging

Calling this before the next set of middleware ensures that the calls that follow can make use of cookies if consented. For more information, check out the detailed post on Cookies and Consent in ASP .NET Core.

Authentication, Authorization & Sessions:

  • UseAuthentication(): used to enable authentication and then subsequently allow authorization.
  • UseSession(): manually added to the Startup file to enable the Session middleware.

Calling these after cookie authentication (but before the endpoint routing middleware) ensures that cookies can be issued as necessary and that the user can be authenticated before the endpoint routing kicks in. For more information, check out the detailed post on Authentication & Authorization in ASP .NET Core.

Endpoint Routing:

  • UseEndpoints(): usage varies based on project templates used for MVC, Razor Pages and Blazor.
  • endpoints.MapControllerRoute(): set the default route and any custom routes when using MVC.
  • endpoints.MapRazorPages(): sets up default Razor Pages routing behavior
  • endpoints.BlazorHub(): sets up Blazor Hub

To create your own custom middleware, check out the official docs at:

Branching out with Run, Map & Use

The so-called “request delegates” are made possible with the implementation of three types of extension methods:

  • Run: enables short-circuiting with a terminal middleware delegate
  • Map: allows branching of the pipeline path
  • Use: call the next desired middleware delegate

If you’ve tried out the absolute basic example of an ASP .NET Core application (generated from the Empty template), you may have seen the following syntax in your Startup.cs file.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseEndpoints(endpoints => 
    {
        endpoints.MapGet("/", async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
     }); 
}

You can use an alternative approach to terminate the pipeline, by calling app.Use() and then triggering the next middleware component with a call to next(context) as shown below. If there is no call to next(), then it essentially short-circuits the middleware pipeline.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    // Approach 1: terminal middleware
    app.Use(next => async context =>
    {
        if (context.Request.Path == "/")
        {
            await context.Response.WriteAsync("Hello and goodbye!");
            return;
        }
        await next(context);
    });
    app.UseRouting();
    app.UseEndpoints(...); // Approach 2: routing
}

Finally, there is the Map() method, which creates separate forked paths/branches for your middleware pipeline and multiple terminating ends. Check out the official documentation on mapped branches for more information:

In the Configure() method, each call to app.Map() establishes a separate branch that can be triggered with the appropriate relative path. Each handler method then contains its own terminating map.Run() method.

In the official sample:

  • accessing /map1 in an HTTP request will call HandleMapTest1()
  • accessing /map2 in an HTTP request will call HandleMapTest2()
  • accessing the / root of the web application will call the default terminating Run() method
  • specifying an invalid path will also call the default Run() method

References

Middleware in ASP .NET Core

By Shahed C on April 3, 2019

This is the thirteenth of a series of posts on ASP .NET Core in 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

M is for Middleware in ASP .NET Core

If you’ve been following my blog series (or if you’ve done any work with ASP .NET Core at all), you’ve already worked with the Middleware pipeline. When you create a new project using one of the built-in templates, your project is already supplied with a few calls to add/configure middleware services and then use them. This is accomplished by adding the calls to the Startup.cs configuration methods.

Blog-Diagram-Middleware-Pipeline

 

The above diagram illustrates the typical order of middleware layers in an ASP .NET Core web application. The order is very important, so it is necessary to understand the placement of each request delegate in the pipeline.

To follow along, take a look at the sample project on Github:

Web Middleware Sample: https://github.com/shahedc/AspNetCoreMiddlewareSample

How It Works

When an HTTP request comes in, the first request delegate handles that request. It can either pass the request down to the next in line or short-circuit the pipeline by preventing the request from propagating further. This is use very useful across multiple scenarios, e.g. serving static files without the need for authentication, handling exceptions before anything else, etc.

The returned response travels back in the reverse direction back through the pipeline. This allows each component to run code both times: when the request arrives and also when the response is on its way out.

Here’s what the Configure() method looks like, in a template-generated Startup.cs file:

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

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

    app.UseAuthentication();

    app.UseSession();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

I have added a call to UseSession()  to call the Session Middleware, as it wasn’t included in the template-generated project. I also enabled authentication and HTTPS when creating the template.

VS2017-Project-New

 

In order to configure the use of the Session middleware, I also had to add the following code in my ConfigureServices() method:

public void ConfigureServices(IServiceCollection services)
{
...
    services.AddDistributedMemoryCache();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromSeconds(10);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });

    services.AddMvc();

...

}

The calls to AddDistributedMemoryCache() and AddSession() ensure that we have enabled a (memory cache) backing store for the session and then prepared the Session middleware for use. In the Razor Pages version of Startup.cs, the Configure() method is a little simpler, as it doesn’t need to set up MVC routes for controllers in the call to useMvc().

Built-In Middleware

The information below explains how the built-in middleware works, and why the order is important. The UseXYZ() methods are merely extension methods that are prefixed with the word “Use” as a useful convention, making it easy to discover Middleware components when typing code. Keep this in mind mind when developing custom middleware.

Exception Handling:

  • UseDeveloperExceptionPage() & UseDatabaseErrorPage(): used in development to catch run-time exceptions
  • UseExceptionHandler(): used in production for run-time exceptions

Calling these methods first ensures that exceptions are caught in any of the middleware components that follow. For more information, check out the detailed post on Handling Errors in ASP .NET Core, earlier in this series.

HSTS & HTTPS Redirection:

  • UseHsts(): used in production to enable HSTS (HTTP Strict Transport Security Protocol) and enforce HTTPS.
  • UseHttpsRedirection(): forces HTTP calls to automatically redirect to equivalent HTTPS addresses.

Calling these methods next ensure that HTTPS can be enforced before resources are served from a web browser. For more information, check out the detailed post on Protocols in ASP .NET Core: HTTPS and HTTP/2.

Static Files:

  • UseStaticFiles(): used to enable static files, such as HTML, JavaScript, CSS and graphics files. Called early on to avoid the need for authentication, session or MVC middleware.

Calling this before authentication ensures that static files can be served quickly without unnecessarily triggering authentication middleware. For more information, check out the detailed post on JavaScript, CSS, HTML & Other Static Files in ASP .NET Core.

Cookie Policy:

  • UseCookiePolicy(): used to enforce cookie policy and display GDPR-friendly messaging

Calling this before the next set of middleware ensures that the calls that follow can make use of cookies if consented. For more information, check out the detailed post on Cookies and Consent in ASP .NET Core.

Authentication, Authorization & Sessions:

  • UseAuthentication(): used to enable authentication and then subsequently allow authorization.
  • UseSession(): manually added to the Startup file to enable the Session middleware.

Calling these after cookie authentication (but before the MVC middleware) ensures that cookies can be issued as necessary and that the user can be authenticated before the MVC engine kicks in. For more information, check out the detailed post on Authentication & Authorization in ASP .NET Core.

MVC & Routing:

  • UseMvc(): enables the use of MVC in your web application, with the ability to customize routes for your MVC application and set other options.
  • routes.MapRoute(): set the default route and any custom routes when using MVC.

To create your own custom middleware, check out the official docs at:

Branching out with Run, Map & Use

The so-called “request delegates” are made possible with the implementation of three types of extension methods:

  • Run: enables short-circuiting with a terminal middleware delegate
  • Map: allows branching of the pipeline path
  • Use: call the next desired middleware delegate

If you’ve tried out the absolute basic example of an ASP .NET Core application (generated from the Empty template), you may have seen the following syntax in your Startup.cs file. Here, the app.Run() method terminates the pipeline, so you wouldn’t add any additional middleware calls after it.

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

The call to app.Use() can be used to trigger the next middleware component with a call to next.Invoke() as shown below:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        // ...
        await next.Invoke();
        // ...
    });

    app.Run(...);
}

If there is no call to next.Invoke(), then it essentially short-circuits the middleware pipeline. Finally, there is the Map() method, which creates separate forked paths/branches for your middleware pipeline and multiple terminating ends. The code snippet below shows the contents of a Startup.cs file with mapped branches:

// first branch handler
private static void HandleBranch1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch 1.");
    });
}

// second branch handler
private static void HandleBranch2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch 2.");
    });
}

public void Configure(IApplicationBuilder app)
{
    app.Map("/mappedBranch1", HandleBranch1);

    app.Map("/mappedBranch2", HandleBranch2);

    // default terminating Run() method
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Terminating Run() method.");
    });
}

In the Configure() method, each call to app.Map() establishes a separate branch that can be triggered with the appropriate relative path. Each handler method then contains its own terminating map.Run() method.

For example:

  • accessing /mappedBranch1 in an HTTP request will call HandleBranch1()
  • accessing /mappedBranch2 in an HTTP request will call HandleBranch2()
  • accessing the / root of the web application will call the default terminating Run() method
  • specifying an invalid path will also call the default Run() method

The screenshots below illustrate the results of the above scenarios:

 

Scenario 1: https://localhost:44391/mappedBranch1

Branch-1

Scenario 2: https://localhost:44391/mappedBranch2

Branch-2

Scenario 3: https://localhost:44391/

Branch-Root

Scenario 4: https://localhost:44391/RandomTextESDRFTGYHJ

Branch-Random

 

References

Hope you enjoyed learning about Middleware in ASP.NET Core. Here is some feedback I received via Twitter: “Learned so [much] about #ASPNetCore  middleware from this article. The templates should have comments in them with this stuff!”