ASP.NET CoreにおけるHttpContext.Currentの利用方法(サービスインジェクションによる代替手法)

はじめに 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が中心的な設計原則となっており、多くの機能はすでに準備されており、それらをより適切な方法で利用するように設計されています。

タグ: ASP.NET Core HttpContext IHttpContextAccessor 依存性注入 AsyncLocal

5月29日 07:15 投稿