ASP.NET CoreでもHttpContext.Current使えるようにしてみた

以前[こんな記事](http://qiita.com/taiga_takahari/items/240a4b0453990db4005f)を書いたときにASP.NET Coreでの開発で辛いことの一つで、『[```HttpContext.Current```がない](http://qiita.com/taiga_takahari/items/240a4b0453990db4005f#httpcontext)』なんてことを書きましたが、ないなら作ればいいじゃないの精神で作ってみました！

まぁ実際なくても個人的にはそこまで困ることもなかったんですが、需要なんてあるのかしら的なことを言ったら[@neuecc先生](https://twitter.com/neuecc)にこんなこと言われたので作った次第です。

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/834225ec-ab2a-75fa-0dd7-c9a967247287.png)


# AspNetCoreCurrentRequestContext

という名前でライブラリを作成しました。

パッケージはNuGet、ソースはGithubで公開しています。

* NuGet - [AspNetCoreCurrentRequestContext](https://www.nuget.org/packages/AspNetCoreCurrentRequestContext/)
* Github - [ttakahari/AspNetCoreCurrentRequestContext](https://github.com/ttakahari/AspNetCoreCurrentRequestContext)

## 使い方

NuGetからパッケージをインストールします。

```ps1
PM > Install-Package AspNetCoreCurrentRequestContext
```

ASP.NET Coreアプリケーションのプロジェクトにパッケージの参照を追加したら```Startup```クラスの```Configure```メソッドで、```IApplicationBuilder```に```CurrentRequestContextMiddleware```を追加します。

標準で拡張メソッドを用意しているのでそれを使った方が楽です。

```csharp
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // ミドルウェアを追加
        app.UseCurrentRequestContext();
    }
}
```

ミドルウェアが追加できたら後は好きな箇所で```HttpContext.Current```を呼ぶだけ、って言いたいところですが現在の.NET言語の機能に静的プロパティを拡張する機能がないため、新しく```AspNetCoreHttpContext```というクラスを用意しています。

このクラスの```Current```という静的プロパティを呼び出すことで、これまでのASP.NETと同様に現在のリクエストの```HttpContext```を取得することが出来ます。

```csharp
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のライブラリ](https://www.nuget.org/packages/OwinRequestScopeContext/)は```CallContext```という、同じ実行コンテキストを持つスレッド間でデータを共有することが出来るクラスを使用して実装していましたが、.NET Core向けライブラリのcorefxには```CallContext```がないため使用できません。

その代わりに、.NET Framework 4.6で追加された```AsyncLocal<T>```というクラスを使用しています。

この```AsyncLocal<T>```はスレッド単位のストレージとして使用することが出来ます。

子スレッドが派生したとしても親スレッドのストレージを参照することができ、子スレッドで値を書き換えた場合は子スレッドのストレージのみが書き換えられ、親スレッドのストレージに影響を及ぼすことはありません。

```csharp
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アプリケーションの新規作成、または既存の移行のハードルが下がってくれたら何よりです。