本文转自:http://www.cnblogs.com/hantianwei/p/5723959.html

前言

  Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware)。

  对于Session来说褒贬不一,很多人直接说不要用,也有很多人在用,这个也没有绝对的这义,个人认为只要不影什么且又可以方便实现的东西是可以用的,现在不对可不可用做表态,我们只关心实现。

类库引用

  这个相对于之前的.net是方便了不少,需要在project.json中的dependencies节点中添加如下内容:

    "StackExchange.Redis": "1.1.604-alpha",
"Microsoft.AspNetCore.Session": "1.1.0-alpha1-21694"

Redis实现

这里并非我实现,而是借用https://github.com/aspnet/Caching/tree/dev/src/Microsoft.Extensions.Caching.Redis代码来实现,不知道为什么之前还有这个类库,而现在NUGET止没有了,为了不影响日后升级我的命名空间也用 Microsoft.Extensions.Caching.Redis

可以看到微软这里有四个类,其实我们只需要三个,第四个拿过来反而会出错:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using StackExchange.Redis; namespace Microsoft.Extensions.Caching.Redis
{
public class RedisCache : IDistributedCache, IDisposable
{
// KEYS[1] = = key
// ARGV[1] = absolute-expiration - ticks as long (-1 for none)
// ARGV[2] = sliding-expiration - ticks as long (-1 for none)
// ARGV[3] = relative-expiration (long, in seconds, -1 for none) - Min(absolute-expiration - Now, sliding-expiration)
// ARGV[4] = data - byte[]
// this order should not change LUA script depends on it
private const string SetScript = (@"
redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
return 1");
private const string AbsoluteExpirationKey = "absexp";
private const string SlidingExpirationKey = "sldexp";
private const string DataKey = "data";
private const long NotPresent = -1; private ConnectionMultiplexer _connection;
private IDatabase _cache; private readonly RedisCacheOptions _options;
private readonly string _instance; public RedisCache(IOptions<RedisCacheOptions> optionsAccessor)
{
if (optionsAccessor == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
} _options = optionsAccessor.Value; // This allows partitioning a single backend cache for use with multiple apps/services.
_instance = _options.InstanceName ?? string.Empty;
} public byte[] Get(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} return GetAndRefresh(key, getData: true);
} public async Task<byte[]> GetAsync(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} return await GetAndRefreshAsync(key, getData: true);
} public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} if (value == null)
{
throw new ArgumentNullException(nameof(value));
} if (options == null)
{
throw new ArgumentNullException(nameof(options));
} Connect(); var creationTime = DateTimeOffset.UtcNow; var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
value
});
} public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} if (value == null)
{
throw new ArgumentNullException(nameof(value));
} if (options == null)
{
throw new ArgumentNullException(nameof(options));
} await ConnectAsync(); var creationTime = DateTimeOffset.UtcNow; var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
value
});
} public void Refresh(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} GetAndRefresh(key, getData: false);
} public async Task RefreshAsync(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} await GetAndRefreshAsync(key, getData: false);
} private void Connect()
{
if (_connection == null)
{
_connection = ConnectionMultiplexer.Connect(_options.Configuration);
_cache = _connection.GetDatabase();
}
} private async Task ConnectAsync()
{
if (_connection == null)
{
_connection = await ConnectionMultiplexer.ConnectAsync(_options.Configuration);
_cache = _connection.GetDatabase();
}
} private byte[] GetAndRefresh(string key, bool getData)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} Connect(); // This also resets the LRU status as desired.
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
RedisValue[] results;
if (getData)
{
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
}
else
{
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
} // TODO: Error handling
if (results.Length >= 2)
{
// Note we always get back two results, even if they are all null.
// These operations will no-op in the null scenario.
DateTimeOffset? absExpr;
TimeSpan? sldExpr;
MapMetadata(results, out absExpr, out sldExpr);
Refresh(key, absExpr, sldExpr);
} if (results.Length >= 3 && results[2].HasValue)
{
return results[2];
} return null;
} private async Task<byte[]> GetAndRefreshAsync(string key, bool getData)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} await ConnectAsync(); // This also resets the LRU status as desired.
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
RedisValue[] results;
if (getData)
{
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
}
else
{
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
} // TODO: Error handling
if (results.Length >= 2)
{
// Note we always get back two results, even if they are all null.
// These operations will no-op in the null scenario.
DateTimeOffset? absExpr;
TimeSpan? sldExpr;
MapMetadata(results, out absExpr, out sldExpr);
await RefreshAsync(key, absExpr, sldExpr);
} if (results.Length >= 3 && results[2].HasValue)
{
return results[2];
} return null;
} public void Remove(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} Connect(); _cache.KeyDelete(_instance + key);
// TODO: Error handling
} public async Task RemoveAsync(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} await ConnectAsync(); await _cache.KeyDeleteAsync(_instance + key);
// TODO: Error handling
} private void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration)
{
absoluteExpiration = null;
slidingExpiration = null;
var absoluteExpirationTicks = (long?)results[0];
if (absoluteExpirationTicks.HasValue && absoluteExpirationTicks.Value != NotPresent)
{
absoluteExpiration = new DateTimeOffset(absoluteExpirationTicks.Value, TimeSpan.Zero);
}
var slidingExpirationTicks = (long?)results[1];
if (slidingExpirationTicks.HasValue && slidingExpirationTicks.Value != NotPresent)
{
slidingExpiration = new TimeSpan(slidingExpirationTicks.Value);
}
} private void Refresh(string key, DateTimeOffset? absExpr, TimeSpan? sldExpr)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} // Note Refresh has no effect if there is just an absolute expiration (or neither).
TimeSpan? expr = null;
if (sldExpr.HasValue)
{
if (absExpr.HasValue)
{
var relExpr = absExpr.Value - DateTimeOffset.Now;
expr = relExpr <= sldExpr.Value ? relExpr : sldExpr;
}
else
{
expr = sldExpr;
}
_cache.KeyExpire(_instance + key, expr);
// TODO: Error handling
}
} private async Task RefreshAsync(string key, DateTimeOffset? absExpr, TimeSpan? sldExpr)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
} // Note Refresh has no effect if there is just an absolute expiration (or neither).
TimeSpan? expr = null;
if (sldExpr.HasValue)
{
if (absExpr.HasValue)
{
var relExpr = absExpr.Value - DateTimeOffset.Now;
expr = relExpr <= sldExpr.Value ? relExpr : sldExpr;
}
else
{
expr = sldExpr;
}
await _cache.KeyExpireAsync(_instance + key, expr);
// TODO: Error handling
}
} private static long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options)
{
if (absoluteExpiration.HasValue && options.SlidingExpiration.HasValue)
{
return (long)Math.Min(
(absoluteExpiration.Value - creationTime).TotalSeconds,
options.SlidingExpiration.Value.TotalSeconds);
}
else if (absoluteExpiration.HasValue)
{
return (long)(absoluteExpiration.Value - creationTime).TotalSeconds;
}
else if (options.SlidingExpiration.HasValue)
{
return (long)options.SlidingExpiration.Value.TotalSeconds;
}
return null;
} private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options)
{
if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration <= creationTime)
{
throw new ArgumentOutOfRangeException(
nameof(DistributedCacheEntryOptions.AbsoluteExpiration),
options.AbsoluteExpiration.Value,
"The absolute expiration value must be in the future.");
}
var absoluteExpiration = options.AbsoluteExpiration;
if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
absoluteExpiration = creationTime + options.AbsoluteExpirationRelativeToNow;
} return absoluteExpiration;
} public void Dispose()
{
if (_connection != null)
{
_connection.Close();
}
}
}
}
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Caching.Redis
{
/// <summary>
/// Configuration options for <see cref="RedisCache"/>.
/// </summary>
public class RedisCacheOptions : IOptions<RedisCacheOptions>
{
/// <summary>
/// The configuration used to connect to Redis.
/// </summary>
public string Configuration { get; set; } /// <summary>
/// The Redis instance name.
/// </summary>
public string InstanceName { get; set; } RedisCacheOptions IOptions<RedisCacheOptions>.Value
{
get { return this; }
}
}
}
using System.Threading.Tasks;
using StackExchange.Redis; namespace Microsoft.Extensions.Caching.Redis
{
internal static class RedisExtensions
{
private const string HmGetScript = (@"return redis.call('HMGET', KEYS[1], unpack(ARGV))"); internal static RedisValue[] HashMemberGet(this IDatabase cache, string key, params string[] members)
{
var result = cache.ScriptEvaluate(
HmGetScript,
new RedisKey[] { key },
GetRedisMembers(members)); // TODO: Error checking?
return (RedisValue[])result;
} internal static async Task<RedisValue[]> HashMemberGetAsync(
this IDatabase cache,
string key,
params string[] members)
{
var result = await cache.ScriptEvaluateAsync(
HmGetScript,
new RedisKey[] { key },
GetRedisMembers(members)); // TODO: Error checking?
return (RedisValue[])result;
} private static RedisValue[] GetRedisMembers(params string[] members)
{
var redisMembers = new RedisValue[members.Length];
for (int i = 0; i < members.Length; i++)
{
redisMembers[i] = (RedisValue)members[i];
} return redisMembers;
}
}
}

配置启用Session

我们在Startup中ConfigureServices增加

          services.AddSingleton<IDistributedCache>(
serviceProvider =>
new RedisCache(new RedisCacheOptions
{
Configuration = "192.168.178.141:6379",
InstanceName = "Sample:"
}));
services.AddSession();

在Startup中Configure增加

app.UseSession(new SessionOptions() { IdleTimeout = TimeSpan.FromMinutes(30) });

到此我们的配置完毕,可以测试一下是否写到了Redis中

验证结果

在Mvc项目中,我们来实现如下代码

            if (string.IsNullOrEmpty(HttpContext.Session.GetString("D")))
{
var d = DateTime.Now.ToString();
HttpContext.Session.SetString("D", d);
HttpContext.Response.ContentType = "text/plain";
await HttpContext.Response.WriteAsync("Hello First timer///" + d);
}
else
{
HttpContext.Response.ContentType = "text/plain";
await HttpContext.Response.WriteAsync("Hello old timer///" + HttpContext.Session.GetString("D"));
}

运行我们发现第一次出现了Hello First timer字样,刷新后出现了Hello old timer字样,证明Session成功,再查看一下Redis看一下,有值了,这样一个分布式的Session就成功实现了。

对于上面的实例我把源码放在了:https://github.com/hantianwei/Microsoft.Extensions.Caching.Redis

且也在Nuget上上传了一份,方便直接使用,Tianwei.Microsoft.Extensions.Caching.Redis ,只是ID加了Tianwei 空间名还是Microsoft.Extensions.Caching.Redis

从上面的实例我们发现微软这次是真的开放了,这也意味着如果我们使用某些类不顺手或不合适时可以自已写自已扩展

 
分类: .NET
标签: DotnetCore

[转]Asp.net Core 使用Redis存储Session的更多相关文章

  1. Asp.net Core 使用Redis存储Session

    前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware). 对于Session来说褒贬不一, ...

  2. Redis存储Session

    net Core 使用Redis存储Session   前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(m ...

  3. asp.net core 使用 Redis 和 Protobuf

    asp.net core 使用 Redis 和 Protobuf 前言 上篇博文介绍了怎么样在 asp.net core 中使用中间件,以及如何自定义中间件.项目中刚好也用到了Redis,所以本篇就介 ...

  4. ASP.NET Core中间件实现分布式 Session(转载)

    ASP.NET Core中间件实现分布式 Session 1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件 ...

  5. ASP.NET Core 使用 Redis 客户端

    Mac OS 安装 Redis(用于连 Redis 服务器,方便查看数据):https://redis.io/topics/quickstart wget http://download.redis. ...

  6. Tomcat 使用Redis存储Session

    Tomcat Redis Session Github 地址. 下载 commons-pool2-2.2.jar,jedis-2.5.2.jar,tomcat-redis-session-manage ...

  7. redis存储session配制方法

    redis存储session配制方法需要三个模块: 1.redis 2.express-session 3.connect-redis 项目中的配置方法代码片段如下: 首先连接redis,连接redi ...

  8. 几分钟搞定redis存储session共享——设计实现

    前面我们写过C#在redis中存储常用的5种数据类型demo,没看过的可以点击电梯直达:https://www.cnblogs.com/xiongze520/p/10267804.html 我们上一篇 ...

  9. Spring Boot+redis存储session,满足集群部署、分布式系统的session共享

    本文讲述spring-boot工程中使用spring-session机制进行安全认证,并且通过redis存储session,满足集群部署.分布式系统的session共享. 原文链接:https://w ...

随机推荐

  1. <select>标签默认值设置

    <td> <label>操作类型:</label> <select id="operation_type" class="com ...

  2. Linux 运维工程师经典面试题合集(不断更新中 ...)

    写在前面的话 做运维以来,关注了很多关于互联网技术,培训等乱七八糟的公众号,时不时的就会推一些各种公司的面试题过来. 大致看了一下,发现自己很多知识要么遗忘了,要么很难说出个所以然,所以这里对那些个人 ...

  3. CHOCBase

    1.基础常用知识点 1.1 监听某个对象变化的四种方式 代理监听Delegate Notification通知 KVO键值监听 Block代码块 addTarget方法 1.2 音频视频相册等 2.常 ...

  4. python-循环(while循环、for循环)

    循环:循环会重复执行循环体里面的代码,python中循环可分为while循环和for循环. break 不管循环有没有完成,立即结束循环 continue 结束本次循环,继续进行下一次循环 一.whi ...

  5. 【spring cloud】源码分析(一)

    概述 从服务发现注解 @EnableDiscoveryClient入手,剖析整个服务发现与注册过程 一,spring-cloud-common包 针对服务发现,本jar包定义了 DiscoveryCl ...

  6. [转][Java]使用Spring配合Junit进行单元测试的总结

    http://www.51testing.com/html/14/n-1408814.html 1.直接对spring中注入的bean进行测试(以DAO为例): 在测试类上添加@RunWith注解指定 ...

  7. npm 安装命令中的参数 --save-dev

    为项目安装模块的npm命令一般是—— npm i module-name 执行完毕后,需要手动把模块名称连同版本号一同的添加到模块配置文件package.json中的依赖里(dependencies) ...

  8. [转] 用Python建立最简单的web服务器

    [From] http://www.cnblogs.com/xuxn/archive/2011/02/14/build-simple-web-server-with-python.html 利用Pyt ...

  9. Axis 生成客户端client stub文件

    [转自] http://blog.csdn.net/qiao000_000/article/details/5568442 开发前,有个同事先给我们不熟悉Web Service的程序员进行了一些培训, ...

  10. 神马是代码简单的cmd模式,这就是!

    小狼正在研究 “怎么查找连在一起的同色方块?”算法问题 ,突然感觉我是不是需要一种开发模式,不然感觉自己的代码好乱的. 可能是研究算法吧,导致小狼的思路特别清晰,加上也用了差不多1年的nodejs.s ...