An Introduction to the Derived Type Model Binder
The DerivedTypeModelBinder and associated code enables you to both render and model-bind instances in your object graph that derive from a common base. Whereas the DefaultModelBinder in Mvc traverses the object graph and attempts to instantiate literal types to build up your instance, DerivedTypeModelBinder enables you to declaratively signal when a common base (interface or class) should look to derived types for actual instantiation. In addition, DerivedTypeModelBinder introduces a new RenderPartial variation called RenderTypedPartial<TModel>(TModel instance) that inspects the model for it's derived type and renders the partial according to name convention. The naming convention is fully configurable and is described below along with discussion on how the pieces work together.
Setting the Stage - Our Object that Refers to a Common Base Type
Let’s say I’m working for a furniture manufacturer that makes a wide range of products – from case goods to upholstered furniture. On our website, we want to allow the user to configure a given product in one step and simplify scenarios for generating our page content.
We’ll start with a type called DynamicItem that contains multiple parts:
1: public class DynamicItem
2: {
3: public string ItemName { get; set; }
4:
5: public IConfigurablePart[] ConfigurableParts {get; set;}
6: }
The key with respect to rendering DynamicItem is the ability to render the various flavors of IConfigurablePart. Before we get into that, let’s look at the definition IConfigurablePart and its derivatives next.
1: public interface IConfigurablePart
2: {
3: string ItemType { get; set; }
4: }
5:
6: public class FurnitureLeg : IConfigurablePart
7: {
8: #region IConfigurablePart Members
9:
10: public string ItemType { get; set; }
11:
12: #endregion
13:
14: public string LegStyle { get; set; }
15: public int Height { get; set; }
16: }
17:
18: public class RoundFurnitureTop : IConfigurablePart
19: {
20: #region IConfigurablePart Members
21:
22: public string ItemType { get; set; }
23:
24: #endregion
25:
26: public string WoodStyle { get; set; }
27: public string Diameter { get; set; }
28: }
29:
30: public class UpholsteredPart : IConfigurablePart
31: {
32: #region IConfigurablePart Members
33:
34: public string ItemType { get; set;}
35:
36: #endregion
37:
38: public bool Plaid { get; set; }
39: }
Now we have a set of options for IConfigurablePart that help us demonstrate the mechanism (even if they are greatly simplified).
Declaring Type Variations for the Model Binding Process
Our first step is declaring the possible options for IConfigurablePart and there are two approaches.
The first approach follows the KnownTypeAttribute pattern established in WCF by using our attribute called ‘DerivedTypeBinderAwareAttribute':
1: [DerivedTypeBinderAware(typeof(FurnitureLeg))]
2: [DerivedTypeBinderAware(typeof(RoundFurnitureTop))]
3: [DerivedTypeBinderAware(typeof(UpholsteredPart))]
4: public interface IConfigurablePart
5: {
6: string ItemType { get; set; }
7: }
The second approach that can be used in place of the attribute method – declaring the definitions at a single point in the application (i.e. application start).
1: protected void Application_Start()
2: {
3: DerivedTypeModelBinderCache.RegisterDerivedTypes(typeof(IConfigurablePart),
4: new[]
5: {
6: typeof (FurnitureLeg),
7: typeof (RoundFurnitureTop),
8: typeof (UpholsteredPart)
9: });
The advantage to the second approach is avoiding the reflection hit – but keep in mind that the BinderCache is the common mechanism so the reflection cost is only hit once. It can also be helpful when you do not have control over the set of objects – perhaps they live in a separate assembly provided to you.
Setting Up the Controller
Next, let’s setup the controller with an item that we can walk through. Not much here – just a basic instantiation and passthrough to our strongly typed view.
1: public ActionResult Configure()
2: {
3: // create a simple instance for the sake of demonstration
4:
5: return View(new DynamicItem
6: {
7: ItemName = "Side Table",
8:
9: ConfigurableParts =
10: new IConfigurablePart[] {
11: new RoundFurnitureTop
12: {Diameter = "50in", WoodStyle = "Maple", ItemType = "standardRound"},
13: new FurnitureLeg
14: {Height = 36, LegStyle = "Queen Anne", ItemType = "StandardLeg"} }
15: });
16: }
Setting Up the View
Our next step is set up the base view to be used to render the root object. First off, we’ll use MvcContrib’s ModelView… page/partial classes as these handle binding context across the views quite nicely.
1: <h2>Configure</h2>
2: <%using (Html.BeginForm())
3:
4: <div class="editor-label">
5: <%=this.Label(m => m.ItemName)%>
6: </div>
7: <div class="editor-field">
8: <%=this.TextBox(m => m.ItemName)%>
9: <%=this.ValidationMessage(m => m.ItemName)%>
10: </div>
11:
12: <%for (var i = 0; i < Model.ConfigurableParts.Count(); i++){%>
13: <div class='config-part'>
14: <%
15: this.RenderTypedPartial(m => m.ConfigurableParts[i]);%>
16: </div>
17: <%}%>
18: <input type="submit" value="Submit" />
Note the use of the for() loop – a Repeater could be used here as long as it integrates with the binder context handling of MvcContrib. The for loop works nicely because it gives the expression handed to RenderTypedPartial a complete handle over the object graph traversal.
Using RenderTypedPartial to Target the Appropriate Partial At Runtime
The RenderTypedPartial takes an instance, determines its type and then renders a partial using a naming convention. This naming convention by default is ‘{TypeName}TypedPartial’. Therefore, and instance of type ‘RoundFurnitureTop’ would resolve to ‘RoundFurnitureTopTypedPartial’. This naming convention is stored in MvcContrib.FluentHtml.PartialNameConventionService.PartialNameConvention. You can modify this in startup if you so choose.
Internally, RenderTypedPartial injects a 'TypeStamp' into the page for you. This permits the DerivedTypeModelBinder to identify the proper object to instantiate at bind-time. Using RenderTypedPartial means you do not need to manually call TypeStamp() in your partial, but if you use another mechanism to display your type-specific content then you will need to do so.
The next step is to set up the partials – and as such I am deriving the partial from ModelViewUserControl from MvcContrib.FluentHtml. The piece to pay attention to here is the ‘TypeStamp()” extension I added to my branch of MvcContrib. It actually wraps a hidden field, but places the type context in the page that can be used by the DerivedTypeModel binder to instantiate the proper item.
First, the code for ‘FurnitureLegTypePartial’
1: <h2>Furniture Leg</h2>
2:
3: <div class="editor-label">
4: <%= this.Label(m => m.Height) %>
5: </div>
6: <div class="editor-field">
7: <%= this.TextBox(m => m.Height)%>
8: <%= this.ValidationMessage(m => m.Height)%>
9: </div>
10: <div class="editor-label">
11: <%= this.Label(m => m.LegStyle) %>
12: </div>
13: <div class="editor-field">
14: <%= this.TextBox(m => m.LegStyle)%>
15: <%= this.ValidationMessage(m => m.LegStyle)%>
16: </div>
And next, the code for ‘RoundFurnitureTopTypePartial’
1: <h2>Round Table Top</h2>
2:
3: <div class="editor-label">
4: <%= this.Label(m => m.Diameter) %>
5: </div>
6: <div class="editor-field">
7: <%= this.TextBox(m => m.Diameter)%>
8: <%= this.ValidationMessage(m => m.Diameter)%>
9: </div>
10: <div class="editor-label">
11: <%= this.Label(m => m.WoodStyle) %>
12: </div>
13: <div class="editor-field">
14: <%= this.TextBox(m => m.WoodStyle)%>
15: <%= this.ValidationMessage(m => m.WoodStyle)%>
16: </div>
Registering Our Base Type with the Model Binding Process
This next step is key – we need to register the invariant type in these binding situations to be handled by the DerivedTypeModelBinder. This can be done using any of the regular options, and we’ll use the direct method on startup in this scenario. This is instructing ModelBinding to use DerivedTypeModelBinder whenenver it runs into an IConfigurablePart reference.
1: ModelBinders.Binders.Add(typeof(IConfigurablePart),
2: new DerivedTypeModelBinder());
Typing it altogether on HttpPost
The final step is wiring up our form submit to the controller. We do this in the normal MVC convention. Because we are following conventions with the DefaultModelBinder and haven’t changed the way validation is being performed, all model validation applies even to our derived types. In this case, we see that DynamicItem is fully populated and any validation errors are thrown through the regular mechanism.
1: [HttpPost]
2: public ActionResult Configure(DynamicItem dynamicItem)
3: {
4: if( ModelState.IsValid)
5: {
6: _repository.Save(dynamicItem);
7: return RedirectToAction("SaveLanding");
8: }
9: return View(dynamicItem);
10: }
Summary
This gives us the ability to render and model-bind much more flexible object structures in a common and reusable manner. To recap the key points, we must:
- Declare the type variations using DerivedTypeBinderAwareAttribute or the RegisterDerivedTypes mechanism mentioned above.
- Ensure that views are rendered by the appropriate derived type at runtime and provide the typestamp in the view output. RenderTypedPartial takes care of both of these conditions automatically.
- Register the common base type with Mvc's ModelBinders.Binders collection.
Build out the appropriate views and you are on your way to having a completely flexible rendering of your object graphs.