ASP.NET COREの1.1系で勉強がてら適当なアプリ作成していてSession Stateについて調べたので備忘です。
Azureで運用するのを前提にRedis CacheやDocument DB(今はもうCosmos DB)の利用も考えたのですが、そこまで大規模に利用するアプリでもないのでSQLDatabaseでSessionStateすることにしました。RedisCache高いよね・・・、メモリキャッシュだから仕方ないのは解っているのですが。
WebアプリをホストするサーバのInPorcでもいいっちゃいいんですがスケールアウトとか考えて後から実装するとなるとやはり面倒なのでせめてDB Stateかなと考えてSQLDatabaseを選択したわけです。メモリキャッシュよりは反応が遅くなりますが安価なので汎用性は高いです。
少し古い情報になるのですが下記の記事を参考に作成しています。
Working with a distributed cache
※この記事は2017年5月25日時点の情報となります。
前提
Visual Studio 2017でASP.NET Core Webアプリケーションを作成します。
作成時に「認証の変更」で「個別のユーザカウント」を選択してしてください。
Extensionの取得
nugetで下記のExtensionを取得してください。Sql Serverを利用したセッション管理機能を提供してくれるパッケージです。
Microsoft.Extensions.Caching.SqlServer
SQL Databaseにキャッシュテーブルを作成する
ここではSQLDatabaseの作成方法については割愛します。
とりあえず以下のDDLを実行してテーブルを作成します。作成したテーブルでログイン後のSession情報を管理コントロールします。
CREATE TABLE [dbo].[SQLSessions]( [Id] [nvarchar](449) NOT NULL, [Value] [varbinary](max) NOT NULL, [ExpiresAtTime] [datetimeoffset](7) NOT NULL, [SlidingExpirationInSeconds] [bigint] NULL, [AbsoluteExpiration] [datetimeoffset](7) NULL, CONSTRAINT [pk_Id] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO CREATE NONCLUSTERED INDEX [Index_ExpiresAtTime] ON [dbo].[SQLSessions] ( [ExpiresAtTime] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO
SQLServer Session Stateを実装する
Startup.csのConfigureServicesに下記を追加します。今回は接続文字列をユーザシークレット管理に記載して取得します。Configuration["ConnectionsString:DbConnection:embed:cite]の部分がユーザシークレット管理部分になりますのでここを接続文字列に置き換えるかユーザシークレット管理を利用するかしてください。
セッションタイムアウトの時間は30分を指定します。ここは明示的に指定していない場合は20分になります。
services.AddDistributedSqlServerCache(options => { options.ConnectionString = Configuration["ConnectionsString:DbConnection:embed:cite]; options.SchemaName = "dbo"; options.TableName = "SQLSessions"; ← 作成したセッション管理用テーブル名 }); services.AddSession(options => { options.CookieName = ".Session"; options.IdleTimeout = TimeSpan.FromMinutes(30); ← Sessionが切れるまでの時間の設定 });
次にStartup.csのConfigureにSessionの登録をします。
app.UseSession();
これで設定関連は完了です。
Sessionが切れた時に特定のページに遷移させる方法
セッションが切れたときの挙動については要件によって変わってくると思いますが大体ログインページに遷移すると思います。というわけでここではログインページに遷移する方法について記載します。
まずは以下の2つのクラスを実装します。
- HttpHelperの実装
Session情報はHttpContextに格納されるのですがHttpContextはどこからでもアクセスできるわけではないのでアクセサ用のインターフェースをきります。HttpHelperというフォルダを追加してHttpHelperクラスを作成し下記のコードを転写してください。
using Microsoft.AspNetCore.Http; namespace {ProjectName}.Helpers { public class HttpHelper { private static IHttpContextAccessor HttpContextAccessor; public static void Configure(IHttpContextAccessor httpContextAccessor) { HttpContextAccessor = httpContextAccessor; } public static HttpContext HttpContext { get { return HttpContextAccessor.HttpContext; } } } }
次にStartup.csのConfigureに下記のソースを追加しHttpHelperの利用登録をします。
HttpHelper.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
- カスタムアトリビュートの実装
セッションが切れた時の動作をカスタムアトリビュートとして実装します。カスタムアトリビュートで実装しておけばControllerにセットするだけで処理されるようになりますしSession管理が不要なControllerではセットしないようにすればいいので汎用性が高くなります。
Atrributesというフォルダを作成してCheckSessionOutAttributeクラスを作成します。処理として特定のSessionKey、ここでは「OperationTime」が存在するか確認してなければログインページに遷移するという処理になっています。
using {ProjectName}.Helpers; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Text; namespace {ProjectName].Attributes { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class CheckSessionOutAttribute : ActionFilterAttribute { byte[] value; if (!HttpHelper.HttpContext.Session.TryGetValue("OperationTime",out value)) { filterContext.Result = new RedirectResult($"~/Login/Index"); return; } base.OnActionExecuting(filterContext); } }
次にビジネスロジック側に実装を行います。
ログイン処理の部分でセッション追加の処理を記載します。ここではログイン時間をセッションにセットしています。
HttpContext.Session.SetString("LoginTime", DateTime.Now.ToString()); HttpContext.Session.SetString("OperationTime", DateTime.Now.ToString());
次にセッション管理を行いたいControllerにカスタムアトリビュートを実装すれば実装は完了です。
[CheckSessionOut] ← こんな感じに追加 public IActionResult Index() { return View(); }
セッションタイムアウトを1分にして確認してみましょう。
DBのセッションテーブルにセッション情報が書き込まれるので確認してください。デフォルトではSlide方式になっているので何かしらのアクションを起こせばSessionExpirationTimeが設定時間分延長して更新されます。ログインしてから何もせずにExpirationTimeを待ってアクションを起こしログイン画面に遷移すれば成功です。
これで冗長構成をもったセッション管理の仕組みをASP.NET Coreで利用することができます。