2011-11-29 10 views
9

Ho la seguente configurazione nel mio modello:È possibile l'ereditarietà del modello quando si utilizza la visualizzazione con caratteri forti in 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 
    { 
    } 
} 

e, a mio avviso:

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

validazione lato client è abilitato. Tuttavia, se invio un oggetto di tipo Sender alla vista, la convalida sul lato client non rileva che è richiesto il campo Address1. C'è un modo per far funzionare la validazione del cliente in questo scenario?

PS: ho scoperto che convalida del client funziona se uso il seguente per visualizzare il campo Address1 nella vista:

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

Il mittente è una persona, ma la persona non è un mittente, la tua vista è fortemente digitata a persona, quindi, non verrà mai rilevato alcunché che abbia a che fare con il mittente. – Maess

+0

In realtà se si aggiunge questo alla vista: Model.GetType(). ToString() vedrai che viene visualizzato quanto segue: QuickTest.Models.Sender che significa che il tipo è noto quando la vista è renderizzata. – pacu

+0

Tuttavia, EditorFor() sta per trattare è come il tipo che hai fortemente digitato, che è persona. – Maess

risposta

8

È possibile personalizzare i validatori ei metadati che provengono dalla classe concreta, ma la soluzione presenta diverse parti mobili, inclusi due provider di metadati personalizzati.

Innanzitutto, creare un numero personalizzato Attribute per decorare ciascuna proprietà della classe base. Questo è necessario come indicatore per i nostri fornitori personalizzati, per indicare quando è necessaria un'ulteriore analisi. Questo è l'attributo:

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

successivo, creare un personalizzato ModelMetadataProvider eredita da 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); 
    } 
} 

Quindi, creare un costume ModelValidatorProvider eredita da 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(); 
    } 
} 

Dopo di che, registrarsi entrambi i fornitori personalizzati in Application_Start in Global.asax.cs:

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

Ora, cambiare i vostri modelli in questo modo:

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

Si noti che la classe di base ha una nuova proprietà, ConcreteType. Questo sarà usato per indicare quale classe ereditaria ha istanziato questa classe base. Ogni volta che una classe ereditaria ha metadati che sovrascrivono i metadati nella classe base, il costruttore della classe ereditante deve impostare la proprietà ConcreteType della classe base.

Ora, anche se la vista usa la classe base, gli attributi specifici di qualsiasi classe di derivazione concreta appariranno nella vista e influenzeranno la convalida del modello.

Inoltre, si dovrebbe essere in grado di trasformare la vista in un modello per il tipo Persona e utilizzare il modello per qualsiasi istanza che utilizza la classe di base o che ne eredita.

+0

var propertyField = target.GetType(). GetField ("proprietà"); A cosa serve questa linea? – Sergey

+0

E che cosa è per target.GetType(). GetField ("vdi") – Sergey

1

Hmm, questo è un ingannevole in quanto il metodo HtmlHelper<T>.EditorFor utilizza il parametro generico di il HtmlHelper<T> per capire quali attributi di convalida sono richiesti.

Suggerisco di scrivere il proprio metodo di estensione EditorFor che delega le chiamate al metodo HtmlHelper.Editor non generico.

0

Hai pensato di creare il tuo EditorTemplate personalizzato per Persona, Mittente e Destinatario? EditorFor e DisplayFor cercano un modello personalizzato che corrisponda al tipo dell'oggetto.

Il metodo interno cercherà un modello che corrisponda al tipo dell'oggetto. Quindi cercherà un modello che corrisponda alla classe base e quindi alla catena dell'ereditarietà.

+1

Sì, ma nel mio caso le differenze tra Sender e Receiver non sono significative, quindi non penso che ci sia un punto in cui avere la stessa vista due volte solo per il gusto di cambiare la direttiva @model in cima ad essa. – pacu

Problemi correlati