English 中文(简体)
ASP.NET MVC - Form Returns Null Model Unless Model is Wrapped in a Custom ViewModel
原标题:

I have a pair of views in my application that both display the same Editor Template for one of my model items; of the two views ("Add" and "Edit"), "Edit" works fine, but "Add" is returning null for the model when my controller action handles the post.

I found that if I give the "Add" view a custom ViewModel and call Html.EditorFor(p => p.PageContent) rather than simply calling the EditorFor() on the whole Model object- Html.EditorFor(p => p), then the form returns the correct, non-null model, but that generates other problems pertaining to my client-side scripting and control IDs (as now all of the fields are prefixed with "PageContent_"). I am using the same Editor Template technique in a few different places throughout my application and none of the others are exhibiting this odd dependency on a ViewModel.

Has anyone else ever experienced similar problems?

Edit View

<%@ 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 (Working)

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

Add View

<%@ 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 (Failing)

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

Before you cry "duplicate"

This question does relate to this one, but this question is intended to target the specific problem I am experiencing rather than the more general question asked there.

最佳回答

I tracked down the problem and it s a rather interesting one.

When the DefaultModelBinder attempts to resolve a model item one of the first things it does is check to see if there are any prefixed fields in the data being bound; it does this by checking for any form items that begin with the name of the model object (this seems extremely arbitrary, if you ask me). If any "prefixed" fields are found then it results in different binding logic being invoked.

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

The controller action I defined to handle the Add action defines a PageContent item called "content" and in my domain PageContent has a property called "Content" which "matched" with the model name of "content" thus causing the DefaultModelBinder to assume I had a prefixed value when in fact it was simply a member of PageContent. By changing the signature-

from:

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

to:

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

The DefaultModelBinder was once again able to correctly bind to my PageContent model item. I m not sure why the Edit view didn t also display this behavior, but either way I ve tracked down the source of the issue.

It seems to me that this issue falls very close to "bug" status. It makes sense that my view worked initially with the ViewModel because "content" was getting prefixed with "PageContent_", but a core framework feature/bug like this ought not be unaddressed IMHO.

问题回答

暂无回答




相关问题
WebForms and ASP.NET MVC co-existence

I am trying to make a WebForms project and ASP.NET MVC per this question. One of the things I ve done to make that happen is that I added a namespaces node to the WebForms web.config: <pages ...

Post back complex object from client side

I m using ASP.NET MVC and Entity Framework. I m going to pass a complex entity to the client side and allow the user to modify it, and post it back to the controller. But I don t know how to do that ...

Create an incremental placeholder in NHaml

What I want to reach is a way to add a script and style placeholder in my master. They will include my initial site.css and jquery.js files. Each haml page or partial can then add their own required ...

asp.net mvc automapper parsing

let s say we have something like this public class Person { public string Name {get; set;} public Country Country {get; set;} } public class PersonViewModel { public Person Person {get; ...

structureMap mocks stub help

I have an BLL that does validation on user input then inserts a parent(PorEO) and then inserts children(PorBoxEO). So there are two calls to the same InsertJCDC. One like this=>InsertJCDC(fakePor)...

ASP.NET MVC: How should it work with subversion?

So, I have an asp.net mvc app that is being worked on by multiple developers in differing capacities. This is our first time working on a mvc app and my first time working with .NET. Our app does not ...

System.Web.Mvc.Controller Initialize

i have the following base controller... public class BaseController : Controller { protected override void Initialize(System.Web.Routing.RequestContext requestContext) { if (...

热门标签