简体   繁体   中英

How to whitelist/blacklist child object fields in the ModelBinder/UpdateModel method?

I have a question regarding how to get the white- and black-listing feature of the MVC controller's UpdateModel/TryUpdateModel to work on individual properties of child objects. For example, lets say I have a questionnaire collecting details about the person filling out the form and about his or her company.

My [simplified] form fields would be named, for example:

YourName
YourEmail
Company.Name
Company.Phone

Now in my model, lets say I don't want Company.ID or Company.IsPremiumMember to be tampered with, so I'd like to exclude them from the model-binding. I have tried a combination of whitelisting, blacklisting, and both in order to get this to work. I have not had any success. Here is what I am running into:

When I explicitly include in my whitelist the same four fieldnames I wrote above, the entire Company does not get bound (ie, questionnaire.Company is left null) unless I also include "Company" in my whitelist. But then this has the undesirable effect of binding the ENTIRE company, and not just the two properties I want.

So, I then tried to include Company.ID and Company.IsPremiumMember in my blacklist, but this seems to be trumped by the whitelist and does not filter out these properties "after the fact" I suppose.

I know that there are other ways to express the "bindability", such as via the [Bind] attribute on members, but this is not ideal as I would like to have the same model classes used in other situations with different binding rules, such as allowing an admin to set whatever properties she would like.

I expect an obvious answer is that I should write my own model binder, and I've already starting trying to look into how to perhaps do this, but I was really hoping to use an "out-of-the-box" solution for what (in my opinion) seems like a very common scenario. Another idea I'm pondering is to fabricate my own ValueProvider dictionary to hand to the UpdateModel method, but again, something I'd rather avoid if there is an easier way.

Thanks for any help! -Mike


Addendum #1

Here are the fields I present on my form:

 YourName \nYourEmail \nCompany.Name \nCompany.Phone  

And here is what a black hat sends my way:

 YourName=Joe+Smith&YourEmail=joe@example.com&Company.Name=ACME+Corp&Company.Phone=555-555-5555&Company.CreditLimit=10000000  

(be sure you notice the extra parameter tacked on there at the end!)

And here is the problem:

As I originally posted, it doesn't seem possible (using the default model binder) to prevent CreditLimit from being set---it's either the entire Company or nothing---without some big workaround. Am I wrong?


Addendum #2

I'm pretty much convinced now that the simple objective I have is not possible "out of the box." My solution has been to walk through the posted form fields and construct my own ValueProvider dictionary, thus whitelisting the fields I want to allow, and handing that to UpdateModel.


Addendum #3

I still have not yet checked out AutoMapper, but with something like that at hand, the solution of creating some ViewModels/DTOs to handle this type of complex whitelisting---plus the ability to easily attach the same server-side validation (FluentValidation) I'm already using on my domain objects---seems a viable solution. Thank you everyone!

I have worked around this problem by making the action accepting two objects (the parent and the child object)

Example:
Suppose we have the following model:

 public class Employee
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Company Comapany { get; set; }
    }
    public class Company
    {
        public int Phone { get; set; }
    }

You build your form like this:

    <form action="Home/Create" method="post">
   <label for="Employee.Name">Name</label>
    <%=Html.TextBox("Employee.Name") %><br />
   <label for="Employee.Name">Age</label>    
    <%=Html.TextBox("Employee.Age") %><br />
   <label for="Employee.Name">Comapany Phone</label>    
    <%=Html.TextBox("Company.Phone") %><br />
    <input type="submit" value="Send" />
   </form>

Then build a "Create" action that accept two objects one of type Employee and the other of type Comapny and assign the Company object to the Employee.Company property inside the action:

 public ActionResult Create(Employee employee,Company company)
        {
            employee.Comapany = company;
            UpdateModel(employee);
            return View();
        }

I hope this help you.

Edit:

public ActionResult Create(Employee employee,Company company)
{
   employee.Comapany = company;
   UpdateModel(employee,new[] {"Name","Email","Phone"});
   return View();
}

In general, the best way to go is to create view-models , models built specifically for your views . These models are not domain objects. They are data-transfer objects, built to transfer data from your controller actions to your view templates. You can use a tool like AutoMapper painlessly to create/update a domain model object from your view-model object or to create/update a view-model object from your domain model.

Have you tried using the following?:

public ActionResult Create([Bind(Exclude="PropertyToExclude1, PropertyToExclude2")] Employee employee) {

//action code here

}

or use Include instead of Exclude to List what fields can be bound rather than which can't

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM