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[