Interpreting ASP 5 & MVC6 Series (8): Session and caching

Source: Internet
Author: User

Original: Interpretation of ASP. 5 & MVC6 Series (8): Session and caching

In the previous version, the session existed in System.Web, and the new version of ASP. NET 5 was not dependent on the System.Web.dll library, so the session became an ASP. A configurable module (middleware) in 5.

Configure Enable session

The session module in ASP. NET 5 exists in the Microsoft.AspNet.Session class library, in order 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 (and configure it) in Configureservices:

services.AddCaching();  // 这两个必须同时添加,因为Session依赖于Cachingservices.AddSession();//services.ConfigureSession(null); 可以在这里配置,也可以再后面进行配置

Finally in the Configure method, open the use session mode, if it has been configured above, you can no longer pass in the configuration information, or the same as the above configuration information, the configuration information passed to the session, the code is as follows:

app.UseInMemorySession(configure:s => { s.IdleTimeout = TimeSpan.FromMinutes(30); });//app.UseSession(o => { o.IdleTimeout = TimeSpan.FromSeconds(30); });//app.UseInMemorySession(null, null);   //开启内存Session//app.UseDistributedSession(null, null);//开启分布式Session,也即持久化Session//app.UseDistributedSession(new RedisCache(new RedisCacheOptions() { Configuration = "localhost" }));

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

Note: The method must be in the app. Usemvc is called before, otherwise the session is not available in MVC, and error occurs.

Get and set session

The

Gets and sets the session object, typically through this in the Controller's action. Context.session To obtain an instance of an interface isessioncollection . This interface can be indexed, set, TryGetValue and other methods to get and set session values, 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 as you did in previous versions of the session. The reason 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[] in order to save it, and get it again to convert the byte[] to its original type again. This form is too troublesome, fortunately Microsoft is in the Microsoft.AspNet.Http namespace (which belongs to Microsoft.AspNet.Http.Extensions.dll ), Added several extension methods for us to set and save the byte[] type, the 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", 21);ViewBag.Name = Context.Session.GetString("Name");ViewBag.Age = Context.Session.GetInt("Age");
Session settings and get for custom types

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

public static class SessionExtensions{    public static bool? GetBoolean(this ISessionCollection session, 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 the extension method of the bool type, we can use it like Setint/getint, as shown in the following example:

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

In addition, the Isessioncollection interface also provides a function of remove (string key) and clear () two methods for deleting a session value and emptying all session values, respectively. However, it is also important to note that this interface does not provide the abandon method functionality in previous versions.

Redis-based session management

Using the distributed session, the main task is to save the session from the original memory to the distributed storage, in this section, we use Redis storage as an example to explain the processing of distributed session.

First look at the extension method using the distributed session, 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 the common interface of the cache caching, that is, as long as we implement the cache interface, it can be used for session management. Further review of the interface found that the set method defined in the interface also requires the implementation of a cache context of type Icachecontext (so that other programs can make delegate calls at the time of invocation), 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 then implement IDistributedCache all the methods and properties of the interface, with the following code:

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//argv[1] = absolute-expiration-ticks as Long ( -1 for none)//argv[2] = SL Iding-expiration-ticks as Long ( -1 for none)//argv[3] = relative-expiration (long, in seconds, 1 for none)-M  In (Absolute-expiration-now, sliding-expiration)//argv[4] = data-byte[]//The 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) {_options = Optionsaccessor.option            S            This allows partitioning a, backend cache for use with multiple apps/services. _instance = _options. InstanceName?? String.        Empty; public void Connect () {if (_connection = = null) {_connection = Conn Ectionmultiplexer.connect (_options.                Configuration); _cache = _connection.            Getdatabase (); }} public Stream Set (string key, Object state, action<icachecontext> create) {Conn            ECT (); 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:tru            e);        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 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 new MemoryStream (results[2], writable:false);        } return null; } private void Mapmetadata (redisvalue[] results, out of DateTimeOffset? absoluteexpiration, out TimeSpan? slidingexpir            ation) {absoluteexpiration = null;            slidingexpiration = null; var absoluteexpirationticks = (long?)            Results[0]; if (absoluteexpirationticks.hasvalue && absoluteexpirationticks.value! = notpresent) {A            Bsoluteexpiration = new DateTimeOffset (Absoluteexpirationticks.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) {//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}} public void Remove (string key) {Connect (); _cache.            Keydelete (_instance + key); Todo:error Handling}}}

In the code above, we used the custom class RedisCacheOptions as the configuration information class for Redis, and in order to implement the Poco-based 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>.Options    {        get { return this; }    }    RedisCacheOptions IOptions<RedisCacheOptions>.GetNamedOptions(string name)    {        return this;    }}

The third section defines the cache context class used when delegating calls CacheContext , with the following code:

Using microsoft.framework.cache.distributed;using system;using System.io;namespace microsoft.framework.caching.redis{Internal class Cachecontext:icachecontext {private ReadOnly Memorystrea        M _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 State {get; internal set;}        Public Stream Data {get {return _data;}} Internal DateTimeOffset creationtime {get; set}//can have delegate set creation time internal DateTimeOffset?        absoluteexpiration {get; private set;} Internal TimeSpan?        slidingexpiration {get; private set;} public void setabsoluteexpiration (timesPan relative)//can have delegates set relative expiration time {if (relative <= TimeSpan.Zero) {throw new            ArgumentOutOfRangeException ("relative", relative, "the relative expiration value must be positive.");        } absoluteexpiration = CreationTime + relative; The public void setabsoluteexpiration (DateTimeOffset absolute)//can have the delegate set an absolute expiration time {if (absolute < = CreationTime) {throw new ArgumentOutOfRangeException ("absolute", absolute, "the absolute expi            Ration value must is in the future. "); absoluteexpiration = Absolute.        ToUniversalTime (); } public void Setslidingexpiration (TimeSpan offset)//can have the delegate set offset expiration {if (offset <= times Pan. Zero) {throw new ArgumentOutOfRangeException ("offset", offset, "The sliding expiration value mu            St be positive. ");}        slidingexpiration = offset;        }Internal long?            Getexpirationinseconds () {if (Absoluteexpiration.hasvalue && slidingexpiration.hasvalue) {return (Long) math.min ((absoluteexpiration.value-creationtime).            TotalSeconds, SlidingExpiration.Value.TotalSeconds); } else if (Absoluteexpiration.hasvalue) {return (long) (ABSOLUTEEXPIRATION.VALUE-CR Eationtime).            TotalSeconds; } else if (Slidingexpiration.hasvalue) {return (long) SlidingExpiration.Value.TotalSe            Conds;        } return null; } internal byte[] GetBytes () {return _data.        ToArray (); }    }}

The final step is to define, RedisCache in the need of the key key to get the cached value of the shortcut method, the code is 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 redisMembers = 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 the cache implementation as session provider is as follows:

app.UseDistributedSession(new RedisCache(new RedisCacheOptions(){    Configuration = "此处填写 redis的地址",    InstanceName = "此处填写自定义实例名"}), 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, and you can manipulate the local cache by getting 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 caches, because of addcaching, the IMemoryCache instance is provider as a distributed cache by default, as shown in the following code:

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 own implementation with the following code:

services.AddTransient<IDistributedCache, RedisCache>();services.Configure<RedisCacheOptions>(opt =>{    opt.Configuration = "此处填写 redis的地址";    opt.InstanceName = "此处填写自定义实例名";});

The basic use method is as follows:

var cache = app.ApplicationServices.GetRequiredService<IDistributedCache>();cache.Connect();var obj1 = cache.Get("key1"); //该对象是流,需要将其转换为强类型,或自己再编写扩展方法var bytes = obj1.ReadAllBytes();
Synchronization and recommendations

This article has been synchronized to the Catalog index: Interpreting ASP. & MVC6 Series

Interpreting ASP 5 & MVC6 Series (8): Session and caching

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.