MVC model binding to nested classes
Asked Answered
B

4

8

When I try and create a new 'Flow' class the nested classes ('Action') always come back as null in the controller

So I have classes within classes like so:

public class Flow
{
    private Action actionField
    private string nameField
    private bool enabledField
    ...
}

public class Action
{
    private ActionSchedule actionScheduleField
    private ActionParameter actionParameterField
    private nameField
}
public class ActionSchedule
...

And a single create view for a 'Flow'

@model ProjectZeus.Models.Flow
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

   @Html.TextBoxFor(model => model.name, new { @placeholder = "Flow name" })
   @Html.ValidationMessageFor(model => model.name)


   @Html.LabelFor(model => model.enabled)

   @Html.EditorFor(model => model.enabled)
   @Html.ValidationMessageFor(model => model.enabled)

@Html.Partial("FlowAction")
...

and then partial views for each of the subclasses

@model ProjectZeus.Models.FlowAction

    @Html.TextBoxFor(model => model.name, new { @placeholder = "Action name" })
    ...

I've tried creating instances of the classes and calling the view - error,

I've tried creating instances of the classes in the views themselves - error,

I've tried not using PartialViews:

@Html.TextBoxFor(model => model.action.name, new { @placeholder = "Action name" })

I've googled and googled and googleedddd but with no luck, help please!?

Edit:

Implementing a customer model binder seems like overkill. This page describes the same problem but the solution code won't compile for me ‘The name ‘helper’ does not exist in the current context’? - http://danielhalldev.wordpress.com/2013/08/23/partial-views-and-nested-mvc-model-binding/

Edit2:

I changed the model defintions for brevity - the model is actually auto generated from an xsd:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class D53ESBFlow
{

    private D53ESBFlowAction actionField;

    [Required]
    private string nameField;

    ...

    private bool enabledField;

    /// <remarks/>
    public D53ESBFlowAction action
    {
        get
        {
            return this.actionField;
        }
        set
        {
            this.actionField = value;
        }
    }
    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string name
    {
        get
        {
            return this.nameField;
        }
        set
        {
            this.nameField = value;

Edit 3 (bump):

It looks like the 'binder'is creating a property and not a class object? whereRu

Br answered 11/7, 2014 at 13:24 Comment(10)
Action is not a nested class, but a property within Flow class.Drawers
Action seems to have the property nameField; not name. Also Flow has actionField, not ActionAngling
I've changed some of the names/signatures, the model classes were autogenerated from an XSD. Could that be an issue!?Br
What I mean is, that if you bind your view to a Flow model and flow has an Action property called "actionField", you should called your partial this way: @Html.Partial("FlowAction", Model.actionField) and not @Html.Partial("FlowAction", Model.action) since Flow has no property called "action".Angling
why are they private? Set them as public with accessors public Action actionField { get; set; } since it's a model which is used to transport data, you want to be able to access this data from outside the model itself...Briareus
And obviously that too ;) Didn't even notice that.Angling
Sorry, updated the question. I'm actually just calling @Html.Partial with the viewname alone and not passing a model.Br
I suggest you create a ViewModel. Things will be much much easierAngling
What types would I include in the viewmodel, wouldn't i just have the same problem?Br
Whatever you need; if you only need the name of the action, just put it in your model as a string called ActionName. Handle the data manipulation on the backend and give yourself all the freedom in the frontend.Angling
B
4

Did you forget the { get; set; } accessors on the property names?

Burk answered 19/9, 2016 at 0:49 Comment(1)
facepalm that was it!Anatomist
I
1

I had a similar issue with MVC 5, .NET 4.5, Visual Studio 2013.

Here's what worked for me: Add a constructor so the contained class gets instantiated, make them properties (not variables) like AntoineLev said, and add the class to the Binding:

public class Flow
{
    public Action actionField {get; set; }

    public class Flow()
    {
        actionField = new Action();  // otherwise it shows up as null
    }
}

In your controller, Add the the whole class in the binding:

public ActionResult Create([Bind(Include="action,name,enabled")] Flow flow)
{
   ... 
}

Your Mileage may vary.

}

Instill answered 21/8, 2014 at 2:30 Comment(4)
Interesting, just seen that 'Bind(Include=' syntax in an auto-generated mvc5 controller. Won't that only work for the first nested class though?Br
@Br Just tested nested classes. Surprisingly, those get mapped correctly as well. I had to add the constructor for the nested class, and add {get; set;} for the properties of the bottom class. Even more awesomly, it picks up a List<T> of that T class.Instill
Great answer, but just a quick note: if you're going to Bind the whole class, then you don't need [Bind(Include="blah blah blah")]. It binds everything by default.Gymnastics
Bind attribute is useful for securing the method. It will only accept the following properties of the Flow object -hackers can attempt to inject extra fields to the object- so it's recommended from that perspective. But it should not affect the binding in your case.Unideaed
B
0

I ended up going through the request response and mapping all the properties individually by name:

flow.action = new D53ESBFlowAction
    {
        name = Request["action.name"],
        ...
Br answered 21/8, 2014 at 10:40 Comment(0)
N
0

I had similar troubles and this article of Jimmy's Bogard helped me. Article here

Look at the html generated will show that with a partial view the html doesn't include the name of the nested class so by default is unable to bind it. Giving the binding statement as in one of the answers above solves the problem

Nonsuch answered 4/10, 2017 at 19:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.