ASP.NET MVC: Show Busy Indicator on Form Submit using JQuery and Ajax

Posted on December 22 2013 05:57 PM by John Atten in CodeProject, C#, ASP.NET MVC, ASP.Net   ||   Comments (6)

ajaxAs we all know, users are impatient. We also know that if we don't tell them our application is "doing something" they tend to do silly things like click the mouse repeatedly, seeking some sign that the requested action is indeed being performed.

For this reason, it is a good idea to throw up some sort of "busy" indicator when a user-initiated action may invoke a long-running process which requires them to wait.

In this article we are going to demonstrate a very basic way to achieve this which will be effective in most cases.

If you are new to ASP.NET MVC, you may want to check out my previous posts on Routing Basics and Route Customization as well. While not critical to understanding what we discuss here, some background on the basics of MVC routing will certainly help in understanding what's going on in places.

Image by Martin Abegglen | Some Rights Reserved

The fully-functioning source code for the example used in this article is available from my Github Repo. You will need to use Nuget Package Restore to pull down all the package goodness so that the project will run. If you are unsure what that means, see Keep Nuget Packages Out of Source Control with Nuget Package Manager Restore.

What the Hell is Ajax?

Ajax is an acronym for Asynchronous JavaScript and XML. Ajax represents a broad range of technologies used to facilitate client-server communication without interfering with the current state of the page.

In the main, we most often use the term when we speak of making an Ajax Request to the server, usually in the context of posting or retrieving data, and updating only a portion of the page, as opposed to completely refreshing a page simply because a small sub-area needs to be updated.

Upon the introduction of the term circa 2005, XML represented what many believed would be the primary data interchange format for this type of client-server transaction. Since then, JavaScript Object Notation (JSON) has become more and more of a standard. While XML is not gone, you are as likely to send and receive JSON in the course of an Ajax request as you are XML.

At present, we often find Ajax used in conjunction with JQuery (more properly, one often uses JQuery to make an Ajax request) when we need to retain the current state of our page while a request is made to the server and then update the portion of the page affected by the information returned from the request.

Adding a Busy indicator to a Page with JQuery and Ajax

The common way to display a busy indicator on a page while we wait for a long-running request to return from the server is to throw up an animated Gif, to let users know something is in fact happening. Equally as often, we need to then remove or hide the animation when the process completes, and refresh the portion of the page affected by our new data.

There really is no good way to do this from the server side in an MVC application – we need to use Ajax. We do this with JavaScript. in this case, we are going to use the popular JQuery library, because it is ubiquitous, it ships with and in integrated into the core MVC project.

Let's take a look at a quick example.

Basic Example – The View

First we will look at a basic view. We've kept it simple here – we will display a couple of data entry fields, and a button to submit the data entered by the user. The essential cshtml code for this is as follows:

A Basic View:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", 
    "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}

 

As we can see, there is nothing unusual here. We use standard Razor syntax to create a form with a couple data entry fields for First and Last names, and a submit button. In the opening of the using statement, The form is wired to a method named LongRunningDemoProcess on our HomeController.

In the form declaration, the syntax is:

Html.BeginForm(<actionName>, <controllerName>, <Http Method Type>, <Html Attributes>)

 

The above basically determines what happens when the form is submitted, and specifies a specific method, and a specific controller as a target for the Http request. It also defines the request method type (GET, POST, PUT, DELETE).

In our example case, we want to send our model data in the POST body. On our controller, the LongRunningDemoProcess method will need to be decorated with the [HttpPost] attribute, and accept the POST body payload as an argument.

Now let's take a look at our Home controller.

Basic Example – The Controller

For our purpose here, I have simply added a method named LongRunningDemoProcess to the stock ASP.NET MVC Home Controller which is part of the default MVC 5 project template:

A Basic Controller with Long-Running Process:
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
    [HttpPost]
    public JsonResult LongRunningDemoProcess(DemoViewModel model)
    {
        Thread.Sleep(1000);
        return Json(model, "json");
    }
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

 

In the above, we use Thread.Sleep to mimic a long-running process on the server. You will need to add a reference to the System.Threading namespace in the using statement imports at the top of your file.

As we can see, LongRunningDemoProcess is the target of for our HTTP Post request from the Index view when form submission occurs. We want to use the Json data returned from the request to update our view, and let the user know their submit succeeded.

At the moment, though, things are not all they could be. We can load up our page, type some data into the form, and hit submit. What happens next, though, is that our page freezes up while the long-running process runs, and then our Json data is returned to a new tab in our browser (Chrome) or we are prompted to Save or Open (IE). 

What we WANT to happen is to display a busy indicator, and then somehow indicate to the user that their submission was successful (or something!).

Get a Busy Indicator GIF

One of the more useful little sites I've found recently is AjaxLoad.Info, which presents a handy library of various customizable animated GIFs. You can specify some basic parameters (Size, Type, Foreground Color, etc.) and then download, ready to use.

Go get one, add it to your project in a location which makes sense (I am using ASP.NET MVC 5 project in VS 2013, so I placed mine at the root level of the Content folder).

Next, let's modify our view, and add a div we can show and hide as needed, and which contains our gif:

The View, with Animated GIF and Placeholder for Submission Result

In the highlighted area below, we have added a div element to contain our Gif. While we're at it, we also added another div, to hold the result when our long-running process returns:

Modified View with Animated Gif and Placeholder Div:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}
   
// We want to show this while the server process is running:
<div id="divProcessing">
    <p>Processing, please wait . . . <img src="../../Content/ajax-loader.gif"></p>
</div>
  
// We want to display the result from our submission in here:
<div id="divResult">
</div>

 

Now, in order to tie all this together, we need to add an Ajax request.

Adding the Ajax Request

The easiest way to achieve what we want is to use JavaScript on the client to show our animated gif, and then submit our data to the server. We also want to be able to respond when the Json is returned by the server by updating our page, without refreshing the whole thing.

For our example, I am going to add the JavaScript right on the view. This may or may not make sense in a production application, but we will do it here for simplicity.

I've added the JQuery code below our now-familiar view:

The View, with JQuery Added:
@model JqueryBusyExample.Models.DemoViewModel
@{
    ViewBag.Title = "Home Page";
}
  
<h3>Enter Your Name and Submit:</h3>
  
@using (Html.BeginForm("LongRunningDemoProcess", "Home", FormMethod.Post, 
    new { encType = "multipart/form-data", id="myform", name = "myform" }))
{
    <div class="form-group">
        @Html.LabelFor(model => model.FirstName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.LastName, 
            new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    
    <input type="submit" name="operation" id="process" value="process" />
}
    
// We want to show this while the server process is running:
<div id="divProcessing">
    <p>Processing, please wait . . . <img src="../../Content/ajax-loader.gif"></p>
</div>
    
// We want to display the result from our submission in here:
<div id="divResult">
  
</div>
  
@section Scripts {
  @Scripts.Render("~/bundles/jqueryval")
  
  <script type="text/javascript">
  
    $(document).ready(function () {
  
      // Hide the "busy" Gif at load:
      $("#divProcessing").hide();
  
      // Attach click handler to the submit button:
      $('#process').click(function () {
          $('#myform').submit();
      });
  
      // Handle the form submit event, and make the Ajax request:
      $("#myform").on("submit", function (event) {
        event.preventDefault();
  
        // Show the "busy" Gif:
        $("#divProcessing").show();
        var url = $(this).attr("action");
        var formData = $(this).serialize();
        $.ajax({
          url: url,
          type: "POST",
          data: formData,
          dataType: "json",
          success: function (resp) {
  
            // Hide the "busy" gif:
            $("#divProcessing").hide();
  
            // Do something useful with the data:
            $("<h3>" + resp.FirstName + " " + resp.LastName + "</h3>").appendTo("#divResult");
          }
        })
      });
    });
  </script>
}

 

In the above, we start with the standard JQuery document.ready function, and hide the div containing our animated Gif.

We then attach the form submit event to the click event of the submit button we defined in our View (Isn't it already attached, you ask? We'll get to why we do this in a moment), and then we handle the form submit event.

Handling the Form Submit Event with an Ajax Request

This is where things get interesting. There are a few things occurring inside this function, in a rather specific order. Let's walk through it. 

First, we want to prevent the default action using (wait for it . . .) the JQuery preventDefault() method when the form submit event occurs, otherwise, form submission will proceed automatically, as before, and any code we place in our handler will not work properly.

Next, we show our animated Gif. Once shown, it will do its thing, giving the user the impression that something  useful is happening.

Finally, we collect what we need from our form in order to submit our Ajax request. First, we grab the Url and stow it in a variable by accessing the form's action attribute using JQuery. The action attribute essentially pulls a Url which matches the route we specified in the form declaration (remember?  ActionMethod, ControllerName, HttpMethodType, etc?).

Next, we serialize the form data ( in this case, our model data) into another variable, again using JQuery.

Once we have collected these things, we can set up our Ajax request by making the property assignments shown. Note that we need to specify the data type as Json, so that when the request returns, JQuery will recognize it as valid Json. Then, we come to the all-important success property.

We have assigned a function here, which will execute when our long-running process returns a response. The resp argument will contain the Json returned from our controller method. In this very simple case, it is merely the data we already submitted. However, it could just as easily be the result of persisting some data in a database on the server. In any case, we know that when this function is called, our remote, long-running task has completed.

That being the case, the first thing we want to do is hide our animated Gif. Next, we push the returned data into the div we set up as a placeholder (after doing a little formatting, of course).

Fairly Simple, but Not Always Obvious

And there you have it. The example we have examined is pretty basic, but gives a good look at how to both display a busy indicator, and how to make Ajax requests from your ASP.NET MVC page.

There is a lot more to learn about Ajax, and it provides a foundation for much of the interactivity found in today's modern websites. I welcome comments and constructive criticism in the comments section below (especially if I got something wrong here . . .)!

You can clone or download the example project from my Github Repo. Note that you will need to use Nuget Package Restore to pull in all the package dependencies. If you don't know what that means, see Keep Nuget Packages Out of Source Control with Nuget Package Manager Restore.

Additional Resources and Items of Interest

 

Posted on December 22 2013 05:57 PM by John Atten     

Comments (6)

C# - Wildcard Search Using LINQ

Posted on August 7 2013 09:26 PM by John Atten in C#, CodeProject   ||   Comments (2)

telescope-by-salendron-320

Need to create a wild-card style search method using LINQ? It is ridiculously easy.

Below is a simple example, using the .StartsWith method from the string class. In an application for work, I needed a "Google-like" search performed on a list where with each keystroke, the list is filtered to more closely match what is typed into the search input box.

.At work I am stuck still using Windows Forms, so what you see below is used to populate the venerable Winforms Treeview. The filtering piece, though, can be useful in other situations.

Image by salendron | Some rights reserved

 

I'm stepping away briefly from my examination of routing in ASP.NET MVC and ASP.NET Web API to toss this little gem out there. There's nothing fancy or new here, but if you are new to LINQ, or just never had to use LINQ with a wild card search scenario before, this might be helpful.

Basic LINQ Query Using the .StartsWith Method

The wildcard search below will filter the IEnumerable<Company> on the Company.CompanyName property, as well as on the Company.CompanyCode property (which represents the company instance's US Tax ID number).

Basic Code for the Wildcard Search Using LINQ .StartsWith Method:
public void LoadListView(IEnumerable<Company> companies, string searchString = "")
{
    IEnumerable<Company> query;
    if (!(searchString.Length > 1))
    {
        query = companies;
    }
    else
    {
        query =
        (from company in companies
         where company.CompanyName.StartsWith(searchString, 
            StringComparison.OrdinalIgnoreCase) 
        || company.CompanyCode.StartsWith(searchString, 
            StringComparison.OrdinalIgnoreCase)
         orderby company.CompanyName
         select company);
    }
    foreach (var filteredCompany in query)
    {
        // Load the ListView control . . .
    }
}

 

In the above, a source Enumerable is passed in and becomes the target for the filter. Additionally, an optional search string is passed, with an empty string as default. The way the method above is set up, if the search string is not at least one character in length, the entire contents of the source Enumerable are shown. Once the length of the input string is greater than a single character, the filter is applied.

The Search Function Before Typing in the Search Textbox:

search-companies-before-typing

As you can see, once we begin to type, and the input string ( in this case, "al" so far) length exceeds a single character, the filtering begins:

The Search Function After Typing in the Search Textbox:

search-companies-after-typing

Depending upon your needs, you might modify this to display nothing until an input string is present. Also, depending upon the size of your original data set, you might need to apply other strategies to winnow down the starting instance of IEnumerable passed in (in my case, the entire list of companies is supplied. We may have to look at that if it grows too large).

For Case-Insensitivity Use StringComparison.OrdinalIgnoreCase

By default, LINQ will perform a case-sensitive comparison. Not especially useful in this context.  To tell LINQ to ignore case, I have provided a second parameter to the StartsWith method, StringComparison.OrdinalIgnoreCase.

LINQ filtering can also utilize the corresponding string methods EndsWith and Contains which perform their respective functions in the manner implied by their names.

Additional Items of Interest:

 

Posted on August 7 2013 09:26 PM by John Atten     

Comments (2)

Let the Spin, Begin: Google CEO Larry Page and Facebook CEO Mark Zuckerberg Read from the Same . . . um . . . Script?

Posted on June 7 2013 04:15 PM by John Atten in Constitution, Security, SOPA, Free Internet   ||   Comments (0)

seal200-c1461dd881869c32e36ed518f969a37ce2ed1614-s1In the wake of news reports that the National Security Agency (NSA) has been secretly mining data from large internet service providers Facebook CEO Mark Zuckerberg and Google CEO Larry Page both released statements about their respective company's position on the matter.

While both read like well-thought-out, carefully crafted (but very "corporate") responses, and appear to support the notion that neither company actually participates in a program like the alleged PRISM, both statements also read like thinly disguised paraphrases of the same script. You know, almost like they were being spoon fed.

We've Never Heard of PRISM

Both companies also appear to craft their statements using very carefully chosen language. From Zuckerbergs post on Facebook (Emphasis mine):

"Facebook is not and has never been part of any program to give the US or any other government direct access to our servers. We have never received a blanket request or court order from any government agency asking for information or metadata in bulk, like the one Verizon reportedly received. And if we did, we would fight it aggressively. We hadn't even heard of PRISM before yesterday."

And now, From Larry Page's post, released shortly prior to Zuck's statement (again, emphasis mine):

"First, we have not joined any program that would give the U.S. government—or any other government—direct access to our servers. Indeed, the U.S. government does not have direct access or a “back door” to the information stored in our data centers. We had not heard of a program called PRISM until yesterday . . . Until this week’s reports, we had never heard of the broad type of order that Verizon received"

Interesting that in both instances, each CEO is very specific in stating what their company is not doing. They have not granted the government direct access to their servers. They have never received a blanket request such as that reportedly received by Verizon. If you read the full content of each CEO's statement you will find that they are essentially paraphrasing each other, or some third source.

Apple spokesman Steve Dowling also responds with a similar statement:

"We have never heard of PRISM. We do not provide any government agency with direct access to our servers, and any government agency requesting customer data must get a court order."

When asked whether Apple had joined the joint NSA-FBI data collection program, Apple declined to comment, according to the Huffington Post.

Only with a Court Order?

OriginalAll of the companies that have responded publicly make some form of statement indicating that they comply only with court orders, and under applicable law. Each gives some lip-service to resisting attempts by government to compromise the privacy of users. However, in an article from the Washington Post, legal director of the American Civil Liberties Union states:

"I would just push back on the idea that the court has signed off on it, so why worry? This is a court that meets in secret, allows only the government to appear before it, and publishes almost none of its opinions. It has never been an effective check on government."

Bad things are happening on the web. Governments around the world seek to monitor and control this invaluable communications resource. Totalitarian governments like those of China and Iran have set the example for what we, in the US, do not want to see happen.

Except, well, it kind of is happening, and has been for several years now.

I understand that, to paraphrase US President Barack Obama, you can't have 100% security, and 100% freedom, and 100% privacy with zero inconvenience. But the lack of transparency and sheer underhandedness of the US government in this case appears to be not only appalling, but growing by the minute.

It's not the Crime that Kills You, It's the Cover-up

We don't know all the facts at this point, and we likely never will. But paying close attention to the manner in which the big internet providers respond is at the very least interesting, and quite possibly a sign that there is, indeed, more here than meets the eye.

I encourage the Googles and Facebooks and Apples of the Silicon Valley to remember the old political adage: It's not the crime that kills you, it's the cover-up.

 

Posted on June 7 2013 04:15 PM by John Atten     

Comments (0)

About the author

My name is John Atten, and my username on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, Java, SQL Server 2012, learning ASP.NET MVC, html 5/CSS/Javascript. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten@typecastexception.com

Web Hosting by