Snabbstart: Använda Azure Cache for Redis med en ASP.NET-webbapp

I den här snabbstarten använder du Visual Studio 2019 för att skapa en ASP.NET-webbapp som ansluter till Azure Cache for Redis för att lagra och hämta data från cachen. Sedan distribuerar du appen till Azure App Service.

Hoppa till koden på GitHub

Om du vill hoppa direkt till koden kan du gå ASP.NET snabbstarten på GitHub.

Förutsättningar

Skapa Visual Studio-projektet

  1. Öppna Visual Studio och välj sedan Arkiv > ny > Project.

  2. I dialogrutan Skapa ett nytt projekt gör du följande:

    Skapa projekt

    a. I sökrutan anger du C# ASP.NET Web Application.

    b. Välj ASP.NET (.NET Framework).

    c. Välj Nästa.

  3. I rutan Project namn ger du projektet ett namn. I det här exemplet använde vi ContosoTeamStats.

  4. Kontrollera att .NET Framework 4.6.1 eller senare har valts.

  5. Välj Skapa.

  6. Välj MVC som projekttyp.

  7. Kontrollera att Ingen autentisering är angivet i autentiseringsinställningarna. Beroende på din version av Visual Studio kan det vara en annan standardinställning för Autentisering. För att ändra detta väljer du Ändra autentisering och sedan Ingen autentisering.

  8. Välj Skapa för att skapa projektet.

Skapa en cache

Nu ska skapa du cachen för appen.

  1. Om du vill skapa ett cacheminne loggar du in Azure Portal och väljer Skapa en resurs.

    Skapa en resurs markeras i det vänstra navigeringsfönstret.

  2. På sidan Nytt väljer du Databaser och väljer sedan Azure Cache for Redis.

    På Ny är Databaser markerat och Azure Cache for Redis är markerat.

  3. På sidan Ny Redis Cache konfigurerar du inställningarna för den nya cachen.

    Inställning Välj ett värde Beskrivning
    Prenumeration I listrutan väljer du din prenumeration. Prenumerationen där den nya instansen ska skapas Azure Cache for Redis instansen.
    Resursgrupp Välj en resursgrupp i listrutan eller välj Skapa ny och ange ett nytt resursgruppnamn. Namnet på den resursgrupp där du vill skapa ditt cacheminne och andra resurser. Genom att placera alla dina appresurser i en resursgrupp kan du enkelt hantera eller ta bort dem tillsammans.
    DNS-namn Ange ett unikt namn. Cachenamnet måste vara en sträng mellan 1 och 63 tecken som endast innehåller siffror, bokstäver eller bindestreck. Namnet måste börja och sluta med en siffra eller bokstav och får inte innehålla efterföljande bindestreck. Cacheinstansens värdnamn blir <DNS name> .redis.cache.windows.net.
    Plats Välj en plats i listrutan. Välj en region nära andra tjänster som ska använda ditt cacheminne.
    Cachetyp I listrutan väljer du en nivå. Nivån avgör storlek, prestanda och funktioner som är tillgängliga för cachen. Mer information finns i Översikt över Azure Cache for Redis.
  4. Välj fliken Nätverk eller välj knappen Nätverk längst ned på sidan.

  5. fliken Nätverk väljer du din anslutningsmetod.

  6. Välj fliken Nästa: Avancerat eller välj knappen Nästa: Avancerat längst ned på sidan.

  7. På fliken Avancerat för en basic- eller standard-cacheinstans väljer du aktivera om du vill aktivera en icke-TLS-port. Du kan också välja vilken Redis-version du vill använda, antingen 4 eller 6.

    Redis version 4 eller 6.

  8. På fliken Avancerat för premiumcacheinstansen konfigurerar du inställningarna för icke-TLS-port, klustring och datapersistence. Du kan också välja vilken Redis-version du vill använda, antingen 4 eller 6.

  9. Välj fliken Nästa: Taggar eller välj knappen Nästa: Taggar längst ned på sidan.

  10. Du kan också ange namn och värde på fliken Taggar om du vill kategorisera resursen.

  11. Välj Granska + skapa. Du kommer till fliken Granska + skapa där Azure verifierar din konfiguration.

  12. När det gröna meddelandet Valideringen har godkänts visas väljer du Skapa.

Det tar en stund innan cacheminnet skapas. Du kan övervaka förloppet på Azure Cache for Redis översiktssidan. När Status visas som Körs är cacheminnet klart att användas.

Hämta värdnamn, portar och åtkomstnycklar från Azure Portal

För att kunna ansluta Azure Cache for Redis en instans behöver cacheklienter värdnamnet, portarna och en nyckel för cachen. Vissa klienter kan hänvisa till dessa objekt med namn som skiljer sig något. Du kan hämta värdnamn, portar och nycklar från Azure Portal.

  • Om du vill hämta åtkomstnycklarna går du till det vänstra navigeringsfönstret i cacheminnet och väljer Åtkomstnycklar.

    Nycklar för Azure Cache for Redis

  • Om du vill hämta värdnamnet och portarna går du till det vänstra navigeringsfönstret i cacheminnet och väljer Egenskaper. Värdnamnet har formen <DNS name> .redis.cache.windows.net.

    Egenskaper för Azure Cache for Redis

Redigera filen CacheSecrets.config

  1. Skapa en fil på datorn med namnet CacheSecrets.config. Placera den på en plats där den inte checkas in med källkoden för exempelprogrammet. För den här snabbstarten finns filen CacheSecrets.config i C:\AppSecrets\CacheSecrets.config.

  2. Redigera filen CacheSecrets.config. Lägg sedan till följande innehåll:

    <appSettings>
        <add key="CacheConnection" value="<cache-name>.redis.cache.windows.net,abortConnect=false,ssl=true,allowAdmin=true,password=<access-key>"/>
    </appSettings>
    
  3. Ersätt <cache-name> med din cachens värdnamn.

  4. Ersätt <access-key> med primärnyckeln för cachen.

    Tips

    Du kan använda den sekundära åtkomstnyckeln under nyckelrotation som en alternativ nyckel medan du återskapar den primära åtkomstnyckeln.

  5. Spara filen.

Uppdatera MVC-appen

I det här avsnittet uppdaterar du programet så att det stöder en ny vy som visar ett enkelt test mot Azure Cache for Redis.

Uppdatera web.config-filen med en appinställning för cachen

När du kör appen lokalt används informationen i CacheSecrets.config för att ansluta till Azure Cache for Redis-instansen. Du kommer senare att distribuera appen till Azure. Då konfigurerar du en appinställning i Azure som appen kommer att använda för att hämta cachens anslutningsinformation i stället för den här filen.

Eftersom CacheSecrets.config inte har distribuerats till Azure med din app använder du den bara när du testar appen lokalt. Var noga med att skydda informationen så att inte obehöriga kan komma åt cachelagrade data.

Uppdatera filen web.config

  1. I Solution Explorer dubbelklickar du på web.config för att öppna den.

    Web.config

  2. Leta reda på elementet <appSetting> i filen web.config. Lägg sedan till följande file-attribut. Om du använder ett annat namn eller en annan plats, byter du ut dessa värden mot de som visas i exemplet.

  • Innan: <appSettings>
  • Efter: <appSettings file="C:\AppSecrets\CacheSecrets.config">

ASP.NET-körningsmiljön sammanfogar innehållet i den externa filen med markeringen i <appSettings>-elementet. Vid körningen ignoreras filattributet om det inte går att hitta den angivna filen. Din hemliga information (anslutningssträngen till cachen) ingår inte i källkoden för programmet. När du distribuerar din webbapp till Azure så distribueras inte filen CacheSecrets.config.

Konfigurera appen till att använda StackExchange.Redis

  1. Konfigurera appen att använda NuGet-paketet i StackExchange.Redis för Visual Studio genom att välja Verktyg > NuGet Package Manager > Package Manager-konsolen.

  2. Kör följande kommando från fönstret Package Manager Console:

    Install-Package StackExchange.Redis
    
  3. NuGet-paketet hämtar och lägger till de nödvändiga sammansättningsreferenserna för klientprogrammet för att få åtkomst till Azure Cache for Redis med cacheklienten StackExchange.Azure Cache for Redis. Om du vill använda en starkt krypterad version av StackExchange.Redis klientbiblioteket ska du installera paketet StackExchange.Redis.

Uppdatera HomeController och layout

  1. I Solution Explorer expanderar du mappen Controllers och öppnar filen HomeController.cs.

  2. Lägg till using följande -instruktioner överst i filen.

    using StackExchange.Redis;
    using System.Configuration;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    
  3. Lägg till följande medlemmar i klassen HomeController för att stödja en ny åtgärd som kör vissa kommandon mot den nya RedisCache cachen.

         public async Task<ActionResult> RedisCache()
        {
            ViewBag.Message = "A simple example with Azure Cache for Redis on ASP.NET.";
    
            if (Connection == null)
            {
                await InitializeAsync();
            }
    
            IDatabase cache = await GetDatabaseAsync();
    
            // Perform cache operations using the cache object...
    
            // Simple PING command
            ViewBag.command1 = "PING";
            ViewBag.command1Result = cache.Execute(ViewBag.command1).ToString();
    
            // Simple get and put of integral data types into the cache
            ViewBag.command2 = "GET Message";
            ViewBag.command2Result = cache.StringGet("Message").ToString();
    
            ViewBag.command3 = "SET Message \"Hello! The cache is working from ASP.NET!\"";
            ViewBag.command3Result = cache.StringSet("Message", "Hello! The cache is working from ASP.NET!").ToString();
    
            // Demonstrate "SET Message" executed as expected...
            ViewBag.command4 = "GET Message";
            ViewBag.command4Result = cache.StringGet("Message").ToString();
    
            // Get the client list, useful to see if connection list is growing...
            // Note that this requires allowAdmin=true in the connection string
            ViewBag.command5 = "CLIENT LIST";
            StringBuilder sb = new StringBuilder();
            var endpoint = (System.Net.DnsEndPoint)(await GetEndPointsAsync())[0];
            IServer server = await GetServerAsync(endpoint.Host, endpoint.Port);
            ClientInfo[] clients = await server.ClientListAsync();
    
            sb.AppendLine("Cache response :");
            foreach (ClientInfo client in clients)
            {
                sb.AppendLine(client.Raw);
            }
    
            ViewBag.command5Result = sb.ToString();
    
            return View();
        }
    
        private static long _lastReconnectTicks = DateTimeOffset.MinValue.UtcTicks;
        private static DateTimeOffset _firstErrorTime = DateTimeOffset.MinValue;
        private static DateTimeOffset _previousErrorTime = DateTimeOffset.MinValue;
    
        private static SemaphoreSlim _reconnectSemaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
        private static SemaphoreSlim _initSemaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
    
        private static ConnectionMultiplexer _connection;
        private static bool _didInitialize = false;
    
        // In general, let StackExchange.Redis handle most reconnects,
        // so limit the frequency of how often ForceReconnect() will
        // actually reconnect.
        public static TimeSpan ReconnectMinInterval => TimeSpan.FromSeconds(60);
    
        // If errors continue for longer than the below threshold, then the
        // multiplexer seems to not be reconnecting, so ForceReconnect() will
        // re-create the multiplexer.
        public static TimeSpan ReconnectErrorThreshold => TimeSpan.FromSeconds(30);
    
        public static TimeSpan RestartConnectionTimeout => TimeSpan.FromSeconds(15);
    
        public static int RetryMaxAttempts => 5;
    
        public static ConnectionMultiplexer Connection { get { return _connection; } }
    
        public static async Task InitializeAsync()
        {
            if (_didInitialize)
            {
                throw new InvalidOperationException("Cannot initialize more than once.");
            }
    
            _connection = await CreateConnectionAsync();
            _didInitialize = true;
        }
    
        // This method may return null if it fails to acquire the semaphore in time.
        // Use the return value to update the "connection" field
        private static async Task<ConnectionMultiplexer> CreateConnectionAsync()
        {
            if (_connection != null)
            {
                // If we already have a good connection, let's re-use it
                return _connection;
            }
    
            try
            {
                await _initSemaphore.WaitAsync(RestartConnectionTimeout);
            }
            catch
            {
                // We failed to enter the semaphore in the given amount of time. Connection will either be null, or have a value that was created by another thread.
                return _connection;
            }
    
            // We entered the semaphore successfully.
            try
            {
                if (_connection != null)
                {
                    // Another thread must have finished creating a new connection while we were waiting to enter the semaphore. Let's use it
                    return _connection;
                }
    
                // Otherwise, we really need to create a new connection.
                string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
                return await ConnectionMultiplexer.ConnectAsync(cacheConnection);
            }
            finally
            {
                _initSemaphore.Release();
            }
        }
    
        private static async Task CloseConnectionAsync(ConnectionMultiplexer oldConnection)
        {
            if (oldConnection == null)
            {
                return;
            }
            try
            {
                await oldConnection.CloseAsync();
            }
            catch (Exception)
            {
                // Ignore any errors from the oldConnection
            }
        }
    
        /// <summary>
        /// Force a new ConnectionMultiplexer to be created.
        /// NOTES:
        ///     1. Users of the ConnectionMultiplexer MUST handle ObjectDisposedExceptions, which can now happen as a result of calling ForceReconnectAsync().
        ///     2. Call ForceReconnectAsync() for RedisConnectionExceptions and RedisSocketExceptions. You can also call it for RedisTimeoutExceptions,
        ///         but only if you're using generous ReconnectMinInterval and ReconnectErrorThreshold. Otherwise, establishing new connections can cause
        ///         a cascade failure on a server that's timing out because it's already overloaded.
        ///     3. The code will:
        ///         a. wait to reconnect for at least the "ReconnectErrorThreshold" time of repeated errors before actually reconnecting
        ///         b. not reconnect more frequently than configured in "ReconnectMinInterval"
        /// </summary>
        public static async Task ForceReconnectAsync()
        {
            var utcNow = DateTimeOffset.UtcNow;
            long previousTicks = Interlocked.Read(ref _lastReconnectTicks);
            var previousReconnectTime = new DateTimeOffset(previousTicks, TimeSpan.Zero);
            TimeSpan elapsedSinceLastReconnect = utcNow - previousReconnectTime;
    
            // If multiple threads call ForceReconnectAsync at the same time, we only want to honor one of them.
            if (elapsedSinceLastReconnect < ReconnectMinInterval)
            {
                return;
            }
    
            try
            {
                await _reconnectSemaphore.WaitAsync(RestartConnectionTimeout);
            }
            catch
            {
                // If we fail to enter the semaphore, then it is possible that another thread has already done so.
                // ForceReconnectAsync() can be retried while connectivity problems persist.
                return;
            }
    
            try
            {
                utcNow = DateTimeOffset.UtcNow;
                elapsedSinceLastReconnect = utcNow - previousReconnectTime;
    
                if (_firstErrorTime == DateTimeOffset.MinValue)
                {
                    // We haven't seen an error since last reconnect, so set initial values.
                    _firstErrorTime = utcNow;
                    _previousErrorTime = utcNow;
                    return;
                }
    
                if (elapsedSinceLastReconnect < ReconnectMinInterval)
                {
                    return; // Some other thread made it through the check and the lock, so nothing to do.
                }
    
                TimeSpan elapsedSinceFirstError = utcNow - _firstErrorTime;
                TimeSpan elapsedSinceMostRecentError = utcNow - _previousErrorTime;
    
                bool shouldReconnect =
                    elapsedSinceFirstError >= ReconnectErrorThreshold // Make sure we gave the multiplexer enough time to reconnect on its own if it could.
                    && elapsedSinceMostRecentError <= ReconnectErrorThreshold; // Make sure we aren't working on stale data (e.g. if there was a gap in errors, don't reconnect yet).
    
                // Update the previousErrorTime timestamp to be now (e.g. this reconnect request).
                _previousErrorTime = utcNow;
    
                if (!shouldReconnect)
                {
                    return;
                }
    
                _firstErrorTime = DateTimeOffset.MinValue;
                _previousErrorTime = DateTimeOffset.MinValue;
    
                ConnectionMultiplexer oldConnection = _connection;
                await CloseConnectionAsync(oldConnection);
                _connection = null;
                _connection = await CreateConnectionAsync();
                Interlocked.Exchange(ref _lastReconnectTicks, utcNow.UtcTicks);
            }
            finally
            {
                _reconnectSemaphore.Release();
            }
        }
    
        // In real applications, consider using a framework such as
        // Polly to make it easier to customize the retry approach.
        private static async Task<T> BasicRetryAsync<T>(Func<T> func)
        {
            int reconnectRetry = 0;
            int disposedRetry = 0;
    
            while (true)
            {
                try
                {
                    return func();
                }
                catch (Exception ex) when (ex is RedisConnectionException || ex is SocketException)
                {
                    reconnectRetry++;
                    if (reconnectRetry > RetryMaxAttempts)
                        throw;
                    await ForceReconnectAsync();
                }
                catch (ObjectDisposedException)
                {
                    disposedRetry++;
                    if (disposedRetry > RetryMaxAttempts)
                        throw;
                }
            }
        }
    
        public static Task<IDatabase> GetDatabaseAsync()
        {
            return BasicRetryAsync(() => Connection.GetDatabase());
        }
    
        public static Task<System.Net.EndPoint[]> GetEndPointsAsync()
        {
            return BasicRetryAsync(() => Connection.GetEndPoints());
        }
    
        public static Task<IServer> GetServerAsync(string host, int port)
        {
            return BasicRetryAsync(() => Connection.GetServer(host, port));
        }
    
  4. I Solution Explorer expanderar du mappen Vyer > Delad. Öppna sedan filen _Layout.cshtml.

    Ersätt:

    @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
    

    med:

    @Html.ActionLink("Azure Cache for Redis Test", "RedisCache", "Home", new { area = "" }, new { @class = "navbar-brand" })
    

Lägga till en ny RedisCache-vy

  1. I Solution Explorer expanderar du mappen Vyer och högerklickar sedan på mappen Start. Välj Lägg till > vy....

  2. Ange RedisCache som vynamn i dialogrutan Lägg till vy. Välj Lägg till.

  3. Ersätt koden i filen RedisCache.cshtml med följande kod:

    @{
        ViewBag.Title = "Azure Cache for Redis Test";
    }
    
    <h2>@ViewBag.Title.</h2>
    <h3>@ViewBag.Message</h3>
    <br /><br />
    <table border="1" cellpadding="10">
        <tr>
            <th>Command</th>
            <th>Result</th>
        </tr>
        <tr>
            <td>@ViewBag.command1</td>
            <td><pre>@ViewBag.command1Result</pre></td>
        </tr>
        <tr>
            <td>@ViewBag.command2</td>
            <td><pre>@ViewBag.command2Result</pre></td>
        </tr>
        <tr>
            <td>@ViewBag.command3</td>
            <td><pre>@ViewBag.command3Result</pre></td>
        </tr>
        <tr>
            <td>@ViewBag.command4</td>
            <td><pre>@ViewBag.command4Result</pre></td>
        </tr>
        <tr>
            <td>@ViewBag.command5</td>
            <td><pre>@ViewBag.command5Result</pre></td>
        </tr>
    </table>
    

Köra appen lokalt

Som standard är projektet konfigurerat att vara värd för appen lokalt i IIS Express för testning och felsökning.

Köra appen lokalt

  1. I Visual Studio du Felsöka Starta felsökning för att skapa och starta > appen lokalt för testning och felsökning.

  2. Välj Azure Redis Cache for Redis-test i webbläsarens navigeringsfält.

  3. I exemplet nedan ser du att Message-nyckeln tidigare hade ett cachelagrat värde som angavs med Azure Cache for Redis-konsolen i portalen. Appen uppdatera det cachelagrade värdet. Appen körde även kommandona PING och CLIENT LIST.

    Enkelt test slutfört – lokalt

Publicera och köra i Azure

När du har testat appen lokalt distribuerar du den till Azure och kör den i molnet.

Publicera appen i Azure

  1. Högerklicka på projektnoden i Solution Explorer i Visual Studio. Välj sedan Publicera.

    Publicera

  2. Välj Microsoft Azure App Service, välj Skapa ny och välj sedan Publicera.

    Publicera i App Service

  3. Gör följande ändringar i dialogrutan Skapa App Service:

    Inställningen Rekommenderat värde Beskrivning
    Appens namn Använd standardvärdet. Appnamnet blir värdnamnet för appen när den har distribuerats till Azure. Namnet kan ha ett tidsstämpelsuffix som lagts till för att göra det unikt, om det behövs.
    Prenumeration Välj din Azure-prenumeration. Den här prenumerationen debiteras för eventuella relaterade värdkostnader. Om du har flera Azure-prenumerationer kontrollerar du att den önskade prenumerationen har valts.
    Resursgrupp Använd den resursgrupp som du skapade cachen i (till exempel TestResourceGroup). Resursgruppen hjälper dig att hantera alla resurser som en grupp. Senare när du vill ta bort appen är det bara att ta bort gruppen.
    App Service-plan Välj Nytt och skapa en ny App Service-Plan med namnet TestingPlan.
    Använd samma plats du använde när du skapade cachen.
    Välj Ledigt som storlek.
    En App Service-plan definierar en uppsättning beräkningsresurser för en webbapp att köra med.

    Dialogrutan App Service

  4. Välj Skapa när du har konfigurerat App Service-värdinställningarna.

  5. I fönstret Utdata i Visual Studio kan du se publiceringsstatus. När appen har publicerats loggas URL:en för appen:

    Publicera utdata

Lägga till appinställningen för cachen

Lägg till en ny appinställning när den nya appen har publicerats. Den här inställningen används för att lagra cacheanslutningsinformationen.

Lägga till appinställningen

  1. Skriv appnamnet i sökfältet längst upp i Azure-portalen för att söka efter den nya appen som du skapade.

    Leta efter appen

  2. Lägg till en ny appinställning med namnet CacheConnection som appen ska använda för att ansluta till cachen. Använd samma värde som du har konfigurerat för CacheConnection i filen CacheSecrets.config. Värdet innehåller cachens värdnamn och åtkomstnyckel.

    Lägga till appinställning

Köra appen i Azure

Gå till URL:en för appen i webbläsaren. URL:en visas i resultatet av publiceringsåtgärden i utdatafönstret i Visual Studio. Den finns också i Azure-portalen på översiktssidan för appen du skapade.

Testa cacheåtkomsten genom att välja Azure Cache for Redis-test i navigeringsfältet.

Enkelt test slutfört – Azure

Rensa resurser

Om du tänker fortsätta till nästa självstudie kan du behålla resurserna som du har skapat i den här självstudien och använda dem igen.

Om du är klar med exempelappen i snabbstarten kan du ta bort Azure-resurserna som du skapade i snabbstarten för att undvika kostnader.

Viktigt

Att ta bort en resursgrupp kan inte ångras. När du tar bort en resursgrupp tas alla resurser som ingår i den bort permanent. Kontrollera att du inte av misstag tar bort fel resursgrupp eller resurser. Om du har skapat resurserna som är värdar för det här exemplet i en befintlig resursgrupp som innehåller resurser som du vill behålla, kan du ta bort varje resurs individuellt till vänster i stället för att ta bort resursgruppen.

Ta bort en resursgrupp

  1. Logga in på Azure-portalen och välj Resursgrupper.

  2. Skriv namnet på din resursgrupp i rutan Filtrera efter namn.... Anvisningarna för den här artikeln använde en resursgrupp med namnet TestResources. På din resursgrupp i resultatlistan väljer du ... och sedan Ta bort resursgrupp.

    Ta bort

Du blir ombedd att bekräfta borttagningen av resursgruppen. Skriv namnet på din resursgrupp för att bekräfta och välj sedan Ta bort.

Efter en liten stund tas resursgruppen och de resurser som finns i den bort.

Nästa steg

I nästa kurs får du använda Azure Cache for Redis i ett mer realistiskt scenario så att du kan förbättra prestandan för en app. Du uppdaterar appen på cachens resultattavla med hjälp av cache-aside-mönstret med ASP.NET och en databas.