Kendo Grid MVC Wrapper Automatic Column Configuration
- 10 minutes to read
- edit
The Telerik Kendo Grid control is really powerful, especially when combined with the MVC wrappers. One of the things that make the MVC wrapper so useful is the ability to automatically (and easily) generate data-bound columns. It’s a single line of code:
.Columns(columns => columns.AutoGenerate(true))
The code behind AutoGenerate(true)
understands some of the System.ComponentModel.DataAnnotations
attributes. Specifically, it knows how to automatically configure the grid column for these attributes:
Atribute | Description |
---|---|
Compare |
Compares two properties. |
CreditCard |
Specifies that a data field value is a credit card number. |
CustomValidation |
Specifies a custom validation method that is used to validate a property. |
DataType |
Specifies the name of an additional type to associate with a data field. |
Display |
Provides a general-purpose attribute that lets you specify localizable strings for types and members of entity partial classes. |
DisplayColumn |
Specifies the column that is displayed in the referred table as a foreign-key column. |
DisplayFormat |
Specifies how data fields are displayed and formatted by ASP.NET Dynamic Data. |
Editable |
Indicates whether a data field is editable. |
EmailAddress |
Validates an email address. |
EnumDataType |
Enables a .NET Framework enumeration to be mapped to a data column. |
FileExtensions |
Validates file name extensions. |
FilterUIHint |
Represents an attribute that is used to specify the filtering behavior for a column. |
MaxLength |
Specifies the maximum length of array or string data allowed in a property. |
MinLength |
Specifies the minimum length of array or string data allowed in a property. |
Phone |
Specifies that a data field value is a well-formed phone number using a regular expression for phone numbers. |
Range |
Specifies the numeric range constraints for the value of a data field. |
RegularExpression |
Specifies that a data field value in ASP.NET Dynamic Data must match the specified regular expression. |
Required |
Specifies that a data field value is required. |
ScaffoldColumn |
Specifies whether a class or data column uses scaffolding. |
StringLength |
Specifies the minimum and maximum length of characters that are allowed in a data field. |
UIHint |
Specifies the template or user control that Dynamic Data uses to display a data field. |
Url |
Provides URL validation. |
What’s nice about this support is that you can use these attributes to adorn your model properties and declaratively describe some of the metadata about the column.
The problem, though, is that you can’t also set the Kendo Grid specific properties, such as column width, if the column is locked or not, and if it should be included in the column menu (to name just a few).
Fortunately, we can hook into the AdditionalValues
dictionary of the Metadata
property on a data-bound column (which is of type Kendo.Mvc.UI.GridBoundColumn<TModel, TValue>
). This property holds an instance of a System.Web.Mvc.ModelMetadata
(more specifically an instance of a CachedModelMetadata<TPrototypeCache>
) object, which has all of the metadata related attributes defined on the properties of the model and is the key to our solution of providing automatic column configuration based on data annotation attributes. To do this, we simply define our own attribute and implement the IMetadataAware
interface. The runtime will handle everything for us and our new attribute will be added to the AdditionalValues
dictionary.
I created a GridColumnAttribute
to allow all of the additional Kendo specific properties to be set.
using System;
using System.Web.Mvc;
namespace Cadru.Web.KendoExtensions
{
public class GridColumnAttribute : Attribute, IMetadataAware
{
public const string Key = "GridColumnMetadata";
public bool Hidden { get; set; }
public bool IncludeInMenu { get; set; }
public bool Lockable { get; set; }
public bool Locked { get; set; }
public int MinScreenWidth { get; set; }
public string Width { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues[GridColumnAttribute.Key] = this;
}
}
}
Now, we can decorate our model with the new attribute:
public class EmployeeModel
{
[Editable(false)]
[GridColumn(Width = "100px", Locked = true)]
public string EmployeeID { get; set; }
[GridColumn(Width = "200px", Locked = true)]
public string EmployeeName { get; set; }
[GridColumn(Width = "100px")]
public string EmployeeFirstName { get; set; }
[GridColumn(Width = "100px")]
public string EmployeeLastName { get; set; }
}
However, that’s only part of the solution. We still need to tell the Kendo Grid that it needs to do something with this new attribute. To do this we can use the overload for the AutoGenerate
method which takes an Action
:
.Columns(columns => columns.AutoGenerate(c => GridColumnHelpers.ConfigureColumn(c)))
The GridColumnHelpers.ConfigureColumn<T>
method looks like
using Kendo.Mvc.UI;
using System;
using System.Web.Mvc;
namespace Cadru.Web.KendoExtensions
{
public static class GridColumnHelpers
{
public static void ConfigureColumn<T>(GridColumnBase<T> column) where T : class
{
CachedDataAnnotationsModelMetadata metadata = ((dynamic)column).Metadata;
object attributeValue = null;
if (metadata.AdditionalValues.TryGetValue(GridColumnAttribute.Key, out attributeValue))
{
var attribute = (GridColumnAttribute)attributeValue;
column.Width = attribute.Width;
column.Locked = attribute.Locked;
column.Hidden = attribute.Hidden;
column.IncludeInMenu = attribute.IncludeInMenu;
column.Lockable = attribute.Lockable;
column.MinScreenWidth = attribute.MinScreenWidth;
}
}
}
}
This takes advantage of the fact that the method is being called in the context of automatically generating data-bound columns, so it’s able to take the column and cast it to a dynamic
object in order to reference the Metadata
property. We have to do this because the IGridBoundColumn
doesn’t expose the Metadata
property and we can’t cast it directly to a GridBoundColumn<TModel, TValue>
because (among other reasons) we don’t know the type for TValue
. That leaves us with casting it to dynamic
and letting the dynamic dispatcher figure out how to give us back the requested property. From there, we look to see if the AdditionalValues
dictionary contains our attribute, and if it does we then set the column properties to their respective values as specified by the attribute. We now have the ability to automatically configure the additional column properties using metadata specified in our model while still automatically generating the data-bound columns.