ASP.NET Core中间件实现分布式 Session

ASP.NET Core中间件实现分布式 Session 发表时间:2017-11-03 09:31

点上方绿标即可收听朗读音频

双击文章内容从指定位置处朗读

1.1. 中间件原理

1.1.1. 什么是中间件

中间件是段代码用于处理请求和响应通常多个中间件链接起来形成管道由每个中间件自己来决定是否要调用下一个中间件

1.1.2. 中间件执行过程

举一个示例来演示中间件的执行过程(分别有三个中间件日志记录、权限验证和路由)当请求进入应用程序时执行执行日志记录的中间件它记录请求属性并调用链中的下一个中间件权限验证如果权限验证通过则将控制权传递给下一个中间件不通过则设置401 HTTP代码并返回响应响应传递给日志中间件进行返回

1.1.3. 中间件的配置

中间件配置主要是用RunMapUse方法进行配置三者的不同参见上篇ASP.NET Core 运行原理剖析简单的中间件可以直接使用匿名方法就可以搞定如下代码

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

如果想重用中间件就需要单独封装到一个类中进行调用

1.2. 依赖注入中间件

在实际项目中中间件往往需要调用其它对象的方法所以要创建对象之间的依赖,由于ASP.NET Core 内置的依赖注入系统写程序的时候可以创建更优雅的代码

首先需要要在IOC容器中注册类就是Startup类中的ConfigureServices方法中进行注册ConfigureServices方法会在Configure方法之前被执行以便在用中间件时所有依赖都准备好了

现在有一个Greeter类

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

第一步在ConfigureServices方法中进行注册

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

笔者这里使用的是AddTransient进行注册该方法在每次请求时创建该类的新实例可以选择其它方法AddSingletonAddScoped或简单的Add(所有在幕后前使用)整个DI系统在官方文档中有所描述

在注册了依赖项后就可以使用它们了IApplicationBuilder实例允许在Configure方法中有一个RequestServices属性用于获取Greeter实例由于已经注册了这个IGreeter接口所以不需要将中间件与具体的Greeter实现相结合

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

如果Greeter类有一个参数化的构造函数它的依赖关系也必须在其中注册ConfigureServices

中间件可以很容易解决依赖关系可以向中间件构造函数添加其他参数

 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); } } 

或者可以将此依赖关系添加到Invoke方法中

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

如果DI系统知道这些参数的类型则在类被实例化时它们将被自动解析很简单!

1.3. Cookies和session中间件

1.3.1. Session

HTTP是一个无状态协议Web服务器将每一个请求都视为独立请求并且不保存之前请求中用户的值

Session 状态是ASP.NET Core提供的一个功能它可以在用户通应用访问网络服务器的时候保存和存储用户数据由服务器上的字典和散列表组成Session状态通过浏览器的请求中得到Session的数据保存到缓存中

ASP.NET Core通过包含Session ID的Cookie来维护会话状态每个请求都会携带此Session ID

Microsoft.AspNetCore.Session包中提供的中间件用来管理Session状态要启用Session中间件Startup类里面需要做以下几个操作

  • 使用任何一个实现了IDistributedCache接口的服务来启用内存缓存
  • 设置AddSession回调由于AddSession是在Microsoft.AspNetCore.Session包内实现的所以必须在Nuget中添加Microsoft.AspNetCore.Session
  • UseSession回调

具体示例代码如下

 using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System; public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // 添加一个内存缓存 services.AddDistributedMemoryCache(); services.AddSession(options => { // 设置10秒钟Session过期来测试 options.IdleTimeout = TimeSpan.FromSeconds(10); options.Cookie.HttpOnly = true; }); } public void Configure(IApplicationBuilder app) { app.UseSession(); app.UseMvcWithDefaultRoute(); } }

上面代码中IdleTimeout属性用来确定用户多久没有操作时丢弃Session此属性和Cookie超时无关通过Session中间件的每个请求都会重置超时时间

1.3.2. Session保存到Redis中

实现分布式Session方法官方提供有Redis、Sql Server等但是Sql Server效率对于这种以key/value获取值的方式远远不及Redis效率高所以这里笔者选用Redis来作示例实现分布式Session

准备Redis

由于目前Redis还不支持windows所以大家在安装Redis的时候准备一台linux操作系统笔者这里的系统是ubuntu 16.04下载及安装方式可以参考官方示例

安装成功以后启动Redis 服务如果看到以下信息就代表Redis启动成功

相关配置

首先需要用Nuget安装包Microsoft.Extensions.Caching.Redis安装成功以后就可以在app.csproj文件中可以看到

Configure方法中添加app.UseSession()然后再ConfigureServices添加Redis服务

 public void ConfigureServices(IServiceCollection services){ services.AddDistributedRedisCache(options=>{ options.Configuration="127.0.0.1"; //多个redis服务器{RedisIP}:{Redis端口},{RedisIP}:{Redis端口} options.InstanceName="sampleInstance"; }); services.AddMvc(); services.AddSession(); } 

以上代码中笔者只用一个Redis服务器来作测试实际项目中需要多个Redis服务器配置方法如options.Configuration="地址1:端口,地址2:端口";这里笔者并没有给端口而是用的默认端口6379

完整代码

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(); } } 

Index页面只做一件事给Session设置值"apptestvalue"ShowRedis页面显示Session值

ShowRedis.cshtml

 Redis Session Value:ViewData["Redis"] 

演示结果

现在开始运行页面首先直接进入到ShowRedis页面Session值显示为空

当点击SetSessionValue以后再次回到ShowRedis页面Session就值显示出来了

看到apptestvalue代表Session值已经存到Redis里面怎样证明apptestvalue值是从Redis里面取到呢?接下来就证明给大家看

1.3.3. 实现分布Session

前面已经将Session保存到Redis中但是大家不清楚这个值是否是真的保存到Redis里面去了还是在项目内存中所以这里就实现在两个不的应用程序(或两台不同的机器)中共享Session也就是实现分布式Session分布式即代表了不同的机器不同的应用程序但往往有下面的一种尴尬的情况就算是每个HTTP请求时都携带了相同的cookie值

造成这个的问题的原因是每个机器上面的ASP.NET Core的应用程序的密钥是不一样的所以没有办法得到前一个应用程序保存的Session数据;为了解决这个问题,.NET Core团队为提供了Microsoft.AspNetCore.DataProtection.AzureStorageMicrosoft.AspNetCore.DataProtection.Redis包将密钥保存到Azure或Redis中这里选择将密钥保存到Redis

利用Microsoft.AspNetCore.DataProtection.Redis包提供的PersistKeysToRedis重载方法将密钥保存到Redis里面去所以这里需要在ConfigureServices方法中添AddDataProtection()

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

下面演示怎样实现分布式Session

配置步骤

  • 同时创建两个项目分别为app1和app2
  • 添加Microsoft.AspNetCore.DataProtection.RedisStackExchange.Redis.StrongName

  • 由于在同一台机器上ASP.NET Core程序默认启动的时候端口为5000由于app1已经占用了所以将app2的启端口设置为5001

完整代码

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"] 

Startup.cs
配置同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"] 

运行效果

首次打开进入ShowRedis页面Session值为空

点击SetSessionValue以后再回到ShowRedis页面

以上是用Redis实现分布式Session示例

1.4. 总结

本节讲解了中间件的运行原理及配置过程中间件之间对象依赖关系的配置和平时项目中常用到Session的配置问题并在实际代码展示了怎样使用中间件实现分布式Session

作者xdpie 出处http://www.cnblogs.com/vipyoumay/p/7771237.html

亲,眼睛太累了,关注exread(睿读吧)微信号,用耳朵“阅读”微信。

您可以将文章的链接或收藏的微信发送到睿读吧微信号中,我们会帮您转换成音频来听读,让您的眼睛休息一下吧!
查看来源 违规举报