温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么在Asp.net Core中利用Redis存储Session

发布时间:2021-03-31 15:51:17 来源:亿速云 阅读:247 作者:Leah 栏目:开发技术

怎么在Asp.net Core中利用Redis存储Session?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

前言

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', 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就成功实现了。


关于怎么在Asp.net Core中利用Redis存储Session问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI