Interpretation of ASP. NET 5 MVC 6 Series Tutorials (8): Session and Caching
- 2021-07-26 07:30:42
- OfStack
In the previous version, Session existed in System. Web, and in the new version of ASP. NET 5, since it no longer depends on System. Web. dll library, Session has become a configurable module (middleware) in ASP. NET 5.
Configure to enable Session
The Session module in ASP. NET 5 exists in the Microsoft. AspNet. Session class library. 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 Session in ConfigureServices (and configure it):
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
Finally, in the Configure method, the mode of using Session is started. If it has been configured above, the configuration information can no longer be passed in. Otherwise, the configuration information of Session should be passed in like the configuration information 1 above. 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); // Open memory Session
//app.UseDistributedSession(null, null);// Turn on distributed Session That is, persistence Session
//app.UseDistributedSession(new RedisCache(new RedisCacheOptions() { Configuration = "localhost" }));
For the UseInMemorySession method, two optional parameters are accepted, which are:
IMemoryCache
Can be used to modify the default save address of Session data;
Action<SessionOptions>
Delegation allows you to modify the default options, such as the path of Session cookie, the default expiration time, etc. In this example, we modify the default expiration time to 30 minutes.
Note: This method must be called before app. UseMvc, otherwise Session will not be obtained in Mvc and an error will occur.
Gets and sets Session
Gets and sets the Session object, which is generally used in action of Controller through
this.Context.Session
To get, which gets a interface-based
ISessionCollection
Gets or sets an instance of the. This interface can get and set Session values through indexes, Set, TryGetValue and other methods, but we find that when getting and setting Session, we can only use byte [] type, but can't set any type of data like the previous version of Session1. The reason is that the new version of Session requires serialization to support storage on remote servers, so saving as
byte[]
Type. So when we save Session, we need to convert it to
byte[]
To save, and get the
byte[]
Convert to your original type. This form is too troublesome. Fortunately, Microsoft is
Microsoft.AspNet.Http
Namespace that belongs to
Microsoft.AspNet.Http.Extensions.dll
In), we have added several extension methods for setting and saving
byte[]
Type,
Action<SessionOptions>
0
Type, and
string
Type, code 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);
So, in
Controller
Quote in
Microsoft.AspNet.Http
Namespace, we can set and get 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 Gets for Custom Types
As we said earlier, to save Session of custom type, it is necessary to convert its type into byte [] array. In this example, we set and get the code for Session data of bool type. 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 an Session value and empty all Session values respectively. However, it should also be noted that this interface does not provide the Abandon method functionality in previous versions.
Session Management Based on Redis
Using distributed Session, its main work is to change the storage place of Session from the original memory to distributed storage. In this section, we take Redis storage as an example to explain the processing of distributed Session.
First, look at the extension method using distributed Session. The example is as follows. We can see that its Session container needs to be a support
IDistributedCache
Example of the interface of.
public static IApplicationBuilder UseDistributedSession([NotNullAttribute]this IApplicationBuilder app, IDistributedCache cache, Action<SessionOptions> configure = null);
This interface is a general interface for caching Caching, that is, as long as we implement the cache interface, we can use it for the management of Session. Looking at the interface in step 1, we find that the Set method defined in this interface also needs to implement a cache context of ICacheContext type (so that other programs can delegate calls when calling). 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 functions based on Redis and create
RedisCache
Class and inherits the
IDistributedCache
, quote
StackExchange.Redis
Assembly, and then implement the
IDistributedCache
All the methods and properties of the 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<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.Configuration);
_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 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 used the custom class
RedisCacheOptions
As the configuration information class of Redis, in order to implement the configuration definition based on POCO, we also inherit
this.Context.Session
0
Interface, which is defined as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
0
Part 3, Defining the Cache Context Class Used in Delegate Invoke
this.Context.Session
1
The specific code is as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
1
The last step defines,
RedisCache
The shortcut to get the cached value according to the key key is as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
2
At this point, all the work is done, and the code method for registering the cache implementation as provider of Session is as follows:
app.UseDistributedSession(new RedisCache(new RedisCacheOptions()
{
Configuration = " Fill in here redis The address of ",
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, and you can operate on the local cache by getting an example of that interface, as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
4
For the distributed cache, because of AddCaching, the default is to use the IMemoryCache instance as the provider of the distributed cache, and the code is as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
5
Therefore, to use the new distributed Caching implementation, we need to register our own implementation with the following code:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
6
The basic usage method is as follows:
services.AddCaching(); // Both must be added at the same time because Session Depend on Caching
services.AddSession();
//services.ConfigureSession(null); You can configure it here or later
7