はじめに ASP.NET Coreは、従来のMVC5やASP.NET WebFormsに基づく大幅なリファクタリングを経て構築された最新のフレームワークです。旧バージョンで利用可能だったHttpContext.Currentは、現在のASP.NET Coreでは直接使用できません。これは、該当するAPIが存在しないことを意味します。 Controller内からHttpContextにアクセスすることは可能ですが、特定の状況下ではHttpContext.Currentのような使いやすさを維持することが困難です。
IHttpContextAccessorの活用 ASP.NET Coreの依存性注入コンテナ機能を使用して、リクエスト時にIHttpContextAccessorインターフェースを取得することで、HttpContext.Currentのような動作を模倣することが可能です。ただし、デフォルトではIHttpContextAccessorはDIコンテナによって管理されていないため、最初にServiceCollectionに登録する必要があります:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// サービスコンテナを他のクラスへ渡す
demoCore.DAL.HttpContext.ServiceProvider = services;
// その他のコード...
}
上記のように設定することで、HttpContext.Currentと同様の操作が可能になります:
namespace WebCore.Utils
{
public static class HttpContext
{
public static IServiceCollection ServiceProvider;
public static Microsoft.AspNetCore.Http.HttpContext Current
{
get
{
var provider = ServiceProvider.BuildServiceProvider();
var accessor = provider.GetService<IHttpContextAccessor>();
return accessor.HttpContext;
}
}
}
}
この実装において、staticなServiceProviderフィールドが初期化されていないため、StartupクラスのConfigureServicesメソッドで適切に設定する必要があります:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
WebCore.Utils.HttpContext.ServiceProvider = services;
// その他のコード...
}
これにより、ASP.NET Coreでも従来のASP.NETと同様にWebCore.Utils.HttpContext.Currentを利用できるようになります。
スレッドセーフについて HttpContext.Currentの話題には、マルチスレッド環境での問題点がつきものです。従来のASP.NETでは、複数スレッドが同時に処理される場合、HttpContext.Currentがnullになる可能性がありました。その解決策として、CallContextが提案されていました。
CallContextは、呼び出しコンテキストに属するスレッドローカルストレージのような仕組みであり、各論理実行スレッドに対して固有のデータスロットを提供します。これらのデータスロットは異なる論理スレッド間では共有されません。実行パスを通じてCallContextが伝播し、個々のオブジェクトによってチェックされる際に、オブジェクトを追加できます。
ASP.NETにおいては、スレッドプールのスレッドが再利用されるものの、CallContextは論理スレッド毎に保持されるTLS(スレッドローカルストレージ)であるため、物理的に同じスレッドが再利用されてもデータは共有されません。これは、データベース接続プールが非管理リソースを保持するのと似ています。つまり、異なるスレッドが同じ物理スレッドを使用しても、それぞれのCallContextデータスロットは独立しています。
一方、ThreadStaticAttributeは、物理スレッドのTLSにデータを格納するため、再利用されたスレッド内で同一の値を参照できます。
.NET Coreでは、AsyncLocal<T>という新しいAPIも提供されています。
HttpContextAccessorの内部実装 ASP.NET CoreにおけるIHttpContextAccessorの実装を見てみましょう:
public class HttpContextAccessor : IHttpContextAccessor
{
#if NET451
private static readonly string LogicalDataKey = "__HttpContext_Current__" + AppDomain.CurrentDomain.Id;
public HttpContext HttpContext
{
get
{
var handle = CallContext.LogicalGetData(LogicalDataKey) as ObjectHandle;
return handle?.Unwrap() as HttpContext;
}
set
{
CallContext.LogicalSetData(LogicalDataKey, new ObjectHandle(value));
}
}
#elif NETSTANDARD1_3
private AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();
public HttpContext HttpContext
{
get
{
return _httpContextCurrent.Value;
}
set
{
_httpContextCurrent.Value = value;
}
}
#endif
}
ASP.NET CoreではDIが中心的な設計原則となっており、多くの機能はすでに準備されており、それらをより適切な方法で利用するように設計されています。