Talking about ASP.NET Core middleware realizes distributed session

Time:2020-11-16

1.1. Middleware principle

1.1.1. What is middleware

Middleware is a piece of code used to process requests and responses. Usually, multiple middleware are linked together to form a pipeline. Each middleware decides whether to call the next middleware or not.

1.1.2. Middleware execution process

Take an example to demonstrate the execution process of Middleware (there are three middleware respectively: logging, permission verification and routing): when a request enters the application, the middleware that performs logging records records the request properties and calls the next middleware permission verification in the chain. If the permission verification is passed, the control right will be transferred to the next middleware. If the permission verification fails, the control right will be transferred to the next middleware Set 401 HTTP code and return the response, which is passed to the log middleware for return.

1.1.3. Configuration of Middleware

Middleware configuration mainly usesRunMapandUseMethod; simple middleware can be configured by using anonymous method directly, as shown in the following code:


app.Run(async (context,next) =>
    {
      await context.Response.WriteAsync("environment " + env);
      await next();
    });

If you want to reuse the middleware, you need to encapsulate it into a class for calling.

1.2. Dependency injection Middleware

In practical projects, middleware often needs to call methods of other objects. So you want to create dependencies between objects because ASP.NET Core’s built-in dependency injection system can create more elegant code when writing programs.

The first step is to register the class in the IOC containerStartupClassConfigureServicesMethod,ConfigureServicesThe method will be inConfigureMethod was previously executed. So that all dependencies are ready when using middleware.

Now there is a greeter class:


public class Greeter : IGreeter
{
  public string Greet()
  {
    return "Hello from Greeter!";
  }
}

public interface IGreeter
{
  string Greet();
}

The first step isConfigureServicesMethod


public void ConfigureServices(IServiceCollection services)
{
  services.AddTransient<IGreeter, Greeter>();
}

The author uses addtransient for registration, which creates a new instance of the class on each request. You can choose other methods: add singleton, add scoped, or simply add (all used behind the scenes). The whole Di system is described in the official document.

Once the dependencies are registered, they are ready to be used.IApplicationBuilderInstance allowed inConfigureThere is one of the methodsRequestServicesProperty is used to getGreeterexample. Because you’ve already registered thisIGreeterInterface, so there is no need to integrate middleware with specificGreeterImplementation.


app.Use(async (ctx, next) =>
  {
    IGreeter greeter = ctx.RequestServices.GetService<IGreeter>();
    await ctx.Response.WriteAsync(greeter.Greet());
    await next();
  });

IfGreeterClass has a parameterized constructor in which its dependencies must also be registeredConfigureServices

Middleware can easily resolve dependencies. You can add additional parameters to the middleware constructor:


public class MyMiddleware
{
  private readonly RequestDelegate _next;
  private readonly IGreeter _greeter;

  public MyMiddleware(RequestDelegate next, IGreeter greeter)
  {
    _next = next;
    greeter = greeter;
  }

  public async Task Invoke(HttpContext context)
  {
    await context.Response.WriteAsync(_greeter.Greet());
    await _next(context);
  }
}

Alternatively, you can add this dependency to the invoke method:


public async Task Invoke(HttpContext context, IGreeter greeter)
{
  await context.Response.WriteAsync(greeter.Greet());
  await _next(context);
}

If the di system knows the type of these parameters, they will be resolved automatically when the class is instantiated. It’s simple!

1.3. Cookies and session Middleware

1.3.1. Session

HTTP is a stateless protocol, and the web server treats each request as a separate request. The value of the user in the previous request is not saved.

Session status is ASP.NET Core provides a function that can save and store user data when users access network servers. The data in the request and the session in the server are stored in the server.

ASP.NET The core maintains session state through a cookie containing the session ID, which is carried with each request.

stayMicrosoft.AspNetCore.SessionThe middleware provided in the package is used to manage the session state. To enable session middleware, the following operations need to be done in startup class:

  1. Use any service that implements the idistributedcache interface to enable memory caching,
  2. Set addsession callback because addsession is in Microsoft.AspNetCore.Session Package, so it must be added in nuget Microsoft.AspNetCore.Session package
  3. Usesession callback

The specific example code is as follows:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddMvc();

    //Add a memory cache
    services.AddDistributedMemoryCache();

    services.AddSession(options =>
    {
      //Set 10 second session expiration to test
      options.IdleTimeout = TimeSpan.FromSeconds(10);
      options.Cookie.HttpOnly = true;
    });
  }

  public void Configure(IApplicationBuilder app)
  {
    app.UseSession();
    app.UseMvcWithDefaultRoute();
  }
}

Code aboveIdleTimeoutProperty is used to determine how long a session is discarded when the user has not operated. This property has nothing to do with cookie timeout, which is reset for each request through session middleware.

1.3.2. Save session to redis

Redis and SQL server are officially provided to implement distributed session methods. However, the efficiency of SQL server is far less efficient than redis for obtaining values by key / value. Therefore, the author selects redis as an example to realize distributed session.

Ready for redis

Since redis does not support windows at present, you should prepare a Linux operating system when you install redis. The system here is Ubuntu 16.04. Please refer to the official examples for download and installation methods.

After the installation is successful, start the redis service. If you see the following information, it means that redis is started successfully:

Related configuration

First, you need to install the package with nuget Microsoft.Extensions.Caching . redis, after the installation is successful, you can app.csproj You can see it in the file.

Add in the configure method app.UseSession (); then configure services to add redis service

public void ConfigureServices(IServiceCollection services){
  services.AddDistributedRedisCache(options=>{
    options.Configuration= "127.0.0.1"; // multiple redis servers: {redisip}: {redis port}, {redisip}: {redis port}
    options.InstanceName="sampleInstance";
  });
  services.AddMvc();
  services.AddSession();
}

In the above code, the author only uses one redis server for testing. In the actual project, multiple redis servers are required. The configuration method is as follows:options.Configuration= "Address 1: port, address 2: Port";,The default port 6379 is used instead of the port

Complete code

Startup.cs


using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching.Redis;
using Microsoft.Extensions.Caching.Distributed;

namespace app{  
  public class Startup{    
    public Startup(IConfiguration configuration)    
    {      
      Configuration = configuration;    
    }
    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services){           
      services.AddDistributedRedisCache(options =>{        
        options.Configuration = "127.0.0.1";        
        options.InstanceName = "sampleInstance";      
      });      
      services.AddMvc();      
      services.AddSession();    
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env){      
      if (env.IsDevelopment())
      {        
        app.UseDeveloperExceptionPage();      
      }      
      else      
      {        
        app.UseExceptionHandler("/Home/Error");      
      }
      app.UseSession();
      app.UseStaticFiles();
      app.UseMvc(routes =>{        
        routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");      
      });    
    }  
  }
}

HomeControler.cs


public class HomeController : Controller  
{    
  public IActionResult Index()    
  {      
    HttpContext.Session.Set("apptest",Encoding.UTF8.GetBytes("apptestvalue"));
    return View();    
  }
  public IActionResult ShowRedis()    
  {      
    byte[] temp;
    if(HttpContext.Session.TryGetValue("apptest",out temp))
    {        
      ViewData["Redis"]=Encoding.UTF8.GetString(temp);      
    }      
    return View();    
  }
}

The index page only does one thing to set the session value: “apptestvalue”, and the showredis page displays the session value.

ShowRedis.cshtml


Redis Session Value:ViewData["Redis"]

Demo results

Now start to run the page. First, go directly to the showredis page, and the session value is empty

When you click Set session value, return to the show redis page again, and the session value will be displayed

Seeing that apptestvalue represents that the session value has been saved in redis, how can we prove that the apptestvalue value is retrieved from redis? Now I’ll show you.

1.3.3. Realize distributed session

The session has been saved to redis, but we are not sure whether this value is really saved in redis or in the project memory. Therefore, we can share sessions in two different applications (or two different machines), that is, distributed sessions. Distributed means different machines and different applications, but there are always the following Even if every HTTP request carries the same cookie value.

The cause of this problem is on every machine ASP.NET The key of the core application is different, so there is no way to get the session data saved by the previous application; To solve this problem, the. Net core team provides Microsoft.AspNetCore.DataProtection . azurestorage and Microsoft.AspNetCore.DataProtection . redis package saves the key to azure or redis. Here, choose to save the key to redis.

utilize Microsoft.AspNetCore.DataProtection The persistkeystoredis overload method provided by the redis package saves the key to redis. So add adddataprotection() to the configureservices method


var redis = ConnectionMultiplexer.Connect("127.0.0.1:6379");
  services.AddDataProtection()
    .SetApplicationName("session_application_name")
    .PersistKeysToRedis(redis, "DataProtection-Keys");

Here’s how to implement a distributed session

Configuration steps

Create two projects at the same time, app1 and app2

add toMicrosoft.AspNetCore.DataProtection.RedisandStackExchange.Redis.StrongName package

Because on the same machine, ASP.NET The port of the core program is 5000 when it is started by default. Since app1 is already occupied, the startup port of app2 is set to 5001

Complete code

App1 project

Startup.cs


using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching.Redis;
using Microsoft.Extensions.Caching.Distributed;

namespace app1{  
  public class Startup{    
    public Startup(IConfiguration configuration)    
    {      
      Configuration = configuration;    
    }
    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services){
      var redis = ConnectionMultiplexer.Connect("127.0.0.1:6379");
      services.AddDataProtection()
        .SetApplicationName("session_application_name")
        .PersistKeysToRedis(redis, "DataProtection-Keys");     
      services.AddDistributedRedisCache(options =>{        
        options.Configuration = "127.0.0.1";        
        options.InstanceName = "sampleInstance";      
      });      
      services.AddMvc();      
      services.AddSession();    
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env){      
      if (env.IsDevelopment())
      {        
        app.UseDeveloperExceptionPage();      
      }      
      else      
      {        
        app.UseExceptionHandler("/Home/Error");      
      }
      app.UseSession();
      app.UseStaticFiles();
      app.UseMvc(routes =>{        
        routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");      
      });    
    }  
  }
}

HomeControler.cs


public class HomeController : Controller  
{    
  public IActionResult Index()    
  {      
    HttpContext.Session.Set("app1test",Encoding.UTF8.GetBytes("app1testvalue"));
    return View();    
  }
  public IActionResult ShowRedis()    
  {      
    byte[] temp;
    if(HttpContext.Session.TryGetValue("app1test",out temp))
    {        
      ViewData["Redis"]=Encoding.UTF8.GetString(temp);      
    }      
    return View();    
  }
}

ShowRedis.cshtml


Redis Session Value:ViewData["Redis"]

App2 project

Startup.cs
The configuration is the same as that of app1.

HomeControler.cs


public class HomeController : Controller  
{    
  public IActionResult Index()    
  {      
    byte[] temp;
    if(HttpContext.Session.TryGetValue("app1test",out temp))
    {        
      ViewData["Redis"]=Encoding.UTF8.GetString(temp);      
    } 
    return View();    
  }
}

Index.cshtml


ViewData["Redis"]

Operation effect

App1 project

Open the showredis page for the first time, and the session value is empty

Click setsessionvalue to return to the showredis page:

App2 project, directly access in the browser: http://localhost :5001

The above is an example of using redis to implement distributed session.

1.4. Summary

This section explains the operation principle and configuration process of middleware, the configuration of object dependency relationship between middleware and the configuration of session commonly used in projects. The actual code shows how to use middleware to realize distributed session.

The above is the whole content of this article, I hope to help you in your study, and I hope you can support developeppaer more.