MVC and JSON Action Results for Meaningful Messages
I recently wrote an asynchronous pipeline for my MVC view which I thought could be beneficial for some of you. Let me set the scene.
The MVC view is a Kendo grid. However, it doesn't use the Kendo JSon datasource because I am using the open source Kendo UI Core grid (you can find more information here). The Kendo UI Core components are simply javascript files that you put in your project and call when required. In this example, I am not using the API to load contents. I am simply creating a table and letting MVC razor render it. Then, in my javascript, I call the Kendo javascript methods to format and complete the grid render. Kendo UI Core gives very useful functionality like pagination, sorting, filtering, column ordering etc. So this is what my table looks like. You can see that I am using Bootstrap too.
<div class="col-md-12">
<table id="user-grid" class="table table-bordered table-striped">
<thead>
<tr>
<th>@Html.DisplayNameFor(model => model.Id)</th>
<th>@Html.DisplayNameFor(model => model.UserName)</th>
<th>@Html.DisplayNameFor(model => model.FirstName)</th>
<th>@Html.DisplayNameFor(model => model.LastName)</th>
<th>@Html.DisplayNameFor(model => model.Email)</th>
<th>@Html.DisplayNameFor(model => model.IsVisible)</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Id)</td>
<td>@Html.DisplayFor(modelItem => item.UserName)</td>
<td>@Html.DisplayFor(modelItem => item.FirstName)</td>
<td>@Html.DisplayFor(modelItem => item.LastName)</td>
<td>@Html.DisplayFor(modelItem => item.Email)</td>
<td>@Html.DisplayFor(modelItem => item.IsVisible)</td>
<td></td>
</tr>
}
</tbody>
</table>
</div>
So now have a nicely formatted table. But we can't do anything with the records in the table. Kendo gives us this functionality. You can see in the code above that there is a blank table header column and also a blank table cell for each row. We can configure Kendo to render command buttons in each row of our table. And it's all done via Javascript in the view.
We will use the table ID as the selector for the Kendo javascript. Read the documentation (link above) to understand the configuration settings.
$(function() {
$("#user-grid").kendoGrid({
height: 500,
sortable: true,
resizable: true,
reorderable: true,
columnMenu: true,
columns: [
{ field: "Id", title: "Id", width: 50 },
{ field: "UserName", title: "UserName", width: 120 },
{ field: "FirstName", title: "FirstName" },
{ field: "LastName", title: "LastName" },
{ field: "Email", title: "Email" },
{ field: "IsVisible", title: "IsVisible", width: 100 },
{
command: [
{ text: "Edit", click: editItem },
{ text: "Delete", click: deleteItem }
],
title: " ",
width: 200
}
]
});
});
The columns
property has a command
property which lets you configure command buttons for any of the columns you want. You can see that I have created two command buttons. One is an Edit button and the other is a Delete button. Each of these commands have a click method associated which can point to any javascript function you like. Here is the Edit javascript function that I used for these two command buttons.
function editItem(e) {
// stop the button doing it's default behaviour
e.preventDefault();
// get the button's parent row so we can get data from the columns
var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
// build the URL for the MVC action that will do something useful with the data
var baseUrl = '@Url.Action("Edit", "Users")';
// a function that navigates to the action (I re-use this in the next command button function too)
go(baseUrl + "/" + dataItem.Id);
}
function go(url) {
window.location = url;
}
So now we have a table with functional rows and an edit command button that will take the user to the edit view for modification. But the delete command is different. We don't need to go to a view (UI) in order to delete the record. We really just want to click the button, have MVC delete the record, refresh the grid and give a message to say whether it worked or not. This is where the asynchronous MVC calls come in. We could just send the user to a view when the delete button is clicked, and then return to the grid once it's finished but that repaints the UI. So we will use an action result called JsonResult to asychronously do the delete and return a result message to the user. Firstly, let me show you the delete javascript function. I am using a component called BootstrapDialog to show a nice dialog prompt. This simply says "Are you sure?".
function deleteItem(e) {
var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
BootstrapDialog.show({
title: 'Delete This Item',
message: 'This will permanently delete this user and they will not be able to log in. Are you sure?',
buttons: [
{
label: 'Yes',
action: function(dialog) {
$.ajax({
url: '@Url.Action("Delete", "Users")',
type: 'POST',
data: { 'id': dataItem.Id },
success: function (result) {
var r = JSON.parse(result);
toastr.success(r.Message);
$("#user-grid").refresh(); // not yet implemented
dialog.close();
},
error: function(result) {
toastr.error(result.Message);
dialog.close();
}
});
}
}, {
label: 'No',
action: function(dialog) {
// not yet implemented
}
}
]
});
}
Let me explain in dot points what will happen when the user clicks the delete button.
- A dialog appears asking the user to confirm they want to delete the record. The user clicks Yes
- Javascript does an HTTP Post action to the MVC action
Users/Delete
- The MVC action returns a Json result message
- AJAX determines if the POST was successful or not
- The Json result is parsed and displayed in a Toastr notification message
- Javascript refreshes the Kendo grid
To make this all work, the MVC action must return a Json object so that the javascript function can consume it. Here is the MVC Delete action code.
[HttpPost]
public JsonResult Delete(int id)
{
string errMessage;
bool delete = _builder.DeleteUser(id, out errMessage);
ViewModelJson result = new ViewModelJson()
{
Result = delete,
Message = errMessage
};
var serializer = new JavaScriptSerializer();
var serializedResult = serializer.Serialize(result);
return Json(serializedResult);
}
///
/// A custom class with properties that carry useful data back to the client.
/// You can extend this class as much as you like to transport more data if you want
///
public class ViewModelJson
{
public bool Result { get; set; }
public string Message { get; set; }
}
So, as a result, the MVC action returns serialized Json to the view. The view parses the Json and decides how to render it to the user.
Feel free to make comments on this and I hope you found it useful.
Til next time ...