本文转自: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. 远程连接linux服务上的mysql

    如果有童鞋linux上还未安装mysql数据库可以参考我上一篇博客 (1)首先确保 linux服务上的 mysql 的3306端口是对外开放的 编辑 vi /etc/sysconfig/iptable ...

  2. java大数据批量处理实现方式

    1. 各批量方式对比 Mybatis与JDBC批量插入MySQL数据库性能测试及解决方案 2. 原理解析 1)MySql PreparedStatement executeBatch过慢问题 3. 工 ...

  3. 《Effective Java》——读后总结

    这本书在Java开发的行业里,颇有名气.今天总算是粗略的看完了…后面线程部分和序列化部分由于心浮气躁看的不仔细.这个月还剩下一周,慢慢总结消化. 1.静态工厂方法代替构造器 静态工厂方法有名称,能确切 ...

  4. 【转】asp.net中设置弹出谈话框的几种方式详解

    源地址:https://blog.csdn.net/zou15093087438/article/details/79637114 注:可用winform的MessageBox的方法,只需引用Syst ...

  5. luoguP2418 yyy loves OI IV

    https://www.luogu.org/problemnew/show/P2418 暴力 DP 做这题只有 30 分 考虑用线段树优化这个 DP 先处理一下整个房间都膜拜一个人的情况,然后将 1 ...

  6. postgresql编译安装与调试(一)

    因为最近组里的项目和postgresql有关,并且需要查看和调试源码,所以专门学习了一下如何安装和调试postgresql,此博文用来记录自己的安装和调试过程.安装环境是CentOS6(CentOS7 ...

  7. 创建React工程

    下载 main.jsBundle 包curl http://localhost:8081/index.ios.bundle -o main.jsbundle <!DOCTYPE html> ...

  8. 转载Java NIO中的Files类的使用

    Java NIO中的Files类(java.nio.file.Files)提供了多种操作文件系统中文件的方法. Files.exists() Files.exits()方法用来检查给定的Path在文件 ...

  9. Vuex 使用了 module 后的访问方法 ..

    如果 使用了  module 和 namespace state 数据:=>   this.$store.state.User.info  (user 是模块名字. info 是 state 里 ...

  10. rest-assured的日志使用介绍

    在许多测试用例当中,为了帮助我们创建正确的断言和发送正确的请求,打印出详细的响应和请求数据是非常有用的.为此我们可以使用rest-assured提供的预定义过滤器或者使用其中的一些快捷方法. 一.请求 ...