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 (i.e., 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 YourEmail Company.Name Company.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!
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.