xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Web APIでリクエスト単位のキャッシュを利用する

ASP.NET Web APIで開発している際、パフォーマンスを上げるためにリクエスト中で取得/生成した値をキャッシュして使い回したいケースがあります。特にWeb APIにはHTTP Message Handlerという機構があるので、外側のレイヤー (Handler) で共通処理として認証を行ったりアクセスログを出力したりすることが多々あります。このとき取得したユーザー情報などをキャッシュしておけば、実処理である内側のコントローラーで効率的に再利用することができるようになります。パフォーマンスは良くて困ることはないですし、しかも簡単に実装できるのでオススメです。

HttpContextを取得する

まず、ASP.NET Web APIのリクエスト情報であるHttpRequestMessageからHttpContextを取得します。これはPropertiesプロパティに "MS_HttpContext" というキーでobject型として格納されています。どこかに拡張メソッドとして定義してしまうと良いでしょう。これが今回のキモです。...というか、ぶっちゃけ大事なのはこれだけです。

public static class HttpRequestMessageExtensions
{
    public static HttpContextBase GetHttpContext(this HttpRequestMessage request)
    {
        if (request == null)
            throw new ArgumentNullException("request");

        var key = "MS_HttpContext";
        if (!request.Properties.ContainsKey(key))
            throw new InvalidOperationException("HttpContext is not available.");

        return request.Properties[key] as HttpContextBase;
    }
}

キャッシュとして値を格納する

HttpContextが取得できたらあとは簡単。HttpContextBaseItemsプロパティに値を入れるだけです。

public class CacheHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var httpContext = request.GetHttpContext();
        var userName = "xin9le";  //--- DBなどから取得したことにする
        httpContext.Items["UserName"] = userName; 
        return base.SendAsync(request, cancellationToken);
    }
}

当然ですが、使うときはキー指定で値を取り出すだけです。

public class DefaultController : ApiController
{
    public string Get()
    {
        var httpContext = this.Request.GetHttpContext();
        var userName = (string)httpContext.Items["UserName"];
        return string.Format("Hello, {0}", userName);
    }
}

この取得/設定の処理をアプリケーション単位で隠蔽しておくと、より利便性が向上すると思います。