My few regular readers will know that I’m a big fan of Telerik and their products. Their product range is comprehensive, the support top-notch and their evangelism and engagement is second to none. Whilst usually involved in the .NET space from ASP.NET UI components through to testing platforms, they have recently branched out into an HTML5/JavaScript UI suite, called Kendo UI.
The more time I spend with ASP.NET MVC, the more I learn about everything that is actually going on in a web request, which is no bad thing and highlights a fault of my own professional development; depending on ASP.NET WebForms to hide the guts of the HTTP request away from me. In gaining this “missing knowledge”, I’ve developed my JavaScript skills tremendously and as such am able to think up some sweet user experiences.
My current requirement is a simple master->detail data structure and editing experience. Obviously this data-structure should not require the user’s knowledge or understanding but as anyone knows, effectively creating a seamless master->detail scenario without some degree of complexity or unnecessary user steps can be challenging. In playing around with the ASP.NET MVC grid it quickly became apparent that the guts of the grid control was becoming a barrier to rapid understanding and deployment, again, this considering I’m relatively new to MVC and the powered-by-unicorns “convention over configuration” pattern. What if I could rule out all the complex data-structure parsing, AJAX and the like and have the entire editing experience occur on the client?
The Kendo UI suite does provide integration with the server and therefore actual data, but only insofar as facilitating AJAX requests. This is fine, as I’m fairly well versed in AJAX, JavaScript and the MVC pattern now. However, I wanted to avoid having to return to the server for “mini edits” via a full POST or AJAX. So I did not want:
- Two steps: one for editing master data, the other for editing individual child data
- AJAX updates: updating child data in lieu of a complete POST and onward persistence (this would require some session state of some form which I am keen to avoid as this project is being developed with future cloud installation in mind)
What I needed was complete client-side editing and a single POST.
And it turns out it’s all very simple.
Given the data structure:

I created the view with the KendoUI grid in, the model containing a series of properties that would hold the modifications made on the client and the controller to tie it all together.
View
The grid is easily created, much like a jQuery UI widget:
<div id="valuesGrid"></div>
I wanted to handle everything on the client, so in in initialising my grid, I needed to consider how I was going to do this. I decided to create a few properties in my Model, which would be rendered as hidden input elements in the form element. This would allow me to manipulate items on the client and post back the individual fields as a single POST, processing them on the server.
My fields are below (in Razor syntax):
@Html.HiddenFor(model=>model.OriginalLookupValuesJson)
@Html.HiddenFor(model => model.NewLookupValuesJson)
@Html.HiddenFor(model => model.EditedLookupValuesJson)
@Html.HiddenFor(model => model.DeletedLookupValuesJson)
On the GET, the controller populates the detail records by setting the OriginalLookupValuesJson field to the JSON representation of the data-structure.
On the client-side, these are manipulated by “overriding” the built-in CRUD methods of the Kendo UI grid:
$(document).ready(function () {
var json = $('input#OriginalLookupValuesJson').val();
var data = eval('('+json+')');
var dataSource = new kendo.data.DataSource({
transport: {
read: function (options) {
options.success(data);
},
create: function (options) {
$("input#NewLookupValuesJson").val(kendo.stringify(options.data.models));
},
update: function (options) {
$("input#EditedLookupValuesJson").val(kendo.stringify(options.data.models));
},
destroy: function (options) {
$("input#DeletedLookupValuesJson").val(kendo.stringify(options.data.models));
}
}, //transport
batch: true,
pageSize: 4,
schema: {
model: {
id: "ID",
fields: {
ID: {
editable: false,
nullable: true
},
LookupSetID: {
editable: false,
nullable: true
},
Value: {
editable: true,
validation: {
required: true
}
},
Description: {
editable: true
}
} //fields
}, //model
data: "data",
total: function (result) {
result = result.data || result;
return result.length || 0;
} //total
} //schema
});
$("#valuesGrid").kendoGrid({
dataSource: dataSource,
editable: true,
toolbar: ["create"],
height: 250,
scrollable: true,
sortable: true,
filterable: false,
pageable: true,
columns: [
{
field: "Value",
title: "Value"
},
{
field: "Description",
title: "Description"
},
{
command: "destroy"
}
]
});
The script implements the following:
- Localises the JSON from the OriginalLookupValuesJson field into a variable, which is then evaluated into a JavaScript object
- A dataSource object is then configured that takes the evaluated JavaScript object as a source for the read implementation. The create, update and destroy implementations are overridden by setting their output as the previously mentioned hidden fields. The results of which will be passed back into my Controller within the Model.
- Initialises and configures the grid with the dataSource variable and the user-interface aspects I require.
Unfortunately, when you submit the form in a POST, the hidden fields used to monitor modifications are exactly as they were when they were requested: blank. You need to synchronise the internal data structures of the grid with the hidden fields by intercepting the form POST by adding the following BLOCKED SCRIPT
$("form").bind("submit", sync);
});
function sync() {
alert('sync');
$("#valuesGrid").data("kendoGrid").dataSource.sync();
}
Model
The Model itself is supremely simple and contains the principle object of the Master object (LookupSet) and the four fields used to store the JSON and allow the manipulation of the LookupValues on the client-side and persistence of the changes on the server-side.
public class LookupModel : ModelBase
{
public LookupSet LookupSet { get; set; }
public IEnumerable<SelectListItem> DataTypes { get; set; }
public string OriginalLookupValuesJson { get; set; }
public string NewLookupValuesJson { get; set; }
public string EditedLookupValuesJson { get; set; }
public string DeletedLookupValuesJson { get; set; }
public LookupModel()
{
}
}
I create my models within a Factory class, ModelFactory, which has a method:
public static LookupModel CreateLookupModel(int id)
{
LookupModel model = new LookupModel();
using (CoreEntities coreEntities = CoreEntitiesFactory.Create(configuration))
{
model.LookupSet = coreEntities.LookupSets.Include("LookupValues").SingleOrDefault(q => q.ID == id);
JavaScriptSerializer javaScriptSerializer=new JavaScriptSerializer();
model.OriginalLookupValuesJson = javaScriptSerializer.Serialize(new { data = GetLookupValueModels(model) });
}
return model;
}
The method creates a new LookupModel and interrogates the data-store for the requested data. This is implemented using Entity Framework but obviously can be replaced with your own framework.
In order to have the detail data (LookupValues) processed at the client, it needs to be serialized into JSON format, using the JavaScriptSerializer class. This is called with the result of the method GetLookupValueModels(), which is shown below:
private static List<LookupValueModel> GetLookupValueModels(LookupModel model)
{
List<LookupValueModel> lookupValueModels = new List<LookupValueModel>();
foreach (LookupValue lookupValue in model.LookupSet.LookupValues)
{
LookupValueModel lookupValueModel = new LookupValueModel()
{
Description = lookupValue.Description ?? string.Empty,
ID = lookupValue.ID,
LookupSetID = lookupValue.LookupSetID
};
if (model.LookupSet.DotNetDataType == typeof(string).FullName)
lookupValueModel.Value = lookupValue.System_String;
if (model.LookupSet.DotNetDataType == typeof(int).FullName && lookupValue.System_Int32.HasValue)
lookupValueModel.Value = lookupValue.System_Int32.Value.ToString();
if (model.LookupSet.DotNetDataType == typeof(decimal).FullName && lookupValue.System_Decimal.HasValue)
lookupValueModel.Value = lookupValue.System_Decimal.Value.ToString();
lookupValueModels.Add(lookupValueModel);
}
return lookupValueModels;
}
This takes the Entity Framework objects and creates a LookupValueModel for each, returning the result. This is then wrapped up in an anonymous type, which assigns the result to the “data” property. The result of which would be typically:
{
"data": [
{
"ID": 3,
"LookupSetID": 2,
"Value": "XXX",
"Description": "Description for XXX"
},
{
"ID": 4,
"LookupSetID": 2,
"Value": "YYY",
"Description": "Description for YYY"
},
{
"ID": 5,
"LookupSetID": 2,
"Value": "ZZZ",
"Description": "Description for ZZZ"
}
]
}
As this is placed in the model property OriginalLookupValuesJson, this is sent to the client as part of the form, and correctly rendered by the Kendo UI grid component.
Controller
For completeness, the HTTP GET request is implemented by the controller as follows:
public ActionResult EditLookup(int id)
{
ActionResult result;
LookupModel model = ModelFactory.CreateLookupModel(id);
if (model.LookupSet == null)
{
result = View("InvalidLookup", ModelFactory.Create<GenericPageModel>("Invalid lookup"));
}
else
{
model.Title = string.Format("Edit lookup '{0}'", model.LookupSet.Name);
result = View("EditLookup", model);
}
return result;
}
But where we are interested in is the POST, which will take the modified values from the hidden form fields and implement the modifications on the data-store. The modifications arrive within the hidden fields as JSON, so it is a simple matter to rehydrate those to usable data that we can work with by deserializing the LookupValues back into CLR objects. I use the Deserialize<T>() method from this StackOverflow post (external link)" href="http://stackoverflow.com/questions/2246694/how-to-convert-json-object-to-custom-c-sharp-object">StackOverflow post:
private static T Deserialize<T>(string json)
{
T obj = Activator.CreateInstance<T>();
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
obj = (T)serializer.ReadObject(ms);
ms.Close();
return obj;
}
Then I call that for each of the modification types in the HTTP POST handler of my Controller (excluding the wider persistence code):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditLookup(LookupModel model)
{
ActionResult result = View(model);
if (ModelState.IsValid)
{
List<LookupValueModel> modifiedLookupValues = Deserialize<List<LookupValueModel>>(model.EditedLookupValuesJson);
List<LookupValueModel> createdLookupValues = Deserialize<List<LookupValueModel>>(model.NewLookupValuesJson);
List<LookupValueModel> deletedLookupValues = Deserialize<List<LookupValueModel>>(model.DeletedLookupValuesJson);
}
return result;
}
How you handle the individual CUD methods is dependent on your persistence layer.