しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC 3 で追加された AdditionalMetadata 属性を使う

恥ずかしながら激しく見逃していたのですが、ASP.NET MVC 3 のソースコードを読んでいた時に発見したので調べました。ちゃんとリリースノートにも書いてあったんですね…。

New "AdditionalMetadataAttribute" Class

You can use the AdditionalMetadata attribute to populate the ModelMetadata.AdditionalValues dictionary for a model property. For example, if a view model has a property that should be displayed only to an administrator, you can annotate that property as shown in the following example:

public class ProductViewModel
{
    [AdditionalMetadata("AdminOnly", true)]
    public string RefundCode {get; set;}
}

This metadata is made available to any display or editor template when a product view model is rendered. It is up to you to interpret the metadata information.

ASP.NET MVC 3 | The ASP.NET Site

といったように、モデルに対してメタデータを追加することが出来る属性で、Display 属性やそれに類似の属性では付加出来ない情報をモデルに付けるための手段です。引用例では管理者のみ RefundCode プロパティを使えるという感じですかね。

例ではモデルのプロパティに対してメタデータを付加していましたが、下の例のようにモデルクラス自体にも付加することが出来ます。

[AdditionalMetadata("foo", "bar")]
public class SampleViewModel
{
    [AdditionalMetadata("hau", "hauhau")]
    public string Property1 { get; set; }
}

ちなみにここで指定したメタデータは、ModelMetadata.AdditionalValues コレクションに格納されます。ModelMetadata というのはモデルクラスや、プロパティ毎に内部で作成されるクラスで、検証などに関連した重要な情報が含まれています。

ではどうやってメタデータを利用するかですが、ModelMetadata クラスに FromLambdaExpression メソッドと FromStringExpression メソッドが用意されているので、これで ModelMetadata のインスタンスを取得して AdditionalValues コレクションから属性で指定した名前で値を取得します。

実際に先程のモデルクラスのメタデータを取得するには以下のように書くだけです。

@* モデルクラスのメタデータを取得する *@
@ModelMetadata.FromLambdaExpression(model => model, ViewData).AdditionalValues["foo"]

@* プロパティのメタデータを取得する *@
@ModelMetadata.FromLambdaExpression(model => model.Property1, ViewData).AdditionalValues["hau"]

誰が見ても使いにくいと思うでしょうね。なのでサクッと HTML ヘルパーを作成して使いやすくしてしまいましょう。

ModelMetadata クラスにラムダ式と文字列で取得するメソッドがそれぞれ用意されているので、作成するヘルパーもラムダ式と文字列の両方で指定できるようにします。

public static class MetadataExtensions
{
    public static object Metadata(this HtmlHelper htmlHelper, string expression, string key)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        return ModelMetadata.FromStringExpression(expression, htmlHelper.ViewData).AdditionalValues[key];
    }

    public static object MetadataFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string key)
    {
        return ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).AdditionalValues[key];
    }
}

これで Html.Metadata/Html.MetadataFor というヘルパーが使えるようになりました。下の例のように書いて使ってください。

@* モデルクラスのメタデータを取得する *@
@Html.Metadata("", "foo")
@Html.MetadataFor(model => model, "foo")

@* プロパティのメタデータを取得する *@
@Html.Metadata("Property1", "hau")
@Html.MetadataFor(model => model.Property1, "hau")

パッと使い道が浮かばないのですが、プロパティ毎に何らかの情報をビュー側に渡したい時に便利かもしれないですね。