2010-02-16 14 views
7

J'ai ce que je pense être une situation quelque peu normale où j'ai besoin de lier des messages de formulaire à un modèle "d'ordre". Ce modèle a quelques niveaux d'information à ce:DefaultModelBinder Problème avec les niveaux imbriqués + autres classeurs

Order.Billing.FirstName 
Order.Billing.Address.City 
Order.Billing.Address.Country 

Utilisation du DefaultModelBinder, si je poste une forme à une action qui prend ce modèle de commande comme param, les champs suivants JustWork (TM):

<%=Html.TextBox("Billing.FirstName")%> 
<%=Html.TextBox("Billing.Address.City")%> 

Ce champ ne:

<%=Html.TextBox("Billing.Address.Country")%> 

la ride que j'ai est la propriété de campagne. Dans notre cas, Address.Country renvoie une instance de classe Country (logique ISO2/3/Name/Code). Ce n'est pas une chaîne. Pas surprenant que cela ne fonctionne pas par défaut.

Ma première pensée a été de créer un CountryModelBinder (hériter de DefaultModelBinder) et ModelBinders.Binders.Add au type de Country. Quand je fais cela, CountryModelBinder n'est jamais appelé dans le scénario ci-dessus.

Ma deuxième pensée était de créer un AddressModelBinder (hériter de DefaultModelBinder) et le lier à notre type d'adresse. Pendant que cela est appelé, l'appel SetProperty pour "Pays" a une valeur vide, même si le formulaire a affiché un champ appelé "facturation.Address.Country". Après un peu de bricolage, il apparaît que le comportement de liaison de modèle appelle uniquement CreateModel lorsque le modèle est la classe de niveau supérieur de l'action, et que tous les autres binders ont leur propriété BindPropery/SetProperty appelée pour les propriétés enfants. En d'autres termes, si je crée des classeurs de modèle pour Order, OrderAddress (facturation), Address et Country. Pour l'action qui prend une commande, seul OrderModelBinder.CreateModel est appelé. ORderAddress et Address.BindProperty/SetProperty sont appelés pour certaines choses, et parfois l'argument de valeur SetProperty est vide lorsqu'il est clairement affiché dans un nom qui correspond aux autres mappages de propriétés de champ.

Il est assez facile de simplement ajouter du code à OrderModelBinder pour extraire le champ Billing.Address.Country de Request.Form. Mais j'ai plusieurs modèles qui utilisent l'adresse et qui font tous sembler cassés.

Qu'est-ce qui me manque ici? Existe-t-il un moyen d'appeler le CountryModelBinder dans ce cas? Je pense que le CountryModelBinder devrait être appelé lorsque Billing.Address.Country est mappé à la propriété Country du classeur d'adresses.

+0

cela pourrait vous aider: http://stackoverflow.com/questions/2462506/model-binding-with-nested-child-models-and-partialviews-in-asp-net-mvc – Will

+0

Je suis le même problème avec une structure de modèle imbriquée, il me semble que les propriétés au niveau supérieur et un niveau inférieur sont liées, mais tout ce qui est inférieur à cela semble être ignoré. Est-ce le comportement normal du classeur modèle? Semble un comportement plutôt arbitraire. – UpTheCreek

Répondre

0

J'ai essayé de faire ce que vous avez fait ici, apparemment sur MVC3 cela fonctionne effectivement si je fournis un modèle de classeur pour ce type.

Ceci est juste une preuve de concept pour montrer qu'il fonctionne, et ne doit pas être considérée comme même proche du code de niveau de production:

Modèles:

public class SimpleModel 
    { 
     public string Value { get; set; } 
     public int Other { get; set; } 
    } 

    public class ComplexModel 
    { 
     public SimpleModel Complexity {get;set;} 
     public string StrVal { get; set; } 
    } 

certains liant:

public class MBinder : IModelBinder 
     { 
      public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
      { 
       if (bindingContext.ModelType == typeof(SimpleModel)) 
       { 
        var simpleModel= new SimpleModel(); 
        simpleModel.Other = 1; 
        simpleModel.Value = controllerContext.HttpContext.Request.Form["Complexity"]; 

        return cm; 
       } 
       return null; 
      } 
     } 

dans asax global:

ModelBinders.Binders.Add(typeof (SimpleModel), new MBinder()); 

code Voir:

@model ComplexModel 

    @using (Html.BeginForm()) 
{ 
    <fieldset> 
     @Html.LabelFor(x => x.Complexity) 
     @Html.TextBoxFor(x => x.Complexity) 
    </fieldset> 

    <fieldset> 
     @Html.LabelFor(x => x.StrVal) 
     <br /> 
     @Html.EditorFor(x => x.StrVal) 
    </fieldset> 
    <input type="submit" /> 
} 

Controller:

public ActionResult Index() 
     { 
      return View(); 
     } 

     [HttpPost] 
     public ActionResult Index(ComplexModel model) 
     { 
      return RedirectToAction("Index"); 

     } 

BTW dans MVC 3 une meilleure option serait d'utiliser l'interface IModelBinderProvider, mais je voulais juste montrer quelque chose qui fonctionnerait.