详解Asp.net Core 使用Redis存储Session
发布时间:2021-01-11 18:51:46 所属栏目:asp.Net 来源:互联网
导读:前言Asp.netCore改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware)。
|
前言 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实现 这里并非我实现,而是借用不知道为什么之前还有这个类库,而现在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',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,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,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,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,DataKey);
}
else
{
results = await _cache.HashMemberGetAsync(_instance + key,out sldExpr);
await RefreshAsync(key,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,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',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,params string[] members)
{
var result = await cache.ScriptEvaluateAsync(
HmGetScript,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 (编辑:十堰站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
相关内容
- asp.net-mvc-3 – 在使用Unity容器时为此对象异常定义的无参
- ASP.NET中TextBox使用Ajax控件显示日期不全的问题解决方法
- asp.net-mvc-3 – Orchard CMS DataAnnotations – 客户端验
- ASP.NET 2.0和4.0似乎在Forms身份验证中以不同方式处理根UR
- asp.net html控件的File控件实现多文件上传实例分享
- asp.net-core – 如何在ASP.NET 5中使用“旧”依赖项
- asp-classic – 如何使用AES在VBScript中进行加密?
- asp.net – 使用AJAX进行WCF调用
- 认证 – asp.net mvc 3:Page.User.IsInRole(“xy”)返回nu
- asp.net – 我可以重新发布或携带POST数据(如果是这样,我可
推荐文章
站长推荐
- asp.net-mvc – MVC Razor – 如何向自己提交表单
- asp.net-core – 加密ASP.Net Core中的连接字符串
- 如何在Asp.Net Mvc中进行Basecamp风格的账户?
- asp.net-mvc – 用于选择的KendoUI网格Ajax绑定参
- asp.net-mvc-3 – 具有最佳实践的示例N层ASP.NET
- asp.net-mvc-3 – 在使用Unity容器时为此对象异常
- asp.net – 应用程序池循环如何影响ASP Net会话状
- asp.net-mvc – ASP.net身份在删除外部帐户后停止
- ASP.NET成员资格 – 让用户使用以前的密码
- asp.net-mvc-2 – MVC源代码单例模式
热点阅读
