読者です 読者をやめる 読者になる 読者になる

xin9le.net

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

DynamicなTempData

Advent Calendar MVC

ASP.NETが大好きでたまらない.NETギークな皆さん、この年末をいかがお過ごしでしょうか。僕はと言えば、衆議院選挙の絡みで#携帯職人#携帯インストラクターとしてASP.NETどころかプログラミングと絶縁無縁な日々を過ごしております(白目。プログラミング、したいですね。

僕がWebプログラミングを本格的に始めたのは、ほぼHokuriku.NET ASP.NET MVC入門からです。@miso_soup3さん、@herara_ofnir3さんをはじめ、関係各者には大変感謝しておりますし、お世話になっておりますm( )m 本日はOne ASP.NET Advent Calendar 2012の13日目。そんなデビューしたてのヒヨッ子ですが、広い心でお付き合いください。

ちょっと前フリ

ASP.NET MVCにはViewDataプロパティというViewとController間で便利に使える入れ物があります。これはIDictionary<string, object>型でできており、以下のように利用します。

public class HogeController : Controller
{
    public ActionResult Fuga()
    {
        this.ViewData["π"] = Math.PI;
        return this.View();
    }
}
<div>@this.ViewData["π"]</div>

しかしながら、これではキーを文字列で指定する必要があります。「そんなのヤダ!!」という至極当然な気持ちを解決するためか、もっとシンプルに実装するためにDynamicObjectを継承したViewBagプロパティが用意されています。これは次のように利用します。

public class HogeController : Controller
{
    public ActionResult Fuga()
    {
        this.ViewBag.π = Math.PI;
        return this.View();
    }
}
<div>@this.ViewBag.π</div>

あたかもπプロパティがあるかのように記述できるため、とっても綺麗です。また、ViewBagは内部的にViewDataを利用しているためそれぞれに互換性があり、双方混合した利用も可能です。

DynamicなTempData

Hokuriku.NET ASP.NET MVC 入門2でのことでした。TempDataという一回のRequest/Response間で利用できる一時データの入れ物があることを教わりました。が、これは前述のViewDataと同じようにIDictionary<string, object>なコレクションとして提供されており、キーを文字列で指定しなければなりません。「じゃあViewBagに対応するモノを使おうじゃないか!」と思うのですが、ありません。TempBag、ないんです。じゃあ「ないものは作っちゃえ」ということで今日の本題、dynamicに解決するTempDataです。

実装

実装方針はいたって簡単で、「基本的にViewBagを真似する」です。TempDataの本体であるTempDataDictionary型のインスタンスを内包するようにDynamicTempDataDictionaryクラスを実装します。

using System.Collections.Generic;
using System.Dynamic;

namespace System.Web.Mvc
{
    internal sealed class DynamicTempDataDictionary : DynamicObject
    {
        private readonly TempDataDictionary tempData = null;

        public DynamicTempDataDictionary(TempDataDictionary data)
        {
            this.tempData = data;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return this.tempData.Keys;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = this.tempData[binder.Name];
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            this.tempData[binder.Name] = value;
            return true;
        }
    }
}

さらに、使いやすくするために以下のような拡張メソッドを作成します。AsDynamicメソッドは簡単で、DynamicTempDataDictionaryのインスタンス生成をラップしたものです。TempBagはホントはプロパティにしたいのですが、さすがに外部からは介入できないので拡張メソッドで実装しています。TempDataプロパティが実装されている型全てでTempBagメソッドを利用できるように用意しました。

いちいちusingして拡張メソッドを有効にするのは面倒なので、名前空間をSystem.Web.Mvcにしておきました。これで基本的にusingなしで利用できます。

using System;

namespace System.Web.Mvc
{
    public static class TempDataExtensions
    {
        public static dynamic AsDynamic(this TempDataDictionary data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            return new DynamicTempDataDictionary(data);
        }

        public static dynamic TempBag(this ControllerBase controller)
        {
            if (controller == null)
                throw new ArgumentNullException("controller");
            return controller.TempData.AsDynamic();
        }

        public static dynamic TempBag(this ViewContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            return context.TempData.AsDynamic();
        }

        public static dynamic TempBag(this ViewMasterPage page)
        {
            if (page == null)
                throw new ArgumentNullException("page");
            return page.TempData.AsDynamic();
        }

        public static dynamic TempBag(this ViewPage page)
        {
            if (page == null)
                throw new ArgumentNullException("page");
            return page.TempData.AsDynamic();
        }

        public static dynamic TempBag(this ViewResultBase result)
        {
            if (result == null)
                throw new ArgumentNullException("result");
            return result.TempData.AsDynamic();
        }

        public static dynamic TempBag(this ViewUserControl control)
        {
            if (control == null)
                throw new ArgumentNullException("control");
            return control.TempData.AsDynamic();
        }

        public static dynamic TempBag(this WebViewPage page)
        {
            if (page == null)
                throw new ArgumentNullException("page");
            return page.TempData.AsDynamic();
        }
    }
}

利用例

これでほぼほぼViewBagのように利用することができるようになりました。TempDataをご利用の際は、ぜひ試してみてください。

public class HogeController : Controller
{
    public ActionResult Fuga()
    {
        this.TempBag().π = Math.PI;
        return this.View();
    }
}
<!-- プロパティではなく拡張メソッドなので注意 -->
<div>@this.TempBag().π</div>

ライブラリ化

大したものではないですが、せっかくなのでCodePlexなどに載せようかと思ったりしています。実際はASP.NETプロジェクトにプロパティ形式として組み込んで貰いたいので、そういう働きかけもしてみようかなー。(などと大それたことを言ってみる