views:

985

answers:

2

Firstly, bear with me here. I have a custom model binder which is successfully mapping form data to a custom object. Within this model binder it also maps form items to different custom object. What I feel I should be able to do is create a separate model binder to take care of this second mapping. This is a simplified version.

Custom objects:

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public string Status { get; set; }
    public string Description { get; set; }
    public IEnumerable<SubCategory> SubCategories { get; set; }
}

public class SubCategory
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Status { get; set; }
}

If my form passes back a bunch of Ids for the SubCategories, what I need to do is run off to the data repository and hydrate the SubCategory object. From the form, a list of subcategories would be submitted in the following format:

<input type="text" name="Name" value="This Category" />

<input type="hidden" name="subcat.Index" value="0" />
<select name="subcat[0].Id">
    <option value="1">Something</option>
    <option value="2">Something else</option>
</select>

<input type="hidden" name="subcat.Index" value="1" />
<select name="subcat[1].Id">
    <option value="1">Something</option>
    <option value="2">Something else</option>
</select>

<input type="hidden" name="subcat.Index" value="2" />
<select name="subcat[2].Id">
    <option value="1">Something</option>
    <option value="2">Something else</option>
</select>

Writing a custom to map the Category is obviously simple, writing the model binder which will in turn map the SubCategory (within the model binder I would run off an query my data repository) is proving a little difficult.

I am not sure how clear I have made this, apologies, thanks for reading and please let me know if there is something I can say to make this clearer!

+3  A: 

My take on this is that model binders should be constructing presentation models, not entity types from your repository. The model binder should be a very simple mapping from the key/value collection of the form to a presentation model which is mostly scalar values with possibly some relationships to other types that are mostly scalar values or lists. Having to materialize entity instances from a repository adds a lot of complication, as you have found.

Moreover, it's unnecessary. Using a presentation model has a large number of advantages, including:

  • There is never a need to whitelist the fields the user is allowed to update, since the presentation model contains only those fields.
  • The default model binder will work for all but the most complex model binding scenarios. In practice, I find that I only need to use a custom model binder when the value the user sees has to be bound to some other value in a conditional manner. When using a presentation model the structure of your presentation model should match the structure of the page, so you do not need to use a custom model binder for structural reasons.
  • You will be able to create your views and controllers before creating a database or entity model. This means you can get customer buy-in on your design before doing a large amount of the work to create the final system. This helps to sort out structural issues in the entity model before they happen. Just create a presentation model which matches the page you think the customer wants to see, build the general outline of the page using a made-up instance of this presentation model, and show it to the customer. If they're happy, you can then build the repository/entity model and write a LINQ query to map that to your presentation model.

So in your example, the subcategories would come in from the form collection as a list of integers. Therefore, the presentation model should have the same list of integers. In the controller, after binding, you can call a method to transfer the model values from the presentation model to a materialized category instance from the repository.

Craig Stuntz
Thanks for your comments Craig. I see your point about presentation but that would mean hydrating a presentation object to pass directly into a service layer to hydrate the actual object. This seems a little cumbersome and I am happy to cut out the middle man and find out how to complete this.
dave
Had a change of heart with this. Whilst most of my views are able to bind directly to my entity model class, I can now see that complex objects are probably best bound to a ViewModel. The latest Herding Code and some posts by K. Scott Allen finally tipped it in favour of doing this. Thanks for mentioning it first Craig!
dave
@dave - can you link to those articles that helped you decide to go with presentation models?
Maslow
+1  A: 

I would recommend taking a look at this Singing Eels post, which gives an example of another approach. Using the sample StatefulObjectBinder approach, it is possible to bind collections of business objects which must be retrieved from the database. Since the controller is implementing IModelBinder, you have access to a repository, which can be used to hydrate the required objects and add them to the object collection.

ZacharyB