JOEL'S CODE


Tags


JOEL'S CODE

Use MVC Templates with Dynamic Generated Types with custom HtmlHelper Extensions

3rd June 2011

In my last blog post, Use MVC Templates with Dynamic Members with custom HtmlHelper Extensions, I discussed how to create an extension method for using MVC Html templates when you don’t know the member name you want to use at design time. In this post I will discuss how to us MVC Html templates when you do not know the model type at runtime.

In my latest project at work I am building a type at runtime using the Dynamic Query code provided by the Visual Studio SDK. This means however that I do not know that type is at design time. This is because the dynamic query code generates a type representing the result of the dynamic query at runtime and returns it.

Not knowing the type at runtime posed a problem. When I went to render my html table to display rows with data from the result of my dynamic Linq query I couldn’t use the existing extension methods to render the templates because they rely on knowing the type at design time. To solve this problem I created HtmlHelper extension methods which mimic the functionality of the existing DisplayFor and EditorFor HtmlHelper extension methods but requires an instance of the type to be passed. Below is an example of one the extension methods.

public static MvcHtmlString EditorFor<TModel>(  
                   this HtmlHelper<TModel> htmlHelper, 
                   object instance, 
                   string expression)
{
    var tType = instance.GetType();

    var lambda = DynamicExpression.ParseLambda(tType, expression);

    var methods = typeof(System.Web.Mvc.Html.EditorExtensions)
                  .GetMethods();

    var method = methods.Where(x =>
    {
        var args = x.GetGenericArguments();
        var parms = x.GetParameters();

        return (   args.Count() == 2 
                   && args[0].Name == "TModel"
                   && args[1].Name == "TValue" 
                   && x.Name == "EditorFor"
                   && parms.Count() == 2 
                   && parms[0].ParameterType.Name == "HtmlHelper`1"
                   && parms[1].ParameterType.Name == "Expression`1");
    }).First();

    method = method.MakeGenericMethod(new[] {tType, lambda.Body.Type});

    var htmlHelperType = typeof(HtmlHelper<>)
                         .MakeGenericType(tType);

    var helper = Activator.CreateInstance(  
                           htmlHelperType, 
                           htmlHelper.ViewContext,
                           new CustomViewDataContainer(                      
                                instance,
                                htmlHelper.ViewContext.ViewData));

    return (MvcHtmlString)method.Invoke(
                                        null, 
                                        new object[]{helper, lambda}
                                        );
}

As you can see the first part of the extension method is similar to the one in my last blog post. However, since the type that the we want the templates to be basedon  (the type returned form the dynamic query) is different than that of the type of the htmlHelper that we have as a part of the view  we need to create a new HtmlHelper based on the instance of the type returned from the dynamic query. This has to be done because inside the HtmHelper template generating logic uses the model type of the view to generate the template. I created an instance of the HTMLHelper based on the type returned from the dynamic query using reflection. Finally, I invoke the method call for the appropriate HtmlHelper extension method on the new HtmlHelper.

Ok, so now that I have this extension method, how does it help me with my problem? Well,  now I can take the metadata I have around the type (In general the property names) that gets generated for the dynamic Linq query to call my new HtmlHelper extension methods to generate templates in the way you would if you knew the type at runtime.

Implementing a solution like this is good when you have the common scenario of allowing customers to build there own entities and/or allow them to determine which fields are displayed in a view across multiple entities.

Assume in the example below you have a list of columns names available in the view’s model in a property named ColumnNames and an IEnumerable property on the model named Result, which contains the results of a dynamic Linq query.

@{  
    var instance = Model.Result.FirstOrDefault();

}

<strong>People</strong>

@if(instance == null){
    <strong>No Records</strong>
 }
@if (instance != null)
{
   <table>
<thead>
 <tr>
    <th></th>
    @foreach (var item in Model.ColumnNames)
    { 

        <th>@Html.LabelFor(instance, item)</th> 
    }
   </tr>
</thead>
<tbody>
    @foreach (var person in Model.People)
    { 
       <tr>
            <td>@Html.ActionLink("Edit", "Detail", 
                            new {Id = person.PersonId})</td>

            @foreach (var item in Model.ColumnNames)
            { 
                      <th>@Html.EditorFor(instance, item)</th> 
            }        
       </tr> 
    }
</tbody>
</table>
}

Please download the attached code which contains an example implementation and all of the HtmlHelper extension methods I described.

Source Code 

Joel Duvall
AUTHOR

Joel Duvall