Interpretation of ASP.net 5 & MVC6 Series (8): Session and Caching_ self-study process

Source: Internet
Author: User
Tags redis relative sessions static class

In the previous version, the session exists in the system.web, the new version of ASP.net 5 because it is not dependent on the System.Web.dll library, so the corresponding, the session has become a asp.net A configurable module (middleware) in 5.

Configure Enable session

The session module in ASP.net 5 exists in the Microsoft.AspNet.Session class library, and to enable session, you first need to add the following in the Dependencies node in Project.json:

"Microsoft.AspNet.Session": "1.0.0-beta3"

Then add a reference to the session in Configureservices (and configure it):

Services. Addcaching (); The two must be added at the same time because the session is dependent on caching
services. Addsession ();
Services. Configuresession (NULL); You can configure it here, or you can configure it later

Finally, in the Configure method, open the mode of using session, if the above has been configured, you can no longer pass the configuration information, or as the above configuration information, incoming session configuration information, the code is as follows:

App. Useinmemorysession (configure:s => {s.idletimeout = Timespan.fromminutes ();});
App. Usesession (o => {o.idletimeout = Timespan.fromseconds ();});
App. Useinmemorysession (null, NULL); Open memory session
//app. Usedistributedsession (null, NULL);/Open distributed session, that is, persistent session
//app. Usedistributedsession (New Rediscache (new Rediscacheoptions () {Configuration = "localhost"}));

For the Useinmemorysession method, receive 2 optional parameters: IMemoryCache The default save address that can be used to modify session data, and Action<SessionOptions> delegates can allow you to modify default options, such as the path of the session cookie, the default expiration time, and so on. In this case, we modify the default expiration time of 30 minutes.

Note: This method must be in app. Call before USEMVC, otherwise you will not get a session in MVC, and you'll get an error.

Get and set session

The

Gets and sets the session object, typically through the controller action this. Context.session To obtain an instance of an interface-based isessioncollection . This interface can be indexed, set, TryGetValue and other methods to get and set the session value, but we found that when we get and set the session, we can only use the byte[] type, You cannot set any type of data like previous versions of the session. The reason for this is that the new version of the session is required to support serialization in order to support storage on the remote server, so it is mandatory to save as byte[] type. So when we save the session, we need to convert it to byte[] before we can save it and get to convert byte[] to its original type again later. This form is too troublesome, fortunately Microsoft in the Microsoft.AspNet.Http namespace (which belongs to Microsoft.AspNet.Http.Extensions.dll ), We added several extension methods for setting and saving the byte[] type, int type, and the string type, with the following code:

public static byte[] Get (This isessioncollection session, string key);
public static int? GetInt (This isessioncollection session, string key);
public static string GetString (this isessioncollection session, string key);
public static void Set (this isessioncollection session, string key, byte[] value);
public static void Setint (this isessioncollection session, string key, int value);
public static void SetString (this isessioncollection session, string key, String value);

So, Controller after referencing the Microsoft.AspNet.Http namespace, we can set up and get the session through the following code:

Context.Session.SetString ("Name", "Mike");
Context.Session.SetInt ("Age",);

Viewbag.name = Context.Session.GetString ("Name");
Viewbag.age = Context.Session.GetInt ("Age");

Session settings and fetch for a custom type

As we said earlier, to save the session of a custom type, you need to convert the type to a byte[] array, in this case, we set up and get code for the session data of type bool, as shown in the following example:

public static class Sessionextensions
{public
 static bool? Getboolean (This isessioncollection sessions, string key)
 {
  var data = session. Get (key);
  if (data = = null)
  {return
   null;
  }
  return Bitconverter.toboolean (data, 0);
 } 

 public static void SetBoolean (this isessioncollection session, string key, bool value)
 {session
  . Set (Key, Bitconverter.getbytes (value));
 }

After defining an extension method of type bool, we can use it like Setint/getint, as shown in the following example:

Context.Session.SetBoolean ("Liar", true);
Viewbag.liar = Context.Session.GetBoolean ("Liar");

Additionally, remove (string key) and clear () Two methods are available on the Isessioncollection interface to remove a session value and empty all session values. However, it is also necessary to note that the interface does not provide the abandon method functionality in previous versions.

Session Management based on Redis

With distributed session, the main task is to swap the memory of the sessions from the original to the distributed storage, this section, we take Redis storage as an example to explain the processing of distributed session.

To see the extension method using distributed sessions first, as shown below, we can see that its session container needs to be an IDistributedCache example of a supported interface.

public static Iapplicationbuilder usedistributedsession ([Notnullattribute]this iapplicationbuilder app, Idistributedcache cache, action<sessionoptions> configure = null);

This interface is a common interface for caching caching, that is, as long as we implement the caching interface, we can use it for session management. Further viewing the interface found that the set method defined in this interface also requires the implementation of a Icachecontext type of cache context (so that other programs can delegate calls at the time of invocation), and the interface definitions are as follows:

Public interface Idistributedcache
{
 void Connect ();
 void Refresh (string key);
 void Remove (string key);
 Stream Set (String key, Object state, action<icachecontext> create);
 BOOL TryGetValue (string key, out Stream value);

Public interface Icachecontext
{
 Stream Data {get;}
 String Key {get;}
 Object state {get;}

 void Setabsoluteexpiration (TimeSpan relative);
 void Setabsoluteexpiration (DateTimeOffset absolute);
 void Setslidingexpiration (TimeSpan offset);
}

Next, we implement the above functionality based on Redis, create RedisCache classes, inherit IDistributedCache , reference StackExchange.Redis assemblies, and implement IDistributedCache all the methods and properties of the interface, as follows:

Using Microsoft.Framework.Cache.Distributed;
Using Microsoft.Framework.OptionsModel;
Using Stackexchange.redis;
Using System;

Using System.IO; Namespace Microsoft.Framework.Caching.Redis {public class Rediscache:idistributedcache {//keys[1] = = key//AR
  GV[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 don't 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) {_options = optionsaccessor.options;
   This allows partitioning a single backend cache-for-use with multiple apps/services. _instance = _options. InstanceName?? String.
  Empty; public void Connect () {if (_connection = = null) {_connection = Connectionmultiplexer.connect (_options.c
    onfiguration); _cache = _connection.
   Getdatabase ();

   } Public Stream Set (string key, Object state, action<icachecontext> create) {Connect ();
   var context = new Cachecontext (key) {state = state};
   Create (context); var value = context.
   GetBytes (); var result = _cache. Scriptevaluate (Setscript, new rediskey[] {_instance + key}, new redisvalue[] {context. Absoluteexpiration? Ticks?? Notpresent,
     Context. SlidingExpiration? Ticks?? Notpresent, context. Getexpirationinseconds ()??
   Notpresent, value});
  Todo:error Handling return new MemoryStream (value, writable:false);
   public bool TryGetValue (string key, out Stream value) {value = Getandrefresh (key, getdata:true);
  return value!= null;
  public void Refresh (string key) {var ignored = Getandrefresh (key, Getdata:false);

   Private Stream Getandrefresh (string key, bool GetData) {Connect ();
   This also resets the LRU status as desired. Todo:can This is done by 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) {//Always get back two results, even if they all null.
    These operations would no-op in the null scenario. DateTimeOffset?
    absexpr; TimeSpan?
    sldexpr;
    Mapmetadata (results, out of absexpr, out sldexpr);
   Refresh (Key, absexpr, sldexpr); } if (results. Length >= 3 && results[2].
   HasValue) {return new MemoryStream (results[2], writable:false);
  return null; } private void Mapmetadata (redisvalue[] results, out DateTimeOffset absoluteexpiration, out TimeSpan? slidingexpiratio
   N) {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) {//Note Refresh has no effect
   If there is just a 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}} public void Remove (string key) {Connect (); _cache.
   Keydelete (_instance + key); Todo:error Handling}}}

In the above code, we used the custom class RedisCacheOptions as the Redis configuration information class, and in order to implement the Poco configuration definition, we also inherited the IOptions interface, which is defined as follows:

public class rediscacheoptions:ioptions<rediscacheoptions>
{public
 string Configuration {get; set;} Public

 string InstanceName {get; set;}

 Rediscacheoptions Ioptions<rediscacheoptions> The Options {get {return this
  ;}
 }

 Rediscacheoptions Ioptions<rediscacheoptions> Getnamedoptions (string name)
 {return this
  ;
 }
}

The third section defines the cache context class that is used when the delegate is invoked CacheContext , as follows:

Using Microsoft.Framework.Cache.Distributed;
Using System;

Using System.IO; namespace Microsoft.Framework.Caching.Redis {Internal class Cachecontext:icachecontext {private ReadOnly memorystr

  EAM _data = new MemoryStream ();
   Internal Cachecontext (string key) {key = key;
  CreationTime = Datetimeoffset.utcnow;
  }///<summary>///The key identifying this entry.

  </summary> public string Key {get; internal set;} <summary>///the state passed into Set.
  This can is used to avoid closures.

  </summary> public object ' {get; internal set;}

  Public Stream Data {get {_data}} Internal DateTimeOffset creationtime {get; set;}//Can let delegate set creation time internal DateTimeOffset?

  absoluteexpiration {get; private set;} Internal TimeSpan?

  slidingexpiration {get; private set;} public void Setabsoluteexpiration (TimeSpan relative)//allows the delegate to set a relative expiration time {if (relative <= TimeSpan.Zero) {th Row newArgumentOutOfRangeException ("relative", relative, "the relative expiration value must be positive.");
  } absoluteexpiration = CreationTime + relative;
   public void Setabsoluteexpiration (DateTimeOffset absolute)//can allow delegates to set absolute expiration {if (absolute <= creationtime) {throw new ArgumentOutOfRangeException ("absolute", absolute, "the absolute expiration value must is in the future."
   ); } absoluteexpiration = Absolute.
  ToUniversalTime ();
    public void Setslidingexpiration (TimeSpan offset)//You can have the delegate set offset expiration {if (offset <= TimeSpan.Zero) {
   throw new ArgumentOutOfRangeException ("offset", offset, "the sliding expiration value must be positive.");
  } slidingexpiration = offset; Internal long? Getexpirationinseconds () {if (Absoluteexpiration.hasvalue && slidingexpiration.hasvalue) {return (lo NG) Math.min ((absoluteexpiration.value-creationtime).
   TotalSeconds, SlidingExpiration.Value.TotalSeconds); else if (ABsoluteexpiration.hasvalue) {return (long) (Absoluteexpiration.value-creationtime).
   TotalSeconds;
   else if (Slidingexpiration.hasvalue) {return (long) SlidingExpiration.Value.TotalSeconds;
  return null; } internal byte[] GetBytes () {return _data.
  ToArray (); }
 }
}

The last step defines the RedisCache shortcut method that is needed to get the cached value based on key key, as follows:

Using Stackexchange.redis;
Using System;

Namespace Microsoft.Framework.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 redi Smembers = new Redisvalue[members. Length];
   for (int i = 0; i < members. Length; i++)
   {
    Redismembers[i] = (redisvalue) members[i];
   }
   var result = cache. Scriptevaluate (Hmgetscript, new rediskey[] {key}, redismembers);
   Todo:error checking?
   Return (redisvalue[]) result;}}

At this point, all the work is done, and the code method for registering this cache implementation as session provider is as follows:

App. Usedistributedsession (New Rediscache rediscacheoptions ()
{
 Configuration = "Fill in the address of the Redis here",
 INSTANCENAME = "Fill in the custom instance name here"
}), Options =>
{
 options. Cookiehttponly = true;
});

Reference: Http://www.mikesdotnetting.com/article/270/sessions-in-asp-net-5

About caching

By default, the local cache uses an example of the IMemoryCache interface, which can be manipulated by obtaining an example of the interface, as shown in the sample code:

var cache = App. Applicationservices.getrequiredservice<imemorycache> ();
var obj1 = cache. Get ("Key1");
BOOL Obj2 = cache. Get<bool> ("Key2");

For, distributed caching, because of addcaching, the IMemoryCache instance is the provider of the distributed cache by default, and the code is as follows:

public static class Cachingservicesextensions
{public
 static iservicecollection addcaching (this Iservicecollection collection)
 {
  collection. AddOptions ();
  Return collection. Addtransient<idistributedcache, localcache> ()
   . Addsingleton<imemorycache, memorycache> ();
 }

So, to use the new distributed caching implementation, we need to register our implementation, the code is as follows:

Services. Addtransient<idistributedcache, rediscache> ();
Services. Configure<rediscacheoptions> (opt =>
{
 opt). Configuration = "Fill in Redis address here";
 Opt. INSTANCENAME = "Fill in the custom instance name here";
};

The basic usage is as follows:

var cache = App. Applicationservices.getrequiredservice<idistributedcache> ();
Cache. Connect ();
var obj1 = cache. Get ("Key1"); The object is a stream, you need to convert it to a strong type, or you can rewrite the extension method
var bytes = obj1. ReadAllBytes ();
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.