2011-11-29 6 views
9

Mam następującą konfigurację w moim modeluMożliwe jest dziedziczenie modeli w przypadku korzystania z mocno wpisanego widoku w 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 
    { 
    } 
} 

i moim zdaniem:

@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> 
} 

walidacja po stronie klienta jest włączone. Jeśli jednak wyślę obiekt typu Sender do widoku, sprawdzanie poprawności po stronie klienta nie wykryje, że pole Adres 1 jest wymagane. Czy istnieje sposób sprawdzania poprawności klienta w tym scenariuszu?

PS: odkryłem, że walidacja klient działa, jeśli mogę użyć następujących czynności, aby wyświetlić pole Address1 w widoku:

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

Nadawca jest osobą, ale osoba nie jest nadawcą, twój widok jest ściśle wpisany na osobę, dlatego nigdy nie wykryje niczego, co ma związek z nadawcą. – Maess

+0

Właściwie jeśli dodasz to do widoku: Model.GetType(). ToString() zobaczysz, że wyświetlane jest następujące: QuickTest.Models.Sender, co oznacza, że ​​typ jest znany podczas renderowania widoku. – pacu

+0

Jednak EditorFor() będzie traktować jako typ, do którego masz silny wpis, który jest osobą. – Maess

Odpowiedz

8

Możesz dostosować walidatory i metadane, aby pochodziły z konkretnej klasy, ale rozwiązanie zawiera kilka ruchomych części, w tym dwa niestandardowe dostawcy metadanych.

Najpierw utwórz niestandardową Attribute, aby ozdobić każdą właściwość klasy bazowej. Jest to konieczne jako flaga dla naszych niestandardowych dostawców, aby wskazać, kiedy potrzebna jest dalsza analiza. Jest to atrybut:

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

Następnie utworzyć niestandardowy ModelMetadataProvider dziedziczenie z DataAnnotationsModelMetadataProvider:

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); 
    } 
} 

Następnie utworzyć niestandardowy ModelValidatorProvider dziedziczenie z DataAnnotationsModelValidatorProvider:

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(); 
    } 
} 

potem zarejestrować zarówno niestandardowi dostawcy w Application_Start w Global.asax.cs:

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

Teraz zmień swoje modele tak:

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 
{ 
} 

Zauważ, że klasa bazowa ma nową właściwość, ConcreteType. To będzie używane do wskazania, która klasa dziedzicząca utworzyła tę klasę podstawową. Ilekroć klasa dziedzicząca ma metadane, które przesłania metadane w klasie bazowej, konstruktor klasy dziedziczącej powinien ustawić właściwość ConcreteType klasy podstawowej.

Teraz, mimo że twój widok korzysta z klasy bazowej, atrybuty specyficzne dla każdej konkretnej klasy dziedziczącej pojawią się w twoim widoku i wpłyną na walidację modelu.

Ponadto, powinieneś być w stanie przekształcić widok w szablon dla typu Person i użyć szablonu dla każdej instancji korzystającej z klasy bazowej lub dziedziczącej z niej.

+0

var propertyField = target.GetType(). GetField ("właściwość"); Do czego służy ta linia? – Sergey

+0

A co jest dla target.GetType(). GetField ("vdi") – Sergey

1

Hmm, to jest kłopotliwe, ponieważ metoda HtmlHelper<T>.EditorFor używa rodzajowego parametr HtmlHelper<T>, aby dowiedzieć się, które atrybuty sprawdzania poprawności są wymagane.

Proponuję napisać własną metodę rozszerzenia EditorFor, która deleguje wywołania do nietypowej metody HtmlHelper.Editor.

0

Czy rozważałeś stworzenie własnego EditorTemplate dla osoby, nadawcy i odbiorcy? EditorFor i DisplayFor szukają niestandardowego szablonu, który pasuje do typu obiektu.

Metoda wewnętrzna wyszuka szablon zgodny z typem obiektu. Następnie będzie szukać szablonu, który pasuje do klasy bazowej, a następnie do łańcucha dziedziczenia.

+1

Tak, zrobiłem, ale w moim przypadku różnice między Nadawcą a Odbiorcą nie są znaczące, więc nie wydaje mi się, aby istniało dwa razy taki sam widok tylko ze względu na zmianę dyrektywy @model na górze. – pacu

Powiązane problemy