本文转自: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);
}