1.1. 中间件原理
1.1.1. 什么是中间件
中间件是段代码用于处理请求和响应,通常多个中间件链接起来形成管道,由每个中间件自己来决定是否要调用下一个中间件。
1.1.2. 中间件执行过程
举一个示例来演示中间件的执行过程(分别有三个中间件:日志记录、权限验证和路由):当请求进入应用程序时,执行执行日志记录的中间件,它记录请求属性并调用链中的下一个中间件权限验证,如果权限验证通过则将控制权传递给下一个中间件,不通过则设置401 HTTP代码并返回响应,响应传递给日志中间件进行返回。
1.1.3. 中间件的配置
中间件配置主要是用Run
、Map
和Use
方法进行配置;简单的中间件可以直接使用匿名方法就可以搞定,如下代码:
1
2
3
4
5
|
app.Run(async (context,next) =>
{
await context.Response.WriteAsync( "environment " + env);
await next();
});
|
如果想重用中间件,就需要单独封装到一个类中进行调用。
1.2. 依赖注入中间件
在实际项目中,中间件往往需要调用其它对象的方法。所以要创建对象之间的依赖,由于ASP.NET Core 内置的依赖注入系统,写程序的时候可以创建更优雅的代码。
首先需要要在IOC容器中注册类,就是Startup
类中的ConfigureServices
方法中进行注册,ConfigureServices
方法会在Configure
方法之前被执行。以便在用中间件时所有依赖都准备好了。
现在有一个Greeter类:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Greeter : IGreeter
{
public string Greet()
{
return "Hello from Greeter!" ;
}
}
public interface IGreeter
{
string Greet();
}
|
第一步在ConfigureServices
方法中进行注册
1
2
3
4
|
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IGreeter, Greeter>();
}
|
笔者这里使用的是AddTransient进行注册,该方法在每次请求时创建该类的新实例。可以选择其它方法:AddSingleton,AddScoped或简单的Add(所有在幕后前使用)。整个DI系统在官方文档中有所描述。
在注册了依赖项后,就可以使用它们了。IApplicationBuilder
实例允许在Configure
方法中有一个RequestServices
属性用于获取Greeter
实例。由于已经注册了这个IGreeter
接口,所以不需要将中间件与具体的Greeter
实现相结合。
1
2
3
4
5
6
|
app.Use(async (ctx, next) =>
{
IGreeter greeter = ctx.RequestServices.GetService<IGreeter>();
await ctx.Response.WriteAsync(greeter.Greet());
await next();
});
|
如果Greeter
类有一个参数化的构造函数,它的依赖关系也必须在其中注册ConfigureServices
。
中间件可以很容易解决依赖关系。可以向中间件构造函数添加其他参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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方法中:
1
2
3
4
5
|
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回调
具体示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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服务
1
2
3
4
5
6
7
8
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
1
|
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()
1
2
3
4
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
1
|
Redis Session Value:ViewData["Redis"]
|
app2项目
Startup.cs
配置同app1配置一样。
HomeControler.cs
1
2
3
4
5
6
7
8
9
10
11
12
|
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
1
|
ViewData["Redis"]
|
运行效果
app1 项目
首次打开进入ShowRedis页面,Session值为空
点击SetSessionValue以后,再回到ShowRedis页面:
app2项目,直接在浏览器访问:http://localhost:5001
以上是用Redis实现分布式Session示例。
1.4. 总结
本节讲解了中间件的运行原理及配置过程,中间件之间对象依赖关系的配置和平时项目中常用到Session的配置问题。并在实际代码展示了怎样使用中间件实现分布式Session。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。