11

Ok, this is weird. I cannot use BindAttribute's Include and Exclude properties with complex type nested objects on ASP.NET MVC.

Here is what I did:

Model:

public class FooViewModel {

    public Enquiry Enquiry { get; set; }
}

public class Enquiry {

    public int EnquiryId { get; set; }
    public string Latitude { get; set; }
}

HTTP POST action:

[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(
    [Bind(Include = "Enquiry.EnquiryId")]
    FooViewModel foo) {

    return View(foo);
}

View:

@using (Html.BeginForm()) {

    @Html.TextBoxFor(m => m.Enquiry.EnquiryId)
    @Html.TextBoxFor(m => m.Enquiry.Latitude)

    <input type="submit" value="push" />
}

Does not work at all. Can I only make this work if I define the BindAttribute for Enquiry class as it is stated here:

How do I use the [Bind(Include="")] attribute on complex nested objects?

Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
tugberk
  • 57,477
  • 67
  • 243
  • 335
  • 1
    I think your best bet is to do it like described in the other post you referenced. – JoJa Jan 17 '12 at 09:56
  • Yeah, it seems that way :s I cannot do that inside the domain model project. I should define partial classes inside the mvc project and do that there I think. – tugberk Jan 17 '12 at 09:58
  • Try my solution here https://stackoverflow.com/questions/47644699/how-to-bind-nested-objects-on-httppost-in-asp-net-mvc/47645228#47645228 – NoWar Dec 05 '17 at 02:43

2 Answers2

15

Yes, you can make it work like that:

[Bind(Include = "EnquiryId")]
public class Enquiry 
{
    public int EnquiryId { get; set; }
    public string Latitude { get; set; }
}

and your action:

[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(FooViewModel foo) 
{
    return View(foo);
}

This will include only the EnquiryId in the binding and leave the Latitude null.

This being said, using the Bind attribute is not something that I would recommend you. My recommendation is to use view models. Inside those view models you include only the properties that make sense for this particular view.

So simply readapt your view models:

public class FooViewModel 
{
    public EnquiryViewModel Enquiry { get; set; }
}

public class EnquiryViewModel 
{
    public int EnquiryId { get; set; }
}

There you go. No longer need to worry about binding.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • thanks @darin. This seems the only way. I haven't dug into this but can we tweak the `DefaultModelBinder` class to make this work by creating a new custom model binder? – tugberk Jan 17 '12 at 09:59
  • 2
    @tugberk, I really wouldn't go that route. Using view models is the way to solve those kind of problems. And not only those kind of problems. You should always use view models in an ASP.NET MVC application. – Darin Dimitrov Jan 17 '12 at 10:00
  • For you second recommendation, I always in between for this:s – tugberk Jan 17 '12 at 10:01
  • Here is what makes me doubt about the second option: I create my domain model in a separate project so that it could be reusable and completely separate from the business logic. I also define validation logic for my models inside that domain model project. If I go for second option, I am no longer in sync with the domain model for that class in terms of validation. I need to redefine validations for the `EnquiryViewModel` separately. That's why I am in between. What is your thoughts on this? – tugberk Jan 17 '12 at 10:08
  • 4
    @tugberk, my thoughts on this is to define validation logic on your view models as well. This validation logic is not always the same as the validation logic you have on your domain model. The validation logic you have defined on the view model is valid only in the context of the given view whereas the validation logic on your domain is valid for the entire application. – Darin Dimitrov Jan 17 '12 at 10:52
  • My base models are generated using EF database first, so I can't annotate them. My ViewModel is a combination (parent-child) of two models, and I have different binding needs per method (edit needs the ID, create does not). This is pretty ugly. – Christopher G. Lewis Oct 07 '14 at 21:31
  • @Christopher_G_Lewis, you can annotate models, since they are defined as partial, but you'll have to follow this approach, with MetadataType. It's tested and works as a charm. Please see the top rated answer: http://stackoverflow.com/questions/2999936/using-dataannotations-with-entity-framework – Daniel Hursan Dec 23 '14 at 18:10
0

IMHO there is a better way to do this.

Essentially if you have multiple models in the view model the post controller's signature would contain the same models, as opposed to the view model.

I.E.

public class FooViewModel {
    public Bar BarV { get; set; }
    public Enquiry EnquiryV { get; set; }
    public int ThisNumber { get; set; }
}

public class Bar {
    public int BarId { get; set; }
}

public class Enquiry {
    public int EnquiryId { get; set; }
    public string Latitude { get; set; }
}

And the post action in the controller would look like this.

[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(
    [Bind(Include = "EnquiryId")]
    Enquiry EnquiryV,
    [Bind(Include = "BarId"])]
    Bar BarV,
    int ThisNumber
{
    return View(new FooViewModel { Bar = BarV, Enquiry = EnquiryV, ThisNumber = ThisNumber });
}

All while the view still looks like this

@using (Html.BeginForm()) {

    @Html.TextBoxFor(m => m.EnquiryV.EnquiryId)
    @Html.TextBoxFor(m => m.EnquiryV.Latitude)
    @Html.TextBoxFor(m => m.BarV.BarId)
    @Html.TextBoxFor(m => m.ThisNumber)

    <input type="submit" value="push" />
}

Keep in mind, this form will still post Latitude back (the way you had it set up), however since it is not included in the Bind Include string for Enquiry on the post action, the action will not accept the new value in the resultant Enquiry. I'd suggest making latitude either disabled or not a form element to prevent additional posting data.

In any other scenario you can use bind just fine, but for some reason it dislikes the dot notation for complex models.

As a side note, I wouldn't put the bind attribute on the class directly as it can cause other issues like code replication, and doesn't account for certain scenarios where you may want to have a different binding.

(I modified the variable names for some clarity. I am also aware your question is rather dated, however in searching for the answer myself this is the first SO I stumbled upon before trying my own solutions and coming to the one I posted. I hope it can help out other people seeking a solution to the same issue.)

vipero07
  • 1,022
  • 1
  • 10
  • 16