Introduction
Tired of setting your DevExpress grid definition by adding columns with property name?
And what about setting column title from resources?
I built a GridViewSetting
generic (typed) that lets you bound a column to a model like that:
@model IEnumerable<Model>
@{
Html.DevExpress()
.GridView<Model>(settings =>
{
settings.Name = "gridView";
settings
.GetColumns()
.Bound(model => model.Field1)
.Bound(model => model.Field2)
.Bound(model => model.Field3)
.Bound(model => model.Amount, column => column
.PropertiesEdit
.DisplayFormatString = "N")
.Bound(model => model.Ratio, column => column
.PropertiesEdit
.DisplayFormatString = "P")
.Bound(model => model.Date, column => column
.PropertiesEdit
.DisplayFormatString = "yyyy-MM-dd");
})
.Bind(Model)
.GetHtml();
}
Using the Code
Let’s start with this context.
A model:
public class Person
{
public int Id { get; set; }
[Display(Description = "First Name" )]
public string FirstName { get; set; }
[Display(Description = "Last Name" )]
public string LastName { get; set; }
[Display(Description = "Function" )]
public string Function { get; set; }
[Display(Description = "Hiring Date" )]
public DateTime HiringDate { get; set; }
}
A controller action:
public ActionResult Index()
{
var persons = new Collection<Person>
{
new Person
{
Id = 1,
FirstName = "Bill",
LastName = "Telnor",
Function = "Sales Rep",
HiringDate = new DateTime(2011, 5, 13)
},
new Person
{
Id = 2,
FirstName = "Joe",
LastName = "Bean",
Function = "Home Officer",
HiringDate = new DateTime(1999, 12, 2)
},
new Person
{
Id = 3,
FirstName = "Stan",
LastName = "Greenspan",
Function = "Horticulturist",
HiringDate = new DateTime(2008, 3, 28)
},
};
return View(persons);
}
And a view:
@using System.Web.UI.WebControls
@using GridViewExtensionWebSite.Models
@model IEnumerable<Person>
@{
Html.DevExpress()
.GridView(settings =>
{
settings .Name = "gridPerson";
settings .Width = Unit.Percentage(100);
settings .Height = Unit.Percentage(100);
settings
.Columns
.Add("FirstName", "First Name");
settings
.Columns
.Add("LastName", "Last Name");
settings
.Columns
.Add("Function", "Function");
settings
.Columns
.Add("HiringDate", "Hiring Date",
MVCxGridViewColumnType.DateEdit);
})
.Bind(Model)
.GetHtml();
}
First, the original Html.DevExpress().GridView
method receives a parameter Action<DevExpress.Web.Mvc.GridViewSettings>
. Then we must inherit from this class to make it generic. I put all classes in the namespace DevExpress.Web.Mvc
to make it simpler.
namespace DevExpress.Web.Mvc
{
public class GridViewSettings<TModel> : GridViewSettings
{
#region Methods
#endregion
}
}
Now we can add an extension method to the DevExpress.Web.Mvc.UI.ExtensionsFactory
class. The generic GridView<TModel>
method will be added but we first need to receive the view context to initialize the GridViewExtension
. DevExpress has internal methods to set and get the current view context. We can make a little bit of code reflection or create our own ExtensionsFactory
, but why not just keep it simple and pass the view context in parameter to the GridView
method.
Now, let’s reproduce basically the same behavior than DevExpress by instantiating our own typed GridViewSettings<TModel>
. Pass it to the delegate and return a new GridViewExtension
.
using System;
using DevExpress.Web.Mvc.UI;
namespace DevExpress.Web.Mvc
{
public static class GridViewExtensions
{
public static GridViewExtension GridView<TModel>(
this ExtensionsFactory extensionsFactory,
ViewContext viewContext,
Action<GridViewSettings<TModel>> action)
{
var gridViewSettings = new GridViewSettings<TModel>();
action(gridViewSettings);
return new GridViewExtension(gridViewSettings, viewContext);
}
}
}
Is that all? Not yet. We will modify our view…
@{
Html.DevExpress()
.GridView<Person>(Html.ViewContext, gridViewSettings =>
{
…
})
.Bind(Model)
.GetHtml();
}
If we run the cursor (in Visual Studio) over the GridViewSettings
variable, we see that its type is now GridViewSettings<Person>
. That's good, but what does it give us? Nothing!
We need to replace the usage of the GridViewSettings.Column
property by something more efficient. GridViewSettings.Column
returns a MVCxGridViewColumnCollection
, our GridViewSettings<TModel>
will return a MVCxGridViewColumnCollection<TModel>
.
Which method do we need to manage the columns? Our needs are pretty simple; we need to bound a column to a property of the model, one to bound with a property and edit column properties, and another one to add a calculated, static, action or other type of column.
And by the way, why not make it fluent...
using System;
using System.Linq.Expressions;
using System.Web.Mvc;
using DevExpress.Utils;
namespace DevExpress.Web.Mvc
{
public class MVCxGridViewColumnCollection<TModel>
{
#region Ctors
public MVCxGridViewColumnCollection(MVCxGridViewColumnCollection columns)
{
ColumnCollection = columns;
}
#endregion
#region Properties
public MVCxGridViewColumnCollection ColumnCollection { get; private set; }
#endregion
#region Methods
public MVCxGridViewColumnCollection<TModel> Bound<TValue>(
Expression<Func<TModel, TValue>> expression,
Action<MVCxGridViewColumn> setColumn)
{
if (expression == null)
throw new ArgumentNullException("expression");
setColumn(GetNewColumn(expression));
return this;
}
public MVCxGridViewColumnCollection<TModel> Bound<TValue>(
Expression<Func<TModel, TValue>> expression)
{
if (expression == null)
throw new ArgumentNullException("expression");
GetNewColumn(expression);
return this;
}
public MVCxGridViewColumnCollection<TModel> Add(Action<MVCxGridViewColumn> setColumn)
{
var gridViewColumn = ColumnCollection.Add();
setColumn(gridViewColumn);
return this;
}
private MVCxGridViewColumn GetNewColumn<TValue>(
Expression<Func<TModel, TValue>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression,
new ViewDataDictionary<TModel>());
var gridViewColumn = ColumnCollection.Add();
gridViewColumn.Caption = metaData.DisplayName;
gridViewColumn.FieldName = metaData.PropertyName;
gridViewColumn.HeaderStyle.Wrap = DefaultBoolean.True;
return gridViewColumn;
}
#endregion
}
}
Now, let's change our GridViewSettings<TModel>
and add a method to get the column collection.
namespace DevExpress.Web.Mvc
{
public class GridViewSettings<TModel> : GridViewSettings
{
#region Methods
public MVCxGridViewColumnCollection<TModel> GetColumns()
{
return new MVCxGridViewColumnCollection<TModel>(Columns);
}
#endregion
}
}
We'll now be able to replace the usage of the Columns
property of the GridViewSettings
in the view by our new method GetColumns
.
@model IEnumerable<Person>
@{
Html.DevExpress()
.GridView<Person>(Html.ViewContext , settings =>
{
settings.Name = "gridPerson";
settings.Width = Unit.Percentage(100);
settings.Height = Unit.Percentage(100);
settings.GetColumns()
.Bound(model => model.FirstName)
.Bound(model => model.LastName)
.Bound(model => model.Function)
.Bound(model => model.HiringDate,
column => column.PropertiesEdit
.DisplayFormatString = "yyyy-MM-dd");
})
.Bind(Model)
.GetHtml();
}
Done deal!
In the source code, I also add code to bound the model to a band column (multi row headers).
I hope you will understand that I don't use bootstraper, view-model, repository and I don't know what other best practices to create this demo. I want to show you a DevExpress extension, not a full solution!
Thanks.
History