In previous versions, Session existed in System.Web. Since the new version of ASP.NET 5 no longer depends on the System.Web.dll library, Session has become a configurable module (middleware) in ASP.NET 5. Configure and enable Session The Session module in ASP.NET 5 exists in the Microsoft.AspNet.Session class library. To enable Session, first add the following to 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();
- services.AddSession();
-
Finally, in the Configure method, turn on the Session mode. If you have already configured it above, you do not need to pass in the configuration information. Otherwise, you still need to pass in the Session configuration information like the above configuration information. The code is as follows: - app.UseInMemorySession(configure:s => { s.IdleTimeout = TimeSpan.FromMinutes( 30 ); });
-
-
-
-
For the UseInMemorySession method, it receives two optional parameters: IMemoryCache can be used to modify the default save address of Session data; Action The delegation allows you to modify the default options, such as the path of the Session cookie, the default expiration time, etc. In this example, we change the default expiration time to 30 minutes. Note: This method must be called before app.UseMvc, otherwise the Session cannot be obtained in Mvc and an error will occur. Getting and Setting Session To get and set the Session object, we usually get it through this.Context.Session in the Controller's action, which gets an instance based on the interface ISessionCollection. This interface can get and set the Session value through methods such as index, Set, and TryGetValue, but we found that when getting and setting the Session, we can only use the byte[] type, and cannot set any type of data like the previous version of Session. The reason is that the new version of Session needs to support storage on a remote server, so it needs to support serialization, so it is mandatory to save it as a byte[] type. So when we save the Session, we need to convert it to byte[] before saving it, and after obtaining it, we need to convert the byte[] to its original type again. This form is too troublesome. Fortunately, Microsoft has added several extension methods for us in the Microsoft.AspNet.Http namespace (belonging to Microsoft.AspNet.Http.Extensions.dll), which are used to set and save byte[] type, int type, and string type. The code is as follows: - 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);
Therefore, after referencing the Microsoft.AspNet.Http namespace in the Controller, we can set and obtain 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" );
Setting and getting Session of custom type As we said before, to save a custom type of Session, you need to convert its type into a byte[] array. In this example, we set and get the code for the bool type Session data. The example is as follows: - 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 bool type, we can use it like SetInt/GetInt, as shown below: - Context.Session.SetBoolean( "Liar" , true );
- ViewBag.Liar = Context.Session.GetBoolean( "Liar" );
In addition, the ISessionCollection interface also provides two methods, Remove(string key) and Clear(), which are used to delete a session value and clear all session values respectively. However, it should also be noted that this interface does not provide the Abandon method function in previous versions. Session management based on Redis The main task of using distributed sessions is to change the location where sessions are stored from the original memory to distributed storage. In this section, we will use Redis storage as an example to explain the processing of distributed sessions. First, let's look at the extension method for using distributed Session. The example is as follows. We can see that its Session container needs to be an interface example that supports IDistributedCache.
public static IApplicationBuilder UseDistributedSession([NotNullAttribute]this IApplicationBuilder app, IDistributedCache cache, Action configure = null);
This interface is a common interface for caching, that is, as long as we implement the caching interface, we can use it for session management. Looking further into the interface, we find that the Set method defined in the interface also needs to implement a cache context of type ICacheContext (so that other programs can delegate the call when calling it). 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 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 functions based on Redis, create the RedisCache class, inherit IDistributedCache, reference the StackExchange.Redis assembly, and then implement all methods and properties of the IDistributedCache interface. The code is 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
// 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 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.Configuration); _cache = _connection.GetDatabase(); } } public Stream Set(string key, object state, Action 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 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 new MemoryStream(results[2], writable: false); } return null; } 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) { // 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 } } public void Remove(string key) { Connect(); _cache.KeyDelete(_instance + key); // TODO: Error handling } } } In the above code, we use the custom class RedisCacheOptions as the configuration information class of Redis. In order to implement the POCO-based configuration definition, we also inherit the IOptions interface. The definition of this class is as follows:
public class RedisCacheOptions : IOptions { public string Configuration { get; set; } public string InstanceName { get; set; } RedisCacheOptions IOptions .Options { get { return this; } } RedisCacheOptions IOptions .GetNamedOptions(string name) { return this; } } The third part is to define the cache context class CacheContext used when delegating the call. The specific code is as follows: - using Microsoft.Framework.Cache.Distributed;
- using System;
- using System.IO;
-
- namespace Microsoft.Framework.Caching.Redis
- {
- internal class CacheContext : ICacheContext
- {
- private readonly MemoryStream _data = new MemoryStream();
-
- internal CacheContext(string key)
- {
- Key = key;
- CreationTime = DateTimeOffset.UtcNow;
- }
-
-
- public string Key { get; internal set; }
-
-
- public object State { get; internal set; }
-
- public Stream Data { get { return _data; } }
-
- internal DateTimeOffset CreationTime { get; set; }
-
- internal DateTimeOffset? AbsoluteExpiration { get; private set; }
-
- internal TimeSpan? SlidingExpiration { get; private set; }
-
- public void SetAbsoluteExpiration(TimeSpan relative)
- {
- if (relative <= TimeSpan.Zero)
- {
- throw new ArgumentOutOfRangeException( "relative" , relative, "The relative expiration value must be positive." );
- }
- AbsoluteExpiration = CreationTime + relative;
- }
-
- public void SetAbsoluteExpiration(DateTimeOffset absolute)
- {
- if (absolute <= CreationTime)
- {
- throw new ArgumentOutOfRangeException( "absolute" , absolute, "The absolute expiration value must be in the future." );
- }
- AbsoluteExpiration = absolute.ToUniversalTime();
- }
-
- public void SetSlidingExpiration(TimeSpan offset)
- {
- 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 ( long )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 is to define the quick method needed in RedisCache to get the cache value according to the key. 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);
-
- return (RedisValue[])result;
- }
- }
- }
At this point, all the work is done. The code method to register the cache implementation as the Session provider is as follows: - app.UseDistributedSession( new RedisCache( new RedisCacheOptions()
- {
- Configuration = "Fill in the redis address here" ,
- InstanceName = "Enter your 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 instance of the IMemoryCache interface. You can operate the local cache by obtaining an instance of the interface. The sample code is as follows: - var cache = app.ApplicationServices.GetRequiredService(); var obj1 = cache.Get( "key1" ); bool obj2 = cache.Get( "key2" );
-
- For distributed cache, due to AddCaching, the IMemoryCache instance is used as the provider of distributed cache by default. The code is as follows:
-
- public static classCachingServicesExtensions
- {
- public static IServiceCollection AddCaching( this IServiceCollection collection)
- {
- collection.AddOptions();
- return collection.AddTransient
Therefore, to use the new distributed Caching implementation, we need to register our own implementation. The code is as follows:
- services.AddTransient
The basic usage is as follows:
var cache = app.ApplicationServices.GetRequiredService (); cache.Connect(); var obj1 = cache.Get("key1"); //This object is a stream, you need to convert it to a strong type, or write your own extension method var bytes = obj1.ReadAllBytes(); |