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

1.1. 中间件原理

1.1.1. 什么是中间件

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

1.1.2. 中间件执行过程

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

1.1.3. 中间件的配置

中间件配置主要是用RunMapUse方法进行配置;简单的中间件可以直接使用匿名方法就可以搞定,如下代码:

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类里面需要做以下几个操作:

  1. 使用任何一个实现了IDistributedCache接口的服务来启用内存缓存,
  2. 设置AddSession回调,由于AddSession是在Microsoft.AspNetCore.Session包内实现的,所以必须在Nuget中添加Microsoft.AspNetCore.Session包
  3. 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.RedisStackExchange.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项目,直接在浏览器访问:http://localhost:5001

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

1.4. 总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 解析Asp.net Core中使用Session的方法

    前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Core中引用dll,以往我们引用DLL都是直接引用,在Core里这样是不行的,必须基于NuGet添加,或者基于project.json添加,然后保存VS会启动还原类库. 第二就是使用Session的问题,Core里使用Session需要添加Session类库. 添加Session 在你的项目上基于NuG

  • 详解Asp.net Core 使用Redis存储Session

    前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware). 对于Session来说褒贬不一,很多人直接说不要用,也有很多人在用,这个也没有绝对的这义,个人认为只要不影什么且又可以方便实现的东西是可以用的,现在不对可不可用做表态,我们只关心实现. 类库引用 这个相对于之前的.net是方便了不少,需要在project.json中的dependencies节点中添加如下内容: "StackExc

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

    1.1. 中间件原理 1.1.1. 什么是中间件 中间件是段代码用于处理请求和响应,通常多个中间件链接起来形成管道,由每个中间件自己来决定是否要调用下一个中间件. 1.1.2. 中间件执行过程 举一个示例来演示中间件的执行过程(分别有三个中间件:日志记录.权限验证和路由):当请求进入应用程序时,执行执行日志记录的中间件,它记录请求属性并调用链中的下一个中间件权限验证,如果权限验证通过则将控制权传递给下一个中间件,不通过则设置401 HTTP代码并返回响应,响应传递给日志中间件进行返回. 1.1.

  • 浅谈ASP.NET Core 中间件详解及项目实战

    前言 本篇文章是我们在开发自己的项目中实际使用的,比较贴合实际应用,算是对中间件的一个深入使用了,不是简单的Hello World. 中间件(Middleware)的作用 我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终到达我们写的代码中.那么中间件就是在应用程序管道中的一个组件,用来拦截请求过程进行一些其他处理和响应.中间件可以有很多个,每一个中间件都可以对管道中的请求进行拦截,它可以决定是否将请求转移给下一个中间件. asp.net

  • 浅谈ASP.NET Core 中jwt授权认证的流程原理

    1,快速实现授权验证 什么是 JWT ?为什么要用 JWT ?JWT 的组成? 这些百度可以直接找到,这里不再赘述. 实际上,只需要知道 JWT 认证模式是使用一段 Token 作为认证依据的手段. 我们看一下 Postman 设置 Token 的位置. 那么,如何使用 C# 的 HttpClient 访问一个 JWT 认证的 WebAPI 呢? 下面来创建一个 ASP.NET Core 项目,尝试添加 JWT 验证功能. 1.1 添加 JWT 服务配置 在 Startup.cs 的 Confi

  • 浅谈ASP.NET Core静态文件处理源码探究

    前言 静态文件(如 HTML.CSS.图像和 JavaScript)等是Web程序的重要组成部分.传统的ASP.NET项目一般都是部署在IIS上,IIS是一个功能非常强大的服务器平台,可以直接处理接收到的静态文件处理而不需要经过应用程序池处理,所以很多情况下对于静态文件的处理程序本身是无感知的.ASP.NET Core则不同,作为Server的Kestrel服务是宿主到程序上的,由宿主运行程序启动Server然后可以监听请求,所以通过程序我们直接可以处理静态文件相关.静态文件默认存储到项目的ww

  • 浅谈ASP.NET Core 2.0 中间件(译)

    问题 如何创建一个最简单的ASP.NET Core中间件? 答案 使用VS创建一个ASP.NET Core 2.0的空项目,注意Startup.cs中的Configure()方法: public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World! (Run)

  • 浅谈ASP.NET Core 2.0 带初始参数的中间件(译)

    问题 如何在ASP.NET Core 2.0向中间件传入初始参数? 答案 在一个空项目中,创建一个POCO(Plain Old CLR Object)来保存中间件所需的参数: public class GreetingOptions { public string GreetAt { get; set; } public string GreetTo { get; set; } } 添加一个中间件: public class GreetingMiddleware { private readon

  • 浅谈ASP.NET Core 2.0 布局页面(译)

    本文介绍了ASP.NET Core 2.0 布局页面,分享给大家,具体如下: 问题 如何在ASP.NET Core 2.0项目中共享可见元素.代码块和指令? 答案 新建一个空项目,首先添加GreetingService服务和UserViewModel模型: public interface IGreetingService { string Greet(string firstname, string surname); } public class GreetingService : IGre

  • 浅谈ASP.NET Core 2.0 部分视图(译)

    问题 如何在ASP.NET Core 2.0中使用部分视图来重用页面的公共部分? 答案 新建一个空项目,在Startup中添加MVC服务和中间件: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopmen

  • 浅谈ASP.Net Core WebApi几种版本控制对比

    一.版本控制的好处: (1)有助于及时推出功能, 而不会破坏现有系统. (2)它还可以帮助为选定的客户提供额外的功能. API 版本控制可以采用不同的方式进行控制,方法如下: (1)在 URL 中追加版本或作为查询字符串参数, (2)通过自定义标头和通过接受标头 在这篇文章中, 让我们来看看如何支持多个版本的 ASP.NET Core Web API. 一.创建asp.net core webapi 项目,引用NuGet包:Install-Package Microsoft.AspNetCore

  • 浅谈ASP.NET Core的几种托管方式

    Kestrel Kestrel 是一个跨平台的适用于 ASP.NET Core 的 Web 服务器,默认包括在 ASP.NET Core 项目模板中. Kestrel 支持以下方案: HTTPS 用于启用 WebSocket 的不透明升级 用于获得 Nginx 高性能的 Unix 套接字 HTTP/2(除 macOS† 以外) 可以单独使用 Kestrel,也可以将其与反向代理服务器 (如 Internet Information Services (IIS).Nginx 或 Apache)结合

随机推荐