2011-11-29 10 views
9

は、私は私のモデルでは、次のセットアップがあります。MVC3で強く型付けされたビューを使用すると、モデルの継承が可能ですか?

namespace QuickTest.Models 
{ 
    public class Person 
    { 
     [Required] 
     [Display(Name = "Full name")] 
     public string FullName { get; set; } 

     [Display(Name = "Address Line 1")] 
     public virtual string Address1 { get; set; } 
    } 
    public class Sender : Person 
    { 
     [Required] 
     public override string Address1 { get; set; } 
    } 
    public class Receiver : Person 
    { 
    } 
} 

をし、私の見解では:

@model QuickTest.Models.Person 
@{ 
    ViewBag.Title = "Edit"; 
} 
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> 

@using (Html.BeginForm()) { 
    <fieldset> 
     <legend>Person</legend> 
     <div class="editor-label"> 
      @Html.LabelFor(model => model.FullName) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.FullName) 
      @Html.ValidationMessageFor(model => model.FullName) 
     </div> 
     <div class="editor-label"> 
      @Html.LabelFor(model => model.Address1) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.Address1) 
      @Html.ValidationMessageFor(model => model.Address1) 
     </div> 

     <div class="errors"> 
      @Html.ValidationSummary(true) 
     </div> 
     <p> 
      <input type="submit" value="Save" /> 
     </p> 
    </fieldset> 
} 

クライアント側の検証が有効になっています。ただし、送信者タイプのオブジェクトをビューに送信すると、クライアント側の検証では、Address1フィールドが必要であることが検出されません。このシナリオでクライアント検証作業を行う方法はありますか?

PS:私はビューにアドレス1フィールドを表示するには、次の使用している場合は、クライアントの検証が機能することを発見し :HtmlHelper<T>.EditorFor方法は以下の一般的なパラメータを使用しているため

<div class="editor-field"> 
    @Html.Editor("Address1", Model.Address1) 
    @Html.ValidationMessageFor(model => model.Address1) 
</div> 
+0

送信者は人ですが、送信者ではないため、あなたの表示は人に強く入力されるため、送信者とは何の関係も検出しません。 – Maess

+0

実際には、これをビューに追加すると、Model.GetType()。ToString()が表示されます。QuickTest.Models.Senderは、ビューのレンダリング時にその型がわかっていることを示します。 – pacu

+0

しかし、EditorFor()はあなたが強く型付けした型であるpersonを扱います。 – Maess

答えて

8

バリデータとメタデータを具体的なクラスからカスタマイズすることはできますが、ソリューションには2つのカスタムメタデータプロバイダを含むいくつかの可動部分があります。

まず、カスタムクラスAttributeを作成して、基本クラスの各プロパティをデコレートします。これは、追加の解析がいつ必要になるかを示すために、カスタムプロバイダのフラグとして必要です。 DataAnnotationsModelValidatorProviderから継承するカスタムModelValidatorProvider作成し、その後

public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider 
{ 
    protected override ModelMetadata CreateMetadata(
     IEnumerable<Attribute> attributes, 
     Type containerType, 
     Func<object> modelAccessor, 
     Type modelType, 
     string propertyName) 
    { 
     var attribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) as BaseTypeAttribute; 
     if (attribute != null && modelAccessor != null) 
     { 
      var target = modelAccessor.Target; 
      var containerField = target.GetType().GetField("container"); 
      if (containerField == null) 
      { 
       var vdi = target.GetType().GetField("vdi").GetValue(target) as ViewDataInfo; 
       var concreteType = vdi.Container.GetType(); 
       return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName); 
      } 
      else 
      { 
       var container = containerField.GetValue(target); 
       var concreteType = container.GetType(); 
       var propertyField = target.GetType().GetField("property"); 
       if (propertyField == null) 
       { 
        concreteType = base.GetMetadataForProperties(container, containerType) 
         .FirstOrDefault(p => p.PropertyName == "ConcreteType").Model as System.Type; 
        if (concreteType != null) 
         return base.GetMetadataForProperties(container, concreteType) 
          .FirstOrDefault(pr => pr.PropertyName == propertyName); 
       } 
       return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName); 
      } 
     } 
     return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); 
    } 
} 

[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)] 
public class BaseTypeAttribute : Attribute { } 

次に、DataAnnotationsModelMetadataProviderから継承したカスタムModelMetadataProviderを作成します。これは属性である。その後

public class MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider 
{ 
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) 
    { 
     List<ModelValidator> vals = base.GetValidators(metadata, context, attributes).ToList(); 

     var baseTypeAttribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) 
      as BaseTypeAttribute; 

     if (baseTypeAttribute != null) 
     { 
      // get our parent model 
      var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model, 
       metadata.ContainerType); 

      // get the concrete type 
      var concreteType = parentMetaData.FirstOrDefault(p => p.PropertyName == "ConcreteType").Model; 
      if (concreteType != null) 
      { 
       var concreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model, 
        Type.GetType(concreteType.ToString())); 

       var concretePropertyMetadata = concreteMetadata.FirstOrDefault(p => p.PropertyName == metadata.PropertyName); 

       vals = base.GetValidators(concretePropertyMetadata, context, attributes).ToList(); 
      } 
     } 
     return vals.AsEnumerable(); 
    } 
} 

を登録両方のカスタムプロバイダがGlobal.asax.csのApplication_Startにあります。

ModelValidatorProviders.Providers.Clear(); 
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider()); 
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider(); 

さて、そのようなあなたのモデルを変更:基底クラスがConcreteTypeは、新しいプロパティを持っていることを

public class Person 
{ 
    public Type ConcreteType { get; set; } 

    [Required] 
    [Display(Name = "Full name")] 
    [BaseType] 
    public string FullName { get; set; } 

    [Display(Name = "Address Line 1")] 
    [BaseType] 
    public virtual string Address1 { get; set; } 
} 

public class Sender : Person 
{ 
    public Sender() 
    { 
     this.ConcreteType = typeof(Sender); 
    } 

    [Required] 
    [Display(Name = "Address Line One")] 
    public override string Address1 { get; set; } 
} 

public class Receiver : Person 
{ 
} 

注意を。これは、継承するクラスがこの基本クラスをインスタンス化したことを示すために使用されます。継承クラスが基本クラスのメタデータをオーバーライドするメタデータを持つ場合、継承するクラスのコンストラクタは基本クラスConcreteTypeプロパティを設定する必要があります。

ビューで基本クラスを使用しても、継承している任意のクラスに固有の属性がビューに表示され、モデルの検証に影響します。

さらに、ビューをPerson型のテンプレートに変換し、ベースクラスを使用するインスタンスまたはそのインスタンスを継承するインスタンスに対してテンプレートを使用することができます。

+0

var propertyField = target.GetType()。GetField( "property"); この行は何ですか? – Sergey

+0

そして、target.GetType()のためのものです。GetField( "vdi") – Sergey

1

うーん、これはトリッキーなものですHtmlHelper<T>を使用して、どの検証属性が必要かを判断します。

非ジェネリックなHtmlHelper.Editorメソッドへの呼び出しを委譲する独自のEditorFor拡張メソッドを作成することをお勧めします。

0

Person、Sender、およびReceiver用に独自のEditorTemplateを作成することを検討しましたか? EditorForおよびDisplayForは、オブジェクトのタイプに一致するカスタムテンプレートを探します。

内部メソッドは、オブジェクトのタイプに一致するテンプレートを探します。次に、基本クラスと一致するテンプレートを探し、継承のチェーンを上にします。

+1

はい、私のケースでは、SenderとReceiverの違いは重要ではないので、@modelディレクティブを変更するために同じビューを2度表示するという点はありません。 – pacu

関連する問題