Objective
The ASP. NET Core changed the previous closure, now open source and open, let's use Redis storage session to do a simple test, or middleware (middleware).
For the session is mixed, many people directly say do not use, there are many people in use, this also does not have absolute this righteousness, individual think as long as do not shadow what and can be convenient to realize the thing is can use, now not can be used as a gesture, we only care about the realization.
Class Library Reference
This is a lot easier than the previous. NET, and you need to add the following 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 realization, but the borrowing of https://github.com/aspnet/Caching/tree/dev/src/ Microsoft.Extensions.Caching.Redis code to implement, do not know why there is this class library, and now NuGet is not, in order to not affect the future upgrade my namespace also use Microsoft.Extensions.Caching.Redis
We can see that there are four classes in Microsoft, in fact we only need three, and the fourth one will be wrong:
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-e Xpiration (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, backend cache for use with multiple apps/services. _instance = _options. InstanceName?? String. Empty; } public byte[] Get (string key) {if (key = = null) {throw new Argument Nullexception (nameof (key)); } return Getandrefresh (key, getdata:true); } public asYnc task<byte[]> Getasync (string key) {if (key = = null) {throw new Arg Umentnullexception (nameof (key)); } return await Getandrefreshasync (key, getdata:true); } public void Set (string key, byte[] value, distributedcacheentryoptions options) {if (key = = Nu LL) {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 Argume Ntnullexception (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 = Con Nectionmultiplexer.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 is done with 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 the back results, even if they is all null. These operations would 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 is done with 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 the back results, even if they is all null. These operations would 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 Argumen Tnullexception (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? absoluteexpirat Ion, out TimeSpan? slidingexpiration) {absoluteexpiration = null; slidingexpiration = null; var absoluteexpirationticks = (long?) Results[0]; if (Absoluteexpirationticks.hasvalue &&amP Absoluteexpirationticks.value! = notpresent) {absoluteexpiration = new DateTimeOffset (absolutee Xpirationticks.value, TimeSpan.Zero); } var slidingexpirationticks = (long?) RESULTS[1]; if (slidingexpirationticks.hasvalue && slidingexpirationticks.value! = notpresent) {SLI Dingexpiration = 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 are 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 are 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.valu E-creationtime). TotalSeconds, Options. SlidingExpiration.Value.TotalSeconds); } else if (Absoluteexpiration.hasvalue) {return (long) (ABSOLUTEEXPIRATION.VALUE-CR Eationtime). 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 is 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, St Ring key, params string[] members) {var result = await cache. Scriptevaluateasync (Hmgetscript, new rediskey[] {key}, Getredismembers (mem bers)); Todo:error checking? Return (REdisvalue[]) result; } private static redisvalue[] Getredismembers (params string[] members) {var redismembers = new R Edisvalue[members. Length]; for (int i = 0; i < members. Length; i++) {Redismembers[i] = (redisvalue) members[i]; } return redismembers; } }}
Configure Enable session
We configureservices increase in startup
Services. Addsingleton<idistributedcache> ( serviceprovider = new Rediscache (new rediscacheoptions { Configuration = "192.168.178.141:6379", INSTANCENAME = "Sample:" }); Services. Addsession ();
Configure increase in Startup
App. Usesession (New Sessionoptions () {IdleTimeout = Timespan.fromminutes (30)});
By the end of this configuration, we can test if it's written in Redis.
Validation results
In the MVC project, let's 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 we found that the first time there is a "hello" timer word, after the refresh of the word Hello old timer, proof that the session success, and then look at the Redis see, there is value, such a distributed session has been successfully implemented.
For the above example I put the source on the: Https://github.com/hantianwei/Microsoft.Extensions.Caching.Redis
And also on the NuGet upload a copy, easy to use directly, Tianwei.Microsoft.Extensions.Caching.Redis, just ID added Tianwei The space name or the Microsoft.Extensions.Caching.Redis
From the above example we find that Microsoft is really open this time, which also means that if we use some classes are not comfortable or inappropriate when you can write self-extension
Redis Storage Session