あるSEのつぶやき・改

ITやシステム開発などの技術に関する話題を、取り上げたりしています。

ASP.NET Coreのセッション情報をRedisに格納する方法

概要

ASP.NET Core のセッション情報を Redis に格納する方法を調べてみました。

開発環境は、macOS Mojave と .NET Core 2.2 SDK(v2.2.100-preview2)、Visual Studio Code になります。

まずは、Mac 単体で動作確認後、AWS(Amazon Web Services)の EC2 の Web サーバー2台 + ロードバランサー(L4スイッチ) + ElastiCache(Redis)で動作確認をします。

Redis を Mac にインストール

まず、Redis を Mac にインストールします。

Windows でも Chocolatey をインストールすることで Redis の動作を確認できるようです。

$ brew install redis

Redis を起動します。

$ brew services start redis

ASP.NET Core プロジェクトの作成

ASP.NET Core MVC プロジェクトを RedisTest という名前で作成します。

$ dotnet new mvc -o RedisTest

Redis に必要なパッケージを Nuget からインストールします。

$ cd RedisTest
$ dotnet add package Microsoft.Extensions.Caching.Redis --version 2.2.0-preview2-35157 
$ dotnet add package Microsoft.AspNetCore.DataProtection.Redis --version 2.2.0-preview2-35157
$ dotnet add package Microsoft.AspNetCore.Session --version 2.2.0-preview2-35157 
$ dotnet restore

ASP.NET Core プロジェクトの修正

できあがったひな形を Redis が動作するように修正します。

Program.csを以下のように修正します。

//追加
using System.Net;

//(中略)

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(options =>
            {
                options.Listen(IPAddress.Loopback, 5001);
            })
            .UseUrls("http://*:5001/")  
            .UseStartup<Startup>();

appsettings.Development.jsonに Redis の設定を行います。

{
  "ConnectionStrings": {
    "Redis": "localhost"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Startup.csを以下のように修正します。

services.AddSession(), services.AddDistributedRedisCache, app.UseSession() が追加になっています。また、SetApplicationNameでアプリケーション名を指定します。これを指定しないと Web サーバー間でセッションが共有できないためご注意ください。

//追加
using Microsoft.AspNetCore.DataProtection;
using StackExchange.Redis;

//(中略)

   public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });


        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        //追加
        services.AddSession();

        //追加
        services.AddDistributedRedisCache(o =>
        {
            o.Configuration = Configuration.GetConnectionString("Redis");
        });

        //セッションを共有するための設定
        var redis = ConnectionMultiplexer.Connect(Configuration.GetConnectionString("Redis"));
            services.AddDataProtection()
                .SetApplicationName("AppName")  //重要!!
                .PersistKeysToRedis(redis, "DataProtection-Keys");
        
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            //app.UseHsts();
        }

        //app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();

        //セッションを有効にする
        app.UseSession();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

HomeController.csの About メソッドを以下のように修正し、Redis にセッションデータを格納/取得するようにします。

    public async Task<IActionResult> About()
    {
        //セッション情報をRedisからロードする
        await HttpContext.Session.LoadAsync();

        //セッションから値を取得する
        string storedValue = HttpContext.Session.GetString("TestValue");
        if (storedValue == null)
        {
            //データが存在しない場合はデータをセットする
            storedValue = "Testing session in Redis. Time of storage: " + DateTime.Now.ToString("s");
            HttpContext.Session.SetString("TestValue", storedValue);
            //Redisにデータを格納する
            await HttpContext.Session.CommitAsync();
        }
        ViewData["Message"] = "Value in session: " + storedValue;

        return View();
    }

About.cshtmlは以下のようになっています。

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

Mac 単体で動作確認

準備ができたので、ビルド&実行し、http://127.0.0.1:5001にブラウザでアクセスします。

$ dotnet build
$ dotnet run

問題なく表示できました。

f:id:fnyablog:20181006114647p:plain:w480

AWS で動作確認

では、AWS で実際に2台の Web サーバー間で Redis を使ってセッションが共有されるか動作確認をします。

Web サーバーは CentOS 7 を使用し、nginx と.NET Core SDK をセットアップします。

nginx と ASP.NET Core の Web サーバーである Kestrel は、5001番ポートを介してリバースプロキシで HTTP サービスを提供するようにします。

ロードバランサーは Network Load Balancer(L4スイッチ)で作成し、ElastiCache で Redis を使用します。

これらのセットアップは、これだけで記事になるため詳細は割愛しますが、興味ある方は調べてみてください。

AWS へデプロイするために以下の修正を行います。

appsettings.json に Redisのエンドポイントを設定します。

{
  "ConnectionStrings": {
    "Redis": "Redisのエンドポイント(ポート番号なし)"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Web サーバー1号機と2号機の区別が付くようにAbout.cshtmlを修正します。

  • 1号機
@{
    ViewData["Title"] = "About";
}

<h1>Web Server 1</h1>
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
  • 2号機
@{
    ViewData["Title"] = "About";
}

<h1>Web Server 2</h1>
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

準備ができたらロードバランサーへアクセスを行います。

最初は、Web サーバー1号機が表示されました。

f:id:fnyablog:20181006114708p:plain:w480

しばらくすると、Web サーバー2号機が表示されましたが、同じ時刻を表示していますね。

f:id:fnyablog:20181006114723p:plain:w480

これで問題なく、Redis で複数の Web サーバー間のセッションが共有されていることが確認できました。

参考サイト