Automatically Generating Column Titles For A KendoUI MVC Grid

I love KendoUI, especially because of the available MVC wrappers. It is a very well engineered product with lots of opportunity for extension, and in this post I’ll briefly discuss one that should relieve a small pain point: generating grid column titles from a DisplayAttribute data annotation. As you probably already know, you can change the way your UI layer presents properties of your model by applying the DisplayAttribute data annotation. This causes most of the UI code to use the Name property of the attribute when displaying that property. It looks like this:

[Display(Name = "The Product!")]
public string ProductName { get; set; }

Until recently, KendoUI would not recognize the DisplayAttribute applied to a bound column. However, before I go much further, it’s worth noting that this is no longer the case. I waited too long to post this article and KendoUI already gets the column title from a DisplayAttribute data annotation if there is one. I am posting this anyway because the technique could be generalized to other ways of customizing the grid by using an extension method.

Now, let’s say we have a KendoUI grid declared in Razor using the MVC wrappers (this is from their demo page):

@model IEnumerable<Kendo.Mvc.Examples.Models.ProductViewModel>

@(Html.Kendo().Grid(Model)    
    .Name("Grid")
    .Columns(columns =>
    {
        columns.Bound(p => p.ProductID);
        columns.Bound(p => p.ProductName);
    })
    .DataSource(dataSource => dataSource
        .Ajax().Read(read => read.Action("Products_Read", "Grid"))
    )
)

When displaying the column titles, KendoUI will use the name of the property to generate the name of the column. In the example above, you will get two columns named “Product ID” and “Product Name” (Kendo is smart enough to add the spaces in between the capital letters). But what if we wanted our second column to be named “The Product!” as in the example application of DisplayAttribute above? We could add the title explicitly using the Title() extension method:

...
columns.Bound(p => p.ProductName).Title("The Product!");
...

But this violates the DRY principle. If you want to change the title in the future, you’ll need to remember to change it in both places. What would be better is if we could write something like:

...
columns.Bound(p => p.ProductName).DisplayNameTitle();
...

This would indicate to the grid that the title should be set by getting a DisplayAttribute Name property (if there is one) and using the normal name generation otherwise. The code for such an extension method is below:

public static GridBoundColumnBuilder<TModel> DisplayNameTitle<TModel>(
    this GridBoundColumnBuilder<TModel> builder) where TModel : class, new()
{
    // Create an adapter to access the typed grid column
    // (which contains the Expression)
    Type adapterType = typeof(GridBoundColumnAdapter<,>)
        .MakeGenericType(typeof(TModel), builder.Column.MemberType);
    IGridBoundColumnAdapter adapter =
        (IGridBoundColumnAdapter)Activator.CreateInstance(adapterType);

    // Use the adapter to get the title and set it
    return builder.Title(adapter.GetDisplayName(builder.Column));
}

private interface IGridBoundColumnAdapter
{
    string GetDisplayName(IGridBoundColumn column);
}

private class GridBoundColumnAdapter<TModel, TValue>
    : IGridBoundColumnAdapter where TModel : class, new()
{
    public string GetDisplayName(IGridBoundColumn column)
    {
        // Get the typed bound column
        GridBoundColumn<TModel, TValue> boundColumn =
            column as GridBoundColumn<TModel, TValue>;
        if (boundColumn == null) return String.Empty;

        // Create the appropriate HtmlHelper and use it to get the display name
        HtmlHelper<TModel> helper = HtmlHelpers.For<TModel>(
            boundColumn.Grid.ViewContext,
            boundColumn.Grid.ViewData,
            new RouteCollection());
        return helper.DisplayNameFor(boundColumn.Expression).ToString();
    }
}

So let’s look at this code a little more closely. The first DisplayNameTitle<TModel>() method is the actual extension. It takes a GridBoundColumnBuilder<TModel> because that’s what the Bound() method returns as part of the KendoUI MVC fluent interface for column specifications. The DisplayNameTitle extension method creates an instance of an adapter class that can be used to manipulate the grid column. Because the KendoUI MVC classes are strongly typed, we need to use reflection to create an adapter with the proper generic type parameters. The key to this working is that the adapter class implements the non-generic IGridBoundColumnAdapter interface, which means that by casting the reflection-generated generic adapter class to the interface we can call non-generic methods that have access to the generic type parameters we used during the reflected construction in the actual implementation of the method.

The real work gets done inside the GetDisplayName() implementation. This method creates an appropriately typed HtmlHelper and then uses it to call the HtmlHelper.DisplayNameFor() extension method. This ensures that our own DisplayNameTitle() extension will always return the exact same title that would be returned if we used the normal MVC HtmlHelper methods to display the property.

This technique could be used to add other extensions to the KendoUI column building fluent interface as well. For example, you could automatically make a column sortable or not based on the data type.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s