Strongly-typed HTML helpers in Orchard shapes
Orchard uses dynamic objects called shapes as view models. It happens commonly that you’ll want to use strongly-typed HTML helpers such as Html.TextBoxFor from within a shape’s template. Unfortunately, this is not possible: those helpers rely fundamentally on the model being strongly-typed, because they need to be able to infer the properties of the object from the Lambda expression passed as a parameter of the helper, but the compiler will refuse to compile Lambda expressions on dynamic objects. As I understand it, this is because the compiler can’t infer enough information about the object at compile time. This is all regrettable but that’s how things are.
What if now, my model has some properties that are what I’m really interested in, and that are of a known strong type? It’s common to use the shape as a bag of stuff for the view to use. For example, in an editor’s template, you may have a bunch of collections that you’re going to use to fill drop-down options in addition to the object you want to edit. You should be able to use strongly-typed helpers on that well-known property of your model that contains your “real” model.
The problem now is that the HTML helpers implicitly work on the Model object for your view. You’d think that you’d be able to specify the context for the helpers, and set it to an arbitrary object, that is not necessarily Model. You’d be wrong however, there is no simple way of doing that (that I know of at least). The target of those helpers is the Model property and nothing else.
In Orchard 1.9, there will be a new helper that will fix that situation, thanks to some code originally written by Max Toro. Imagine that we have the following shape to display:
@Display.Place(Coordinates: new Tuple<double, double>(133, 42))
In the Place.cshtml file, we can build a custom HTML helper like this:
var addressHtml = Html.HtmlHelperFor(Model.Coordinates as Tuple<double, double>, "address");
The first parameter is the object we want the helper to use as the context. We had to cast it, because Model.Coordinates is dynamic as far as the compiler can know, because Model itself is dynamic. Notice that we are specifying a custom prefix as the second parameter.
We can then use that custom helper to build an editor for the object, with full IntelliSense:
<fieldset>
<legend>@T("Coordinates")</legend>
@addressHtml.LabelFor(coordinates => coordinates.Item1, T("Latitude"))
@addressHtml.TextBoxFor(coordinates => coordinates.Item1)
@addressHtml.LabelFor(coordinates => coordinates.Item2, T("Longitude"))
@addressHtml.TextBoxFor(coordinates => coordinates.Item2)
</fieldset>
This renders as expected:
<fieldset>
<legend>Coordinates</legend>
<label for="address_Item1">Latitude</label>
<input id="address_Item1" name="address.Item1" type="text" value="133">
<label for="address_Item2">Longitude</label>
<input id="address_Item2" name="address.Item2" type="text" value="42">
</fieldset>
HTML helpers are a really neat API, and so are Orchard’s shapes. Now, we can use them together…