bind attribute include and exclude property with complex type nested objects
Asked Answered
R

2

12

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?

Refractive answered 17/1, 2012 at 9:52 Comment(3)
I think your best bet is to do it like described in the other post you referenced.Ibex
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.Refractive
Try my solution here #47645199Wrathful
L
16

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.

Lecroy answered 17/1, 2012 at 9:57 Comment(7)
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?Refractive
@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.Lecroy
For you second recommendation, I always in between for this:sRefractive
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?Refractive
@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.Lecroy
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.Menstrual
@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: #3000436Excepting
C
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.)

Corduroys answered 28/8, 2015 at 7:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.