MvcContrib grid with extended sorting capabilities
Posted by Siim on July 7th, 2010Lately I’ve been doing development on ASP.NET MVC and using MvcContrib grid to render simple grids. I have to say, it is very easy to use and extendible in every way. If I feel that something is missing or I want to change some behavior, then I just need to implement the proper interfaces and inject my own implementation for what I need.
For views I use view models with data annotations which specify how some data should be displayed on the view. I wanted to use data annotations also for specifying which properties should be rendered as sortable columns and which is the default one. Grid doesn’t do any sorting by itself, it solely relies on the input (which is a good thing, IMHO).
Fortunately, it was very easy to add. I needed only two things – customize the MVC model metadata provider to include my custom values and create a custom ColumnBuilder for the grid that could read values I included in the metadata. Brad Wilson has a nice post about how to extend model metadata providers. The code for the OrderByAttribute and with the bit that extends model metadata, is here:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class OrderByAttribute : Attribute { public OrderByAttribute() : this(SortDirection.Ascending, false) { } public OrderByAttribute(SortDirection sortOrder, bool isDefault) { SortOrder = sortOrder; IsDefault = isDefault; } public SortDirection SortOrder { get; set; } public bool IsDefault { get; set; } } public class GridMetaDataProvider : DataAnnotationsModelMetadataProvider { public const string SortableValueName = "Sortable"; protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); var orderByAttribute = attributes.OfType<OrderByAttribute>().FirstOrDefault(); if (orderByAttribute!=null) { metadata.AdditionalValues.Add(SortableValueName, orderByAttribute); } return metadata; } }
Next I need to create a column builder, similarly to AutoColumnBuilder which comes with MvcContrib. Unfortunately, I cannot extend that class because there are no extension point, but I used it as a guidance. For each property from metadata, I execute the following code, to apply sorting metadata:
var isSortable = property.AdditionalValues.ContainsKey(GridMetaDataProvider.SortableValueName); column.Sortable(isSortable); column.SortColumnName(property.PropertyName);
That’s all about building columns from metadata. Next thing I need to do, is to tell the grid which column is used for initial sorting. Because grid wants object of type GridSortOptions as an input, it is created by the controller. So I wrote a little extension method to populate GridSortOptions from model metadata.
public static GridSortOptions ApplyDefault<TItem>(this GridSortOptions sortOptions) where TItem : ListItemBase { // When sort options is specified, don't apply default values if (sortOptions != null && !string.IsNullOrEmpty(sortOptions.Column)) return sortOptions; var property = typeof (TItem).GetProperties().Where(ContainsDefaultOrderBy).FirstOrDefault(); if (property == null) return sortOptions; var newSortOptions = sortOptions ?? new GridSortOptions(); newSortOptions.Column = property.Name; return newSortOptions; } private static bool ContainsDefaultOrderBy(PropertyInfo property) { var orderBy = (OrderByAttribute)property.GetCustomAttributes(typeof (OrderByAttribute), false).FirstOrDefault(); return orderBy != null && orderBy.IsDefault; }
That’s it. Now you can decorate your view model with attributes and grid takes care of the rest.