浅谈ASP.NET Core中间件实现分布式 Session
1.1. 中间件原理
1.1.1. 什么是中间件
中间件是段代码用于处理请求和响应,通常多个中间件链接起来形成管道,由每个中间件自己来决定是否要调用下一个中间件。
1.1.2. 中间件执行过程
举一个示例来演示中间件的执行过程(分别有三个中间件:日志记录、权限验证和路由):当请求进入应用程序时,执行执行日志记录的中间件,它记录请求属性并调用链中的下一个中间件权限验证,如果权限验证通过则将控制权传递给下一个中间件,不通过则设置401 http代码并返回响应,响应传递给日志中间件进行返回。
1.1.3. 中间件的配置
中间件配置主要是用run
、map
和use
方法进行配置;简单的中间件可以直接使用匿名方法就可以搞定,如下代码:
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<igreeter, greeter>(); }
笔者这里使用的是addtransient进行注册,该方法在每次请求时创建该类的新实例。可以选择其它方法:addsingleton,addscoped或简单的add(所有在幕后前使用)。整个di系统在官方文档中有所描述。
在注册了依赖项后,就可以使用它们了。iapplicationbuilder
实例允许在configure
方法中有一个requestservices
属性用于获取greeter
实例。由于已经注册了这个igreeter
接口,所以不需要将中间件与具体的greeter
实现相结合。
app.use(async (ctx, next) => { igreeter greeter = ctx.requestservices.getservice<igreeter>(); 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.azurestorage和microsoft.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.redis
和stackexchange.redis.strongname包
由于在同一台机器上,asp.net core程序默认启动的时候端口为5000,由于app1已经占用了,所以将app2的启端口设置为5001
完整代码
app1项目
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项目
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"]
运行效果
app1 项目
首次打开进入showredis页面,session值为空
点击setsessionvalue以后,再回到showredis页面:
app2项目,直接在浏览器访问:
以上是用redis实现分布式session示例。
1.4. 总结
本节讲解了中间件的运行原理及配置过程,中间件之间对象依赖关系的配置和平时项目中常用到session的配置问题。并在实际代码展示了怎样使用中间件实现分布式session。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
ASP.NET Core利用Jaeger实现分布式追踪详解
-
详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁
-
Asp.Net Core中基于Session的身份验证的实现
-
浅谈如何在ASP.NET Core中实现一个基础的身份认证
-
浅谈ASP.NET Core 中间件详解及项目实战
-
如何在ASP.Net Core使用分布式缓存的实现
-
ASP.NET Core 2.1 中的 HttpClientFactory (Part 3) 使用Handler实现传出请求中间件
-
ASP.NET Core中间件初始化的实现
-
ASP.NET Core的JWT的实现(中间件).md
-
详解ASP.NET Core 中基于工厂的中间件激活的实现方法