Redis Cache i ASP.NET Core

I det här inlägget visas hur du kan använda Redis Cache som IDistributedCache i ASP.NET Core. Redis tillhandahåller minnesbaserad lagring för datastrukturer. Redis används som databas, cache och för meddelandehantering. Redis Cache används i multi-instans applikationer för att hantera sessionsdata och cachade data.

Som ett alternativ till Redis kan du använda en SQL-databas eller en NO-SQL databas för att hantera sessiondata i en multi-instans applikation. Redis Cache har inbyggd replikering, stöder många datastrukturer, kan ge hög tillgänglighet och kan användas på Azure.

Installera Redis Server på Windows

Du måste ladda ner och installera Redis på din utvecklingsdator för att kunna testa din implementering i ASP.NET Core. Du kan ladda ner en msi-fil eller en zip-fil, msi-filen installerar Redis och skapar en Windows-tjänst som automatiskt startar Redis-servern vid start.

När du har installerat Redis, se till att tjänsten har startats och att du kan pinga servern. Öppna en kommandotolk som administratör och bläddra till mappen där Redis är installerad (cd ”C:\Program Files\Redis”), starta redis-cli.exe och skriv ”ping”.

Microsoft Windows [Version 10.0.18362.239]
(c) 2019 Microsoft Corporation. Med ensamrätt.

C:\WINDOWS\system32>cd..
C:\Windows>cd..
C:\>cd program files
C:\Program Files>cd redis
C:\Program Files\Redis>redis-cli.exe
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>

Applikationsinställningar

Vi behöver först installera ett NuGet-paket (Microsoft.Extensions.Caching.Redis) i vårt projekt i Visual Studio, det är en distribuerad cache-implementering av IDistributedCache med Redis. Vi sparar en anslutningssträng till Redis som applikationsinställningar i secrets.json och appsettings.json. Standardporten för Redis är 6379, du kanske har använt en annan port.

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Information"
    }
  },
  "RedisCacheOptions": {
    "ConnectionString": "localhost:6379"
  }
}

Modeller

namespace Doxservr.Common.Options
{
    public class CacheOptions
    {
        #region Variables

        public double ExpirationInMinutes { get; set; }

        #endregion

        #region Constructors

        public CacheOptions()
        {
            // Set values for instance variables
            this.ExpirationInMinutes = 240;

        } // End of the constructor

        #endregion

    } // End of the class

} // End of the namespace
namespace Doxservr.Common.Options
{
    public class RedisCacheOptions
    {
        #region Variables

        public string ConnectionString { get; set; }

        #endregion

        #region Constructors

        public RedisCacheOptions()
        {
            // Set values for instance variables
            this.ConnectionString = "";

        } // End of the constructor

        #endregion

    } // End of the class

} // End of the namespace
using System;
using System.Collections.Generic;

namespace Doxservr.Common.Models
{
    public class KeyStringList
    {
        #region Variables

        public IDictionary<string, string> dictionary;

        #endregion

        #region Constructors

        public KeyStringList()
        {
            // Set values for instance variables
            this.dictionary = new Dictionary<string, string>(10);

        } // End of the constructor

        public KeyStringList(Int32 capacity)
        {
            // Set values for instance variables
            this.dictionary = new Dictionary<string, string>(capacity);

        } // End of the constructor

        public KeyStringList(IDictionary<string, string> dictionary)
        {
            // Set values for instance variables
            this.dictionary = dictionary;

        } // End of the constructor

        #endregion

        #region Insert methods

        public void Add(string key, string value)
        {
            // Add the value to the dictionary
            dictionary.Add(key, value);

        } // End of the Add method

        #endregion

        #region Update methods

        public void Update(string key, string value)
        {
            // Update the value
            dictionary[key] = value;

        } // End of the Update method

        #endregion

        #region Get methods

        public string Get(string key)
        {
            // Create the string to return
            string value = key;

            // Check if the dictionary contains the key
            if (this.dictionary.ContainsKey(key))
            {
                value = this.dictionary[key];
            }

            // Return the value
            return value;

        } // End of the Get method

        #endregion

    } // End of the class
    
} // End of the namespace

Tjänster

Vi registrerar tjänster för minnescache, redis-cache och sessioner i metoden ConfigureServices i StartUp-klassen. Minnescache används som backup om vi inte vill använda Redis. Vår sessionstjänst kommer att använda Redis Cache om vi registrerat redis distributed cache eller minnescache om vi sätter anslutningssträngen till en tom sträng.

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public void ConfigureServices(IServiceCollection services)
{
    // Add the mvc framework
    services.AddRazorPages();

    // Create options
    services.Configure<CacheOptions>(options => { options.ExpirationInMinutes = 240d; });

    // Add memory cache
    services.AddDistributedMemoryCache();

    // Add redis distributed cache
    if (configuration.GetSection("RedisCacheOptions")["ConnectionString"] != "")
    {
        services.AddDistributedRedisCache(options =>
        {
            options.Configuration = configuration.GetSection("RedisCacheOptions")["ConnectionString"];
            options.InstanceName = "Mysite:";
        });
    }

    // Add the session service
    services.AddSession(options =>
    {
        // Set session options
        options.IdleTimeout = TimeSpan.FromMinutes(30d);
        options.Cookie.Name = ".Mysite";
        options.Cookie.Path = "/";
        options.Cookie.HttpOnly = true;
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
    });

} // End of the ConfigureServices method

Vi måste också använda sessioner i metoden Configure i klassen StartUp för att automatiskt aktivera sessionshantering i applikationen.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Use sessions
    app.UseSession();

} // End of the Configure method

Arbeta med distribuerat cache

Vi har en klass där vi har injicerat IDistributedCache, vi kan nu använda detta gränssnitt för att få data från Redis cache och för att spara data till Redis cache.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
using Doxservr.Common.Options;
using Doxservr.Common.Models;

namespace Doxservr.Common.Repositories
{
    public class StaticTextRepository : IStaticTextRepository
    {
        #region Variables

        private readonly ICosmosDatabaseRepository cosmos_database_repository;
        private readonly IDistributedCache distributed_cache;
        private readonly CacheOptions cache_options;
        private readonly ILanguageRepository language_repository;

        #endregion

        #region Constructors

        public StaticTextRepository(ICosmosDatabaseRepository cosmos_database_repository, IDistributedCache distributed_cache, IOptions<CacheOptions> cache_options, 
            ILanguageRepository language_repository)
        {
            // Set values for instance variables
            this.cosmos_database_repository = cosmos_database_repository;
            this.distributed_cache = distributed_cache;
            this.cache_options = cache_options.Value;
            this.language_repository = language_repository;

        } // End of the constructor

        #endregion

        #region Add methods

        public async Task<bool> Add(StaticTextsDocument item)
        {
            // Create a document
            return await this.cosmos_database_repository.Add<StaticTextsDocument>(item);

        } // End of the Add method

        #endregion

        #region Update methods

        public async Task<bool> Upsert(StaticTextsDocument item)
        {
            // Upsert a document
            return await this.cosmos_database_repository.Upsert<StaticTextsDocument>(item);

        } // End of the Upsert method

        public async Task<bool> Update(StaticTextsDocument item)
        {
            // Replace a document
            return await this.cosmos_database_repository.Update<StaticTextsDocument>(item.id, item);

        } // End of the Update method

        #endregion

        #region Get methods

        public async Task<ModelItem<StaticTextsDocument>> GetById(string id)
        {
            // Return the post
            return await this.cosmos_database_repository.GetById<StaticTextsDocument>(id, id);

        } // End of the GetById method

        public async Task<ModelItem<StaticTextsDocument>> GetByLanguageCode(string language_code)
        {
            // Create the sql string
            string sql = "SELECT VALUE s FROM s WHERE s.language_code = @language_code AND s.model_type = @model_type";

            // Create parameters
            SqlParameterCollection parameters = new SqlParameterCollection()
            {
                new SqlParameter("@language_code", language_code),
                new SqlParameter("@model_type", "static_text")
            };

            // Return the post
            return await this.cosmos_database_repository.GetByQuery<StaticTextsDocument>(sql, parameters);

        } // End of the GetByLanguageCode method

        public async Task<KeyStringList> GetFromCache(string language_code)
        {
            // Create the cacheId
            string cacheId = "StaticTexts_" + language_code.ToString();

            // Get the cached settings
            string data = this.distributed_cache.GetString(cacheId);

            // Create the list to return
            KeyStringList posts = new KeyStringList();

            if (data == null)
            {
                // Get the post
                ModelItem<StaticTextsDocument> static_texts_model = await GetByLanguageCode(language_code);

                // Make sure that something was found in the database
                if (static_texts_model.item == null)
                {
                    return posts;
                }
                else
                {
                    posts = new KeyStringList(static_texts_model.item.dictionary);
                }

                // Create cache options
                DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
                cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));
                cacheEntryOptions.SetAbsoluteExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));

                // Save data in cache
                this.distributed_cache.SetString(cacheId, JsonConvert.SerializeObject(posts), cacheEntryOptions);
            }
            else
            {
                posts = JsonConvert.DeserializeObject<KeyStringList>(data);
            }

            // Return the post
            return posts;

        } // End of the GetFromCache method

        #endregion

        #region Delete methods

        public async Task<bool> DeleteOnId(string id)
        {
            // Delete a document
            return await this.cosmos_database_repository.DeleteOnId(id, id);

        } // End of the DeleteOnId method

        #endregion

        #region Helper methods

        public async Task RemoveFromCache()
        {
            // Get all languages
            ModelItem<LanguagesDocument> languages_model = await this.language_repository.GetByType();

            // Loop languages
            foreach (KeyValuePair<string, string> entry in languages_model.item.dictionary)
            {
                // Get the data
                string data = this.distributed_cache.GetString("StaticTexts_" + entry.Key);

                // Only remove the cache if it exists
                if (data != null)
                {
                    // Remove data from cache
                    this.distributed_cache.Remove("StaticTexts_" + entry.Key);
                }
            }

        } // End of the RemoveFromCache method

        #endregion

    } // End of the class

} // End of the namespace

Arbeta med sessioner

Vi har lagt till en tilläggsklass för sessioner, detta för att kunna spara och hämta serialiserbara objekt som sessionvariabler.

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace Doxservr.Common.Helpers
{
    public static class SessionExtensions
    {
        public static void Set<T>(this ISession session, string key, T value)
        {
            session.SetString(key, JsonConvert.SerializeObject(value));

        } // End of the Set method

        public static T Get<T>(this ISession session, string key)
        {
            var value = session.GetString(key);
            return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
        }

    } // End of the class
    
} // End of the namespace

Nedan följer några exempel på hur du kan sätta värden för och hämta värden från sessionvariabler i ASP.NET Core.

HttpContext.Session.SetString("SessionKey1", "VALUE 1");
HttpContext.Session.SetInt32("SessionKey2", 2);
string value1 = HttpContext.Session.GetString("SessionKey1");
Int32 value2 = HttpContext.Session.GetInt32("SessionKey2");
HttpContext.Session.Set<DateTime>("SessionKey3", DateTime.Now);
DateTime dt = HttpContext.Session.Get<DateTime>("SessionKey3");

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *