Asp.net Core uses Redis to store sessions, asp. netredis

Source: Internet
Author: User

Asp.net Core uses Redis to store sessions, asp. netredis

Preface

Asp.net Core is now open-source and closed. Next we will use Redis to store sessions for a simple test, or middleware (middleware ).

For sessions, the comments are different. Many people directly say that they are not used, and many others are using it. This is not an absolute meaning, I personally think that everything that can be easily implemented is usable. Now I am not making a statement about the availability. We only care about implementation.

Class Library Reference

This is much more convenient than the previous. net. You need to add the following content to the dependencies node in project. json:

  "StackExchange.Redis": "1.1.604-alpha",  "Microsoft.AspNetCore.Session": "1.1.0-alpha1-21694"

Redis implementation

This is not my implementation, But I borrowed the class library I don't know why, but now NUGET is no longer available. In order not to affect the upgrade of my namespace in the future, Microsoft. Extensions. Caching. Redis is also used.

We can see that Microsoft has four classes here. In fact, we only need three classes, and the fourth class gets an error:

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;    }  }}

Enable Session

We added ConfigureServices in Startup.

services.AddSingleton<IDistributedCache>(        serviceProvider =>          new RedisCache(new RedisCacheOptions          {            Configuration = "192.168.178.141:6379",            InstanceName = "Sample:"          }));      services.AddSession();

Add Configure in Startup

app.UseSession(new SessionOptions() { IdleTimeout = TimeSpan.FromMinutes(30) });

After the configuration is complete, you can test whether the data is written to Redis.

Verification Result

In the Mvc project, we implement the following code:

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"));      }

Run the command and we found that the Hello First timer appeared for the First time, and the Hello old timer appeared after the refresh, proving that the Session was successful. Check Redis and check whether there is a value, this distributed Session is successfully implemented.

For the above instance, I put the source code in: demo download

Tianwei. Microsoft. Extensions. Caching. Redis, but the ID adds the Tianwei space name or Microsoft. Extensions. Caching. Redis

From the above examples, we found that Microsoft is truly open this time, which also means that if we use some classes that are not good or inappropriate, we can write our own extensions.

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.