2010-11-26 28 views
4

Je cherche à utiliser la méthode UpdateModel pour une sous-classe récupérée lors de l'exécution, ce serait génial si quelqu'un pouvait faire la lumière si je fais un hachage total de celui-ci et/ou si oui ou non ce que j'essaie de faire est possible. J'utilise une action générique pour contrôler la validation d'un groupe de vues partielles; J'essaie d'éviter une action spécifique par vue partielle.MVC UpdateModel et sous-classes par rapport à la classe de base

Chaque vue partielle a un modèle unique qui dérive d'un modèle de base:

public class ModelA : ModelBase{ 
    [Required] 
    public string SomeStringProperty{get;set;} 
... 
} 
public class ModelB : ModelBase{ 
    [Required] 
    public DateTime? SomeDateProperty{get;set;} 
... 
} 
public class ModelBase{ 
    public Guid InstanceId{get;set;} 
} 

J'utilise le FormCollection sur l'action pour obtenir les éléments de formulaire soumis et leurs valeurs, ce qui comprend le type de modèle que la vue doit utiliser pour valider sa demande. Ignorer les conséquences sur la sécurité de ce pour cet exemple, je suis au courant et c'est une preuve interne uniquement concept

[HttpPost] 
    public ActionResult ChangeCaseState(int id, FormCollection formCollection) 
    { 
     Guid instanceId = new Guid(formCollection["instanceId"]); 
     string modelType = formCollection["modelType"]; 
     //Return a specific Model class based on the event/modelType 
     var args = GetStateModelClass(modelType, instanceId); 

     try 
     { 
      UpdateModel(args); 
      if(Model.IsValid){ 
      ... 
     } 
     catch (Exception) 
     { 
      return View("~/Views/Shared/StateForms/" + modelType + ".ascx", args); 
     }... 

Et voici le code que je utilise pour retourner une classe en fonction des sous le modelType transmis au contrôleur.

private static ModelBase StateModelClassFactory(string stateModelTypeName, Guid instanceId) 
     { 
      switch (stateModelTypeName) 
      { 
       case "modelTypeA": 
        return new ModelA(workflowInstanceId); 
       case "modelTypeB": 
        return new ModelB(workflowInstanceId); 
    ... 
    } 

Parce que le type de retour de la méthode StateModelClassFactory est de la classe de base, même si je suis en fait le retour d'une classe Sub, le classeur modèle utilisé par la méthode UpdateModel se fixe seulement contre les valeurs dans la classe de base.

Des idées sur la façon dont je peux résoudre ce problème?

MISE À JOUR:

J'ai créé un modèle client Binder:

public class CustomModelBinder : DefaultModelBinder 
    { 
     public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
      { 

et caractérisées par le nouveau modèle Binder à la bonne classe de base pour voir ce qui se passait un peu plus sous le capot:

ModelBinders.Binders.Add(typeof(ModelBase), new CaseController.CustomModelBinder()); 

Lorsque je débogue le classeur de modèle et inspecte le bindingContext, la propriété Model représente le bon Sous-classe, mais la propriété ModelType est celle de la classe de base. Devrais-je envisager de changer le ModelType dans la méthode BindModel? Si tel est le cas, le setter sur le ModelType semble avoir été redondant. J'ai également remarqué que SomeDateProperty de la classe Sub est actaully dans la propriété PropertyMetadata .... semble si proche de se comporter comme je le voudrais.

Répondre

2

Donc je pense que j'ai résolu mon problème. Fondamentalement en raison de la façon dont je récupère la classe Model avant d'appeler le UpdateModel, le Model Binder lie la BaseClass même si le Model était celui de la SubClass - c'est le code que j'ai utilisé pour résoudre mon problème particulier:

public class SubClassModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     var model = bindingContext.Model; 
     var metaDataType = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType()); 
     bindingContext.ModelMetadata = metaDataType; 
     bindingContext.ModelMetadata.Model = model; 

     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

Et dans le Global.asax:

ModelBinders.Binders.Add(typeof(ModelBase), new SubClassModelBinder()); 

Merci à Darin pour son pointeur inital.

+0

+1. Merci de partager votre solution. –

0

Pour résoudre ce problème, vous pouvez écrire un classeur de modèle personnalisé pour le type de base qui, en fonction de la valeur de la propriété string, renvoie l'instance enfant correcte.

+0

Merci Darin, j'ai eu un jeu rapide à ce que je pense est le début de votre solution et mis à jour la question. – Tr1stan

5

Je viens de croiser cette question et a constaté qu'une approche plus générale serait juste de jeter votre modèle à dynamic en passant dans UpdateModel:

[HttpPost] 
public ActionResult ChangeCaseState(int id, FormCollection formCollection) 
{ 
    ...try 
    { 
     UpdateModel((dynamic)args);//!!notice cast to dynamic here 
     if(Model.IsValid){ 
     ... 
    } 
    catch... 

Cela semble mettre toutes les propriétés disponibles de mon type , indépendamment de savoir si ma variable est delcared avec le type de base.

Il y a un élément de travail déposé dans CodePlex pour ce numéro: http://aspnet.codeplex.com/workitem/8277?ProjectName=aspnet

+0

Merci. J'utilisais .net 3.5 (tag ajouté à la question). Je ne connais pas encore le mot clé dynamique, car je n'ai encore travaillé sur aucun site Web .net 4.0, je vais essayer si c'est toujours un problème. – Tr1stan

+0

Belle trouvaille, j'ai rencontré ce même problème lors de l'utilisation de TryUpdateModel plutôt que de UpdateModel et la conversion en dynamique a fait l'affaire. – blins

+0

Epouse-moi! Merci, j'étais en enfer avant de rencontrer ce post. x – ctrlplusb