By design in a MVC web application every action that you perform does a post back and/or navigation to the interface that implements it. If you want to add for example a new entry in the database you’ll have to navigate to “Create a new entry” url where you have a submit button that does a post back to save the data and returns you the result. In my “Post to Wall” app I am trying to avoid such a behavior, any operation that involves calling a controller action must not redirect or post back, this can be accomplished in several ways.
Microsoft provides a helper class named Ajax, with this helper you can create an async submit form by using Ajax.BeginForm, but for my app I chose to use the $.ajax implementation from the JQuery library, I prefer to use Microsoft’s ajax only combined with the WebGrid component. I’ve chosen the jQuery’s ajax because it makes the code easy to debug and in my opinion it’s more compact and straightforward. You can easily debug JQuery with Firefox and Firebug, just set a break point in Firebug to watch the ajax call and another break point in VS.NET in the controller’s method, it would be nice to just use VS.NET but Firebug is a must when it comes to javascript debugging.
There are several steps in order to accomplish an asynchronous data exchange:
- Add a jQuery event handler for the html element that triggers the action
- Call the controller desired action using JQuery ajax
- Handle the json formatted data received from the server based on the response type (success or fault)
- Update the user interface (html) with jQuery
Lets see this implemented in my Post to Wall app:
In Index.cshtml declare a input button like this:
Index.cshtml
<input name="submit" type="submit" value="Post" class="post-button fg-button ui-state-default"/>
In the controller expose a JsonResult action that will perform a insert operation in the database:
TextNotesController.cs
[HttpPost]
[ValidateInput(false)]
public JsonResult PostNote(string content)
{
string message = string.Empty;
var n = TxtNote.CreateTxtNote(0);
n.Title = content;
n.Created = DateTime.Now;
try
{
db.AddToTxtNotes(n);
db.SaveChanges();
}
catch (Exception ex)
{
message = ex.Message;
}
[ValidateInput(false)]
public JsonResult PostNote(string content)
{
string message = string.Empty;
var n = TxtNote.CreateTxtNote(0);
n.Title = content;
n.Created = DateTime.Now;
try
{
db.AddToTxtNotes(n);
db.SaveChanges();
}
catch (Exception ex)
{
message = ex.Message;
}
return Json(new
{
Html = this.RenderPartialView("_NotesList", new List<TxtNote>() { n }),
Message = message
}, JsonRequestBehavior.AllowGet);
}
{
Html = this.RenderPartialView("_NotesList", new List<TxtNote>() { n }),
Message = message
}, JsonRequestBehavior.AllowGet);
}
Note that this method has the ValidInput attribute set to false, I want to be able to receive information that contains characters specific to html like < > ‘ “, those characters will be escaped so it will not break the Index.cshtml code, in order to escape the text I am using System.Security.SecurityElement.Escape method.
The JsonResult consists of two elements, the Html that represents the new entry in the database and the a message in case any error occurs. To make the html for my new entry I am using a partial view, this view will iterate throw a list of notes and make list elements for each one, the PostNote method inserts only one note so it will generate a single li. This partial view is used to generate one or several notes html, so I can use it as well for search results method.
_NotesList.cshtml
@model IEnumerable<MvcSp.Models.TxtNote>
@using MvcSp.Helpers
@foreach (var item in Model)
{
<li class="bar-@item.Id">
<div align="left">
<a href="#" title="Delete" class="del-note" id="@item.Id"><span class="ui-icon ui-icon-trash"style="float:right; margin-right:10px;"></span></a>
<a href="#" title="Edit" class="edit-note" id="edit-@item.Id"><span class="ui-icon ui-icon-pencil"style="float:right; margin-right:1px;"></span></a>
<a href="#" title="Mail" class="mail-note" id="mail-@item.Id"><span class="ui-icon ui-icon-comment" style="float:right; margin-right:1px;"></span></a>
<p class="note-text">@MvcHtmlString.Create(@item.Title.Prep()) <br /><em>@item.Created</em></p>
</div>
</li>
}
@using MvcSp.Helpers
@foreach (var item in Model)
{
<li class="bar-@item.Id">
<div align="left">
<a href="#" title="Delete" class="del-note" id="@item.Id"><span class="ui-icon ui-icon-trash"style="float:right; margin-right:10px;"></span></a>
<a href="#" title="Edit" class="edit-note" id="edit-@item.Id"><span class="ui-icon ui-icon-pencil"style="float:right; margin-right:1px;"></span></a>
<a href="#" title="Mail" class="mail-note" id="mail-@item.Id"><span class="ui-icon ui-icon-comment" style="float:right; margin-right:1px;"></span></a>
<p class="note-text">@MvcHtmlString.Create(@item.Title.Prep()) <br /><em>@item.Created</em></p>
</div>
</li>
}
The last piece of code needed is the JQuery function:
Index.cshtml
//post note
$(function () {
$(".post-button").click(function (e) {
e.preventDefault();
var boxval = $("#content").val();
var dataString = 'content=' + boxval;
if (boxval == '') {
showError("Can't post an empty note");
}
else {
showLoader('Saving note');
$.ajax({
type: "POST",
url: "TextNotes/PostNote",
data: dataString,
cache: false,
dataType: "json",
success: function (data) {
if (data.Message) {
$("#flash").hide();
showError(data.Message);
} else {
$("ol#update").prepend(data.Html);
$("ol#update li:first").slideDown("slow");
document.getElementById('content').value = '';
$("#flash").hide();
}
}
});
}
return false;
});
}); //end post note
$(function () {
$(".post-button").click(function (e) {
e.preventDefault();
var boxval = $("#content").val();
var dataString = 'content=' + boxval;
if (boxval == '') {
showError("Can't post an empty note");
}
else {
showLoader('Saving note');
$.ajax({
type: "POST",
url: "TextNotes/PostNote",
data: dataString,
cache: false,
dataType: "json",
success: function (data) {
if (data.Message) {
$("#flash").hide();
showError(data.Message);
} else {
$("ol#update").prepend(data.Html);
$("ol#update li:first").slideDown("slow");
document.getElementById('content').value = '';
$("#flash").hide();
}
}
});
}
return false;
});
}); //end post note
This function adds a event handler to my post button using $(“post-button”).click, I am using the css class to refer the button, another way is by using the id of the element, like is done to obtain the text from the textarea: $(“#content”).val(). the js will checks if the textarea is empty and will pop-up a jQuery UI dialog with the warning message, same for any errors occurs on the server side.
After the note is extracted from the textarea is appended to the json formatted data, I am using the same name “content=” as used in the controller method. Before starting the ajax call I am displaying a message to the user so he will know that an async operation is started, showing a gif animation is a good practice. For the rest of the js code involved in this operation you should download the project and see for yourself.