2009-11-30 12 views
4

J'ai une paire de vues dans mon application qui affichent le même modèle d'éditeur pour l'un de mes éléments de modèle; des deux vues ("Ajouter" et "Modifier"), "Modifier" fonctionne correctement, mais "Ajouter" renvoie null pour le modèle lorsque mon action de contrôleur gère la publication.ASP.NET MVC - La forme renvoie un modèle nul à moins que le modèle ne soit enveloppé dans un ViewModel personnalisé

Je trouve que si je donne le « Ajouter » voir une coutume ViewModel et appeler Html.EditorFor(p => p.PageContent) plutôt que d'appeler simplement le EditorFor() sur l'ensemble du modèle object- Html.EditorFor(p => p), alors la forme renvoie le modèle correct, non nul, mais Cela génère d'autres problèmes liés à mes identifiants de script et de contrôle côté client (comme maintenant tous les champs sont préfixés avec "PageContent_"). J'utilise la même technique de modèle d'éditeur dans quelques endroits différents dans mon application et aucun des autres ne présente cette dépendance étrange sur un ViewModel.

Est-ce que quelqu'un d'autre a déjà rencontré des problèmes similaires?

Modifier Voir

<%@ Page Title="" Language="C#" MasterPageFile="~/Areas/Admin/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<PageContent>" %> 

<% using (Html.BeginForm()) 
    { %> 
<%=Html.Hidden("PageID", Model.Page.ID) %> 
<%=Html.EditorFor(p => p)%> 
<input type="submit" name="btnSave" value="Save" /> 
<input type="submit" name="btnCancel" value="Cancel" class="cancel" /> 
<% } 

action (travail)

[HttpPost, ValidateInput(false)] 
public ActionResult EditContent(int id, FormCollection formCollection) {} 

Ajouter Voir

<%@ Page Title="" Language="C#" MasterPageFile="~/Areas/Admin/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<PageContent>" %> 

<% using (Html.BeginForm()) 
    { %> 
<%=Html.Hidden("PageID", ViewData["PageID"]) %> 
<%=Html.EditorFor(p => p)%> 
<input type="submit" name="btnSave" value="Save" /> 
<input type="submit" name="btnCancel" value="Cancel" class="cancel" /> 
<% } %> 

action (défaut)

// content is ALWAYS null 
[HttpPost, ValidateInput(false)] 
public ActionResult AddContent(PageContent content, FormCollection formCollection) {} 

Avant de pleurer "dupliquer"

Cette question ne se rapporte à this one, mais cette question est destinée à cibler le problème spécifique que je suis expérience plutôt que la question plus générale posée là.

+0

Essayez de supprimer le 'FormCollection'. Ce n'est pas nécessaire. –

+0

Corrigez-moi si je me trompe mais n'avez-vous pas besoin du préfixe pour être là pour que le classeur par défaut fonctionne? Ou avez-vous oublié de définir l'attribut Bind sur un paramètre? – Min

+0

@Daniel: J'utilise la fonction FormCollection pour vérifier si un bouton "Annuler" est cliqué, ainsi que des informations supplémentaires qui n'appartiennent pas au modèle. J'ai utilisé la même signature sur presque toutes les actions d'ajout/édition jusqu'ici sans faute. –

Répondre

14

J'ai repéré le problème et c'est plutôt intéressant. Lorsque DefaultModelBinder tente de résoudre un élément de modèle, l'une des premières choses qu'il vérifie est de vérifier s'il existe des champs préfixés dans les données liées; il le fait en vérifiant pour tous les éléments de formulaire qui commencent par le nom de l'objet de modèle (cela semble extrêmement arbitraire, si vous me demandez). Si des champs "préfixés" sont trouvés, cela entraîne l'invocation d'une logique de liaison différente.

ASP.NET MVC 2 Preview 2 bindModel() Source

public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { 
    if (bindingContext == null) { 
     throw new ArgumentNullException("bindingContext"); 
    } 
    bool performedFallback = false; 
    if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName)) { 
     // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back 
     // to the empty prefix. 
     if (bindingContext.FallbackToEmptyPrefix) { 
      /* omitted for brevity */ 
      }; 
      performedFallback = true; 
     } 
     else { 
      return null; 
     } 
    } 

    // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string)) 
    // or by seeing if a value in the request exactly matches the name of the model we're binding. 
    // Complex type = everything else. 
    if (!performedFallback) { 
     /* omitted for brevity */ 
    } 
    if (!bindingContext.ModelMetadata.IsComplexType) { 
     return null; 
    } 
    return BindComplexModel(controllerContext, bindingContext); 
} 

L'action du contrôleur je définissais pour gérer l'action Ajouter définit un élément de PageContent appelé "contenu" et dans mon domaine PageContent a une propriété appelé "Content" qui "correspondait" avec le nom de modèle de "content" provoquant ainsi le DefaultModelBinder à supposer que j'avais une valeur préfixée alors qu'en fait c'était simplement un membre de PageContent.En changeant le Signature-

de:

[HttpPost, ValidateInput(false)] 
public ActionResult AddContent(PageContent content, FormCollection formCollection) {} 

à:

[HttpPost, ValidateInput(false)] 
public ActionResult AddContent(PageContent pageContent, FormCollection formCollection) {} 

Le DefaultModelBinder était une nouvelle fois capable de se lier correctement à mon élément de modèle PageContent. Je ne suis pas sûr pourquoi la vue Modifier n'affichait pas également ce comportement, mais de toute façon j'ai suivi la source du problème.

Il me semble que ce problème est très proche de l'état "bug". Il est logique que ma vue a travaillé initialement avec le ViewModel parce que le "contenu" était préfixé avec "PageContent_", mais une fonctionnalité de base/un bug comme celui-ci ne devrait pas être sans réponse à mon humble avis.

+1

Les conventions par leur nature sont arbitraires. Je pense que j'ai eu un problème similaire avec MVC donc je trier les noms et les préfixes. Cela me donne sérieusement envie de pouvoir utiliser Linq pour tout. – Min

+1

Quand vous garantissez que personne ne l'aurait eu, je suis honnête en disant que ce scénario m'a traversé la tête quand je vous ai demandé de poster le maquillage de la classe PageContent ;-) En tout cas, heureux que vous l'ayez compris :-) – Charlino

+0

@Charlino Façon de défier les attentes haha ​​ –