Sunday, January 30, 2011

Using jQuery and ASP.NET MVC to submit a form via AJAX and refresh the page

There will certainly come a time when you will need to perform a post via AJAX, update some data on the server, then get a response back and update your HTML. This can be a daunting task, but fortunately with the help of jQuery and MVC, this is quite easy and you won't even have to maintain state on the client. Source code at the bottom.


We'll be using a simple jQuery .ajax post coupled with the .load. Let's get to some code.

1. Start with creating a new MVC3 web application. We'll just create an empty application and add our own Views and Controllers.



2. Next we'll add a Controller called "AjaxPostDemoController", add a new Views folder called "AjaxPostDemo", and a View called "Index".



Our Controller code simply returns the Index view:

public ActionResult Index()
        {
            return View();
        }


Our Index will simply contain 2 divs. One to hold a static message from Index page, then a second div that will have its content updated via our AJAX call. We'll also add the jQuery script reference which we'll need later.


<html>

<head>

    <title>Index</title>

    <script src="../../Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>

</head>
<body>
    <div>
        Hello From Index
    </div>

 
    <div id="ajaxPostContainer">
    </div>
</body>
</html>


Finally, in our Global.asax.cs file, we'll just update the default route to map our AjaxPostDemo controller:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "AjaxPostDemo", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
 
        }


Running the app shows us our "Hello From Index" message and not much more. Now for the AJAX view.

3. Add a new View to the AjaxPostDemo views folder called "AjaxView". Add the following code:

@model  DateTime
@{
    Layout = null;

}

 
This is the AjaxView! The time is @Html.TextBoxFor(m => m.Date)


As you can see, we are declaring the Model for the page as a DateTime object, then using a TextBoxFor to display the time.

4. In our Controller, we'll create a new method to return this view. In this method we create a new DateTime object and set its value to DateTime.Now, then pass this to the AjaxView View.

public ActionResult AjaxView()

        {

            DateTime time = DateTime.Now;
            return View(time);
        }


5. Now, back in our Index view, we'll add the jQuery code to call the AjaxView.

<script type="text/javascript">

        $(function () {
            loadAjaxView();

        });
 
        function loadAjaxView() {

            $("#ajaxPostContainer").empty();

            $("#ajaxPostContainer").load("/AjaxPostDemo/AjaxView");
        }
    </script>



We have 2 functions here. The first is simply a document.ready function to call the loadAjaxView() on the page load. The second function actually does the work. First it emptys the ajaxPostContainer of any HTML content, then makes an AJAX request to our AjaxView controller method, which returns its HTML. The .load method will append this content to the ajaxPostContainer. Pretty cool!

Running the app now, we get the "Hello From Index" message, along with the message from the AjaxView, with the current timestamp.



6. Now let's setup the app up to handle a post. First we'll a simple class that will act as our response object. We'll call this AjaxPostResponse, and it will only have 2 properties:

public class AjaxPostResponse
    {
        public string Message { get; set; }
        public string Date { get; set; }
    }


7. Next comes our Post Controller method. As you can see it accepts our DateTime object as a parameter. We'll create a new response object and set its properties accordingly. In the return, we pass back a JsonResult using our response object as the parameter:

[HttpPost]
        public ActionResult AjaxPost(DateTime date)
        {
            AjaxPostResponse response = new AjaxPostResponse();
            response.Date = date.ToString();
 
            if (date <= DateTime.Now)
                response.Message = "You entered a date the is less than now!";
            else
                response.Message = "You entered a date in the future!";
 
            return Json(response);
        }


8. Back in the Index View, we need to add some things to wrap it all up. First we add a BeginForm and EndForm, specifying our post Controller method and giving the form an ID so we can reference it via jQuery.

Next, we add a new div that will hold the contents of our response object after the post.

We'll need to add a button that will invoke the javascript function to submit the form.

Finally, we add the function that will submit the form. We're using a basic jQuery .ajax call. We serialize the form using .serialize, then call .ajax to submit. Using the success funcation via .ajax, we can get the JsonResult from our controller method and update our div placeholder. If an error occurs, we use the error function to handle that.

<body>
    @{ Html.BeginForm("AjaxPost", "AjaxPostDemo", FormMethod.Post, new { @id = "AjaxPostForm" }); }
 
    <div>
        Hello From Index
    </div>
 
    <div id="ajaxPostContainer">
    </div>
 
    <!-- Div to hold the response to the AJAX Post -->
    <div id="ajaxPostMessage">
    </div>
 
    <input type="button" value="Submit" onclick="submitForm()" />
 
    @{ Html.EndForm(); }
 
    <script type="text/javascript">
        $(function () {
            loadAjaxView();
        });
 
        function loadAjaxView() {
            $("#ajaxPostContainer").empty();
            $("#ajaxPostContainer").load("/AjaxPostDemo/AjaxView");
        }
 
        function submitForm() {
             dataString = $("#AjaxPostForm").serialize();
 
             $.ajax({
                 type: "POST",
                 url: "/AjaxPostDemo/AjaxPost",
                 data: dataString,
                cache: false,
                 dataType: "json",
                 success: function (data) {
                     $("#ajaxPostMessage").empty();
                     $("#ajaxPostMessage").html(data.Message + " : " + data.Date);
                 },
                 error: function (XMLHttpRequest, textStatus, errorThrown) {
                     $("#ajaxPostMessage").empty();
                     $("#ajaxPostMessage").html(XMLHttpRequest + "<br />" + textStatus + "<br />" + errorThrown);
                 }
             });
 
             loadAjaxView();
        }
    </script>
</body>


Running this new page gives us the same result as before, but now we have a Submit button to post the form.



Place a break point on your post controller method, then change the date to 12/31/2011, and hit submit.



As you can see, the POST took place and the we're in the controller method with new data from the View.



Now the page has been updated with the JsonResult sent back from the controller, all without a PostBack.

Enjoy!

Source Code:

5 comments:

  1. Clear post, thank you. Any reason you didn't attach the click handler to the button programmatically instead of using the onclick="submitForm()" method?

    $('#mybutton').click(function(){}

    ReplyDelete
  2. Thanks! No particular reason for using the onclick, your approach works as well.

    ReplyDelete
  3. Wondering if I am asking the impossible, if I am, forgive me as I am new to mvc3, jquery.
    I just posted my question on stackoverflow.

    Basically how can I get my Controller (in MVC3/razor) to return a popup when the user types in a url that goes to my application; without loading up my application.

    The url would have querystring params which would magically get converted to my view model which is a param in my Controller method. I then want a popup to be displayed.

    Is that impossible to do??

    ReplyDelete
  4. Thanks Brandon!

    I had a little mistake implementing the codes. Since entire the form is empty of elements, jQuery didn't returned anything with ".serialize()" method.
    I logged the result in the console and found that those empty "div" tags will not seen by the method. It fixes when I added a text input in the form.

    By the way, if you want .serialize() method recognize elements as well, it's better to specify both "id" and "name" for them.

    ReplyDelete