[转]Asp.net Core 使用Redis存储Session
本文转自: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
从上面的实例我们发现微软这次是真的开放了,这也意味着如果我们使用某些类不顺手或不合适时可以自已写自已扩展
[转]Asp.net Core 使用Redis存储Session的更多相关文章
- Asp.net Core 使用Redis存储Session
前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware). 对于Session来说褒贬不一, ...
- Redis存储Session
net Core 使用Redis存储Session 前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(m ...
- asp.net core 使用 Redis 和 Protobuf
asp.net core 使用 Redis 和 Protobuf 前言 上篇博文介绍了怎么样在 asp.net core 中使用中间件,以及如何自定义中间件.项目中刚好也用到了Redis,所以本篇就介 ...
- ASP.NET Core中间件实现分布式 Session(转载)
ASP.NET Core中间件实现分布式 Session 1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件 ...
- ASP.NET Core 使用 Redis 客户端
Mac OS 安装 Redis(用于连 Redis 服务器,方便查看数据):https://redis.io/topics/quickstart wget http://download.redis. ...
- Tomcat 使用Redis存储Session
Tomcat Redis Session Github 地址. 下载 commons-pool2-2.2.jar,jedis-2.5.2.jar,tomcat-redis-session-manage ...
- redis存储session配制方法
redis存储session配制方法需要三个模块: 1.redis 2.express-session 3.connect-redis 项目中的配置方法代码片段如下: 首先连接redis,连接redi ...
- 几分钟搞定redis存储session共享——设计实现
前面我们写过C#在redis中存储常用的5种数据类型demo,没看过的可以点击电梯直达:https://www.cnblogs.com/xiongze520/p/10267804.html 我们上一篇 ...
- Spring Boot+redis存储session,满足集群部署、分布式系统的session共享
本文讲述spring-boot工程中使用spring-session机制进行安全认证,并且通过redis存储session,满足集群部署.分布式系统的session共享. 原文链接:https://w ...
随机推荐
- 「BZOJ 2342」「SHOI 2011」双倍回文「Manacher」
题意 记\(s_R\)为\(s\)翻转后的串,求一个串最长的形如\(ss_Rss_R\)的子串长度 题解 这有一个复杂度明显\(O(n)\)的做法,思路来自网上某篇博客 一个双倍回文串肯定当且仅当本身 ...
- [SinGuLaRiTy] 2017 百度之星程序设计大赛 复赛
[SinGuLaRiTy-1038] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. Arithmetic of Bomb Problem D ...
- 洛谷P4207 [NOI2005]月下柠檬树(计算几何+自适应Simpson法)
题面 传送门 题解 我还好奇自适应辛普森法干嘛用的呢--突然想起来积分的一个用处就是求曲边图形的面积-- 我们先来考虑一下这些投影是什么形状 一个圆的投影还是它自己 一个圆锥的投影是一个圆加上一个点, ...
- 【python】Python任务调度模块 – APScheduler
APScheduler是一个Python定时任务框架,使用起来十分方便.提供了基于日期.固定时间间隔以及crontab类型的任务,并且可以持久化任务.并以daemon方式运行应用.目前最新版本为3.0 ...
- request对象常用方法
String getParameter(String name)根据表单组件名称获取提交数据 Sring[] getParameterValues(String name)获取表单组件对应多个值时的请 ...
- 百度分享,简单的一步操作解决你的网站不支持https访问的问题!
百度分享,应该是目前最好用的前端分享插件了.然而,官方却没有支持https.现在越来越多的网站都走入https的安全加密队列了,那么在找不到更好地替代品的情况下,怎么能让它支持https呢? 答案当然 ...
- ob系列函数归纳
输出控制函数(output control函数) flush — 刷新输出缓冲ob_clean — 清空(擦掉)输出缓冲区ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲ob_end_ ...
- Cboard 实现看板里面图表仪表盘之间的联动
首先要了解Cboard的概念, 1.看板 2.图表 看Cborad的菜单知道了,首先先设计图表,可以展现未柱状图或者其它图片形式或者数据表格式.然后看板可以由多个图表组合而成. 需求如下: 看板包含两 ...
- 添加win10激活工具
其实我们可以随便准备一个win10的激活密钥激活! 以管理员的身份进入到CMD下.. slmgr.vbs /upk 清除掉旧的slmgr /ipk W269N-WFGWX ...
- RabbitMQ 在Linux环境中的默认位置
参考:https://www.rabbitmq.com/relocate.html