メモ: C#のカスタムAttributeの作り方

昨日初めて使ったのでメモしておきます。

カスタムAttributeを作る目的としては、次のものが多いみたいですね。

  • Javaでいうところのマーカーアノテーション
  • カスタムバリデーション
  • エラーメッセージのカスタマイズ

カスタムAttributeクラスの作り方

  1. System.Attributeを継承するクラスを作成する
  2. そのクラスにAttributeUsageAttributeを付けて、ターゲットを指定する
Attributeに引数がない時

マーカーアノテーション的に使うようなときはこちらです。クラスの中身はカラッポでOK。

[System.AttributeUsage(System.AttributeTargets.Property]
public class FooAttribute : System.Attribute
{
}
Attributeに引数がある時

一層上に抽象クラスを定義してあげると便利なようです。

public abstract class AbstractAttribute : System.Attribute
{
    public abstract void Process(ModelMetadata data);
}

それを継承してカスタムAttributeを作成します。

[System.AttributeUsage(System.AttributeTargets.Property]
public class FooAttribute : AbstractAttribute
{
    private string bar;

    public FooAttribute(string bar)
    {
        this.bar = bar;
    }
    
    public override void Process(ModelMetadata data)
    {
        // たとえば
        data.AdditionalValues.Add("Foo", bar);
    }
}

カスタムAttributeを付けたモノへの処理の書き方

ケースバイケースですが、ASP.NET MVC 4を使うなら、System.Web.Mvc.DataAnnotationsModelMetadataProviderクラスを継承したカスタムプロバイダで、CreateMetadataメソッドをオーバーライドして、次のように書けます。

引数がないカスタムAttributeのとき
  protected override ModelMetadata CreateMetadata(
    IEnumerable<Attribute> attributes,
    Type containerType,
    Func<object> modelAccessor,
    Type modelType,
    string propertyName)
  {
    var data = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    var foo = attributes.SingleOrDefault(a => a is FooAttribute);
    if(foo != null){
        // FooAttributeが付いていた場合にやりたいこと
        // data = ...
    }
    return data;
  }
引数があるカスタムAttributeのとき
  protected override ModelMetadata CreateMetadata(
    IEnumerable<Attribute> attributes,
    Type containerType,
    Func<object> modelAccessor,
    Type modelType,
    string propertyName)
  {
    var data = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    attributes.OfType<FooAttribute>().ToList().ForEach(x => x.Process(data));
    return data;
  }