以前こんな記事を書いたときにASP.NET Coreでの開発で辛いことの一つで、『HttpContext.Currentがない』なんてことを書きましたが、ないなら作ればいいじゃないの精神で作ってみました!
まぁ実際なくても個人的にはそこまで困ることもなかったんですが、需要なんてあるのかしら的なことを言ったら@neuecc先生にこんなこと言われたので作った次第です。
AspNetCoreCurrentRequestContext
という名前でライブラリを作成しました。
パッケージはNuGet、ソースはGithubで公開しています。
- NuGet - AspNetCoreCurrentRequestContext
- Github - ttakahari/AspNetCoreCurrentRequestContext
使い方
NuGetからパッケージをインストールします。
PM > Install-Package AspNetCoreCurrentRequestContext
ASP.NET Coreアプリケーションのプロジェクトにパッケージの参照を追加したらStartupクラスのConfigureメソッドで、IApplicationBuilderにCurrentRequestContextMiddlewareを追加します。
標準で拡張メソッドを用意しているのでそれを使った方が楽です。
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // ミドルウェアを追加
        app.UseCurrentRequestContext();
    }
}
ミドルウェアが追加できたら後は好きな箇所でHttpContext.Currentを呼ぶだけ、って言いたいところですが現在の.NET言語の機能に静的プロパティを拡張する機能がないため、新しくAspNetCoreHttpContextというクラスを用意しています。
このクラスのCurrentという静的プロパティを呼び出すことで、これまでのASP.NETと同様に現在のリクエストのHttpContextを取得することが出来ます。
using AspNetCoreCurrentRequestContext; // using追加は必須!!
public class Foo
{
    public string GetRequestUrl()
    {
        // このプロパティで現在のHttpContextを取得できる
        var context = AspNetCoreHttpContext.Current;
        return context.Request.Path.Value;
    }
}
ただしCurrentRequestContextMiddlewareの後に追加されたミドルウェアではいつでもAspNetCoreHttpContext.CurrentでHttpContextを取得できますが、それよりも前に追加されたミドルウェア内では取得できないことに注意して下さい。
内部実装
内部実装も至ってシンプルで実際のコード行数は100行もありません。
@neueccさんが作成したOWINで使えるHttpContext.CurrentのライブラリはCallContextという、同じ実行コンテキストを持つスレッド間でデータを共有することが出来るクラスを使用して実装していましたが、.NET Core向けライブラリのcorefxにはCallContextがないため使用できません。
その代わりに、.NET Framework 4.6で追加されたAsyncLocal<T>というクラスを使用しています。
このAsyncLocal<T>はスレッド単位のストレージとして使用することが出来ます。
子スレッドが派生したとしても親スレッドのストレージを参照することができ、子スレッドで値を書き換えた場合は子スレッドのストレージのみが書き換えられ、親スレッドのストレージに影響を及ぼすことはありません。
public class Program
{
    public static void Main(string[] args)
    {
        var storage = new AsyncLocal<int>();
        storage.Value = 1;
        Console.WriteLine($"Parent - {storage.Value}"); // 1
        new Thread(_ =>
        {
            Console.WriteLine($"Child - {storage.Value}"); // 1
        }).Start();
        new Thread(_ =>
        {
            storage.Value = 2;
            Console.WriteLine($"Child - {storage.Value}"); // 2
        }).Start();
        Console.WriteLine($"Parent - {storage.Value}"); // 1
        Console.ReadLine();
    }
}
AspNetCoreCurrentRequestContextではCurrentReuqestContextMiddlewareでAspNetCoreHttpContext内に保持されているAsyncLocal<HttpContext>の変数に値を代入していて、以降はその変数の値を返すようにしています。
まとめ
AspNetCoreCurrentRequestContextを使えば、リクエスト内の統計情報やリクエスト中だけキャッシュしたい値のやり取りが楽になると思います。
これまでASP.NETでHttpContext.Currentに頼っていたためにASP.NET Coreに壁を感じていた方でも、ASP.NET Coreアプリケーションの新規作成、または既存の移行のハードルが下がってくれたら何よりです。
