Unobtrusive Ajax in ASP.NET MVC

ASP.NET MVC Framework has built-in Ajax enabled helper methods for unobtrusive Ajax support, which simplify the process for Ajax calls in your MVC Web Applications. Those Ajax methods are based of course in jQuery, so if you are familiar with the latter, learning to use them will be a piece of cake. We have shown the tranditional way to make Ajax calls in MVC applications in previous post. This post will guide you step by step showing you an alternative and more straight forward way to make Ajax calls through the built-in helper methods which eliminates the need to add blocks of code in your Views. Let’s start.

Start by creating a new ASP.NET MVC Web Application named “MVCUnobtrusiveAjax”, choosing the Basic template and Razor for view engine. We will create first our domain model. Create a C# class file named “Product.cs” in your models folder. Add a Product class and a Category enumeration as follow.

namespace MVCUnobtrusiveAjax.Models
{
    public class Product
    {
        public int ProductId;
        public string Name;
        public string Description;
        public decimal Price;
        public Category Category;
    }

    public enum Category
    {
        Car,
        Airplane,
        Bicycle,
        Motorcycle
    }
}

Add an Empty MVC “ProductController” controller with three action methods in the controllers folder with the following contents.

using MVCUnobtrusiveAjax.Models;

namespace MVCUnobtrusiveAjax.Controllers
{
    public class ProductController : Controller
    {
        private Product[] products = {
                                         new Product {
                                             ProductId = 1, Name = "Audi A3",
                                             Description = "New Audi A3", Category = Category.Car,
                                             Price = 25000
                                         },
                                         new Product {
                                             ProductId = 2, Name = "VW Golf",
                                             Description = "New VW Golf", Category = Category.Car,
                                             Price = 22000
                                         },
                                         new Product {
                                             ProductId = 3, Name = "Boing 747",
                                             Description = "The new Boing airplane", Category = Category.Airplane,
                                             Price = 2000000
                                         },
                                         new Product {
                                             ProductId = 4, Name = "Boing 747",
                                             Description = "The new Boing airplane", Category = Category.Airplane,
                                             Price = 2000000
                                         },
                                         new Product {
                                             ProductId = 5, Name = "Yamaha 250",
                                             Description = "Yamaha's new motorcycle", Category = Category.Motorcycle,
                                             Price = 5000
                                         },
                                         new Product {
                                             ProductId = 6, Name = "honda 750",
                                             Description = "Honda's new motorcycle", Category = Category.Motorcycle,
                                             Price = 7000
                                         }

                                     };

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

        public ActionResult GetProducts()
        {
            return View(products);
        }

        [HttpPost]
        public ActionResult GetProducts(string selectedCategory)
        {
            if (selectedCategory == null || selectedCategory == "All")
            {
                return View(products);
            }
            else
            {
                Category selected = (Category)Enum.Parse(typeof(Category), selectedCategory);
                return View(products.Where(p => p.Category == selected));
            }
        }

    }
}

This class uses some mock Product objects and has two basic operations, the GetProducts() and the GetProducts(String selectedCategory). The first one, will display all available products while the second one, the selected category’s only. Right click inside a GetProducts action and add a View with the same name. Fill IEnumerable<Product> in the Model class (Strongly-typed View).

@using MVCUnobtrusiveAjax.Models
@model IEnumerable<Product>
@{
    ViewBag.Title = "Get Products";
}
<h2>Get Products</h2>
<table style="background-color:lightcoral">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Description</th>
            <th>Category</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (Product p in Model)
        {
            <tr>
                <td>@p.ProductId</td>
                <td>@p.Name</td>
                <td>@p.Description</td>
                <td>@p.Category</td>
                <td>@p.Price</td>
            </tr>
        }
    </tbody>
</table>
@using (Html.BeginForm())
{
    <div>
        @Html.DropDownList("selectedCategory", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Category)))))
        <button type="submit">Submit</button>
    </div>
}

The Html.BeginForm() helper method, ensures that the form will post back to the [HttpPost] GetProducts action method the selectedCategory value. Before running your application you may want to change the default routing values for the controller and action segments in the RouteConfig.cs file like this.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Product", action = "GetProducts", id = UrlParameter.Optional }
            );
        }

This way when your start your application you will redirected immediately to the following View.

ajaxunobtrusive1

At this time, your application works fine, you can select a category, click the Submit button and get the respective results. Though, Unobtrusive Ajax isn’t supported yet, hence you get a full page reloading. To enable it, make sure you have a key=”UnobtrusiveJavaScriptEnabled” value=”true” in your root Web.config file under the appSettings element.

  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>

Then add jQuery support in the central _Layout.cshtml file. Remove the script sections and style sections and add two references from your script folder. You may have different versions on your scripts folder, so make sure you use the right ones.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
    @RenderBody()
</body>
</html>

Next thing we have to do, is to refactor our ProductController. Replace the old GetProducts actions with the following two.

public PartialViewResult GetProductData(string selectedCategory = "All")
        {
            IEnumerable<Product> data = products;
            if (selectedCategory != "All")
            {
                Category selected = (Category)Enum.Parse(typeof(Category), selectedCategory);
                data = products.Where(p => p.Category == selected);
            }
            return PartialView(data);
        }

        public ActionResult GetProducts(string selectedCategory = "All")
        {
            return View((object)selectedCategory);
        }

We are going to use the GetProductData to render a partial View which will update our table rows according to the selected category. On the other hand, the GetProducts action method now will just pass to the View the selectedCategory value. This way we simplified our code removing the [HttpPost] attribute. Now we have to create our Partial View which will update the table contents. Right click in the GetProductData action and add a Partial View with strong-type IEnumerable<Product>. Paste the following contents.

@using MVCUnobtrusiveAjax.Models
@model IEnumerable<Product>

@foreach (Product p in Model)
{
    <tr>
        <td>@p.ProductId</td>
        <td>@p.Name</td>
        <td>@p.Description</td>
        <td>@p.Category</td>
        <td>@p.Price</td>
    </tr>
}

Change your GetProducts View contents as follow. We need to pass a string as the Model this time, since we have changed our respective action method to pass the selected category.

@using MVCUnobtrusiveAjax.Models
@model string
@{
    ViewBag.Title = "Get Products";
}
<h2>Get Products</h2>
<table style="background-color: lightcoral">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Description</th>
            <th>Category</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        @Html.Action("GetProductData", new { selectedCategory = Model })
    </tbody>
</table>
@using (Html.BeginForm())
{
    <div>
        @Html.DropDownList("selectedCategory", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Category)))))
        <button type="submit">Submit</button>
    </div>
}

Still your application works fine but not Ajax asynchronous calls. Though we render our table contents through an action method which return a partial View. It’s time to enable the Unobtrusive Ajax in our View. Change the GetProducts contents again, like this.

@using MVCUnobtrusiveAjax.Models
@model string
@{
    ViewBag.Title = "Get Products";
    AjaxOptions ajaxOptions = new AjaxOptions
    {
        UpdateTargetId = "productsTable"
    };
}
<h2>Get Products</h2>
<table style="background-color: lightcoral">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Description</th>
            <th>Category</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody id="productsTable">
        @Html.Action("GetProductData", new { selectedCategory = Model })
    </tbody>
</table>
@using (Ajax.BeginForm("GetProductData", ajaxOptions))
{
    <div>
        @Html.DropDownList("selectedCategory", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Category)))))
        <button type="submit">Submit</button>
    </div>
}

Build and run your applications. Select a category and ensure that this time you get your results asynchronously. What we did above is to use an Ajax.BeginFrom Ajax helper method which takes the target action method to invoke and an AjaxOptions object parameter. We configured this object in 5-8 requesting the returned data to replace the “UpdateTargetId” tbody element. So simple. If you want to provide the user with a feedback while making the ajax request you can do it this way.

@using MVCUnobtrusiveAjax.Models
@model string
@{
    ViewBag.Title = "Get Products";
    AjaxOptions ajaxOptions = new AjaxOptions
    {
        UpdateTargetId = "productsTable",
        LoadingElementId = "loadingProducts",
        LoadingElementDuration = 1000
    };
}
<h2>Get Products</h2>

<div id="loadingProducts" style="background-color:cadetblue; display:none">
    <p>Loading Products...</p>
</div>
@* Other code ommited *@

If you wish to prompt the user before making an Ajax request you can to as follow.

    AjaxOptions ajaxOptions = new AjaxOptions
    {
        UpdateTargetId = "productsTable",
        LoadingElementId = "loadingProducts",
        LoadingElementDuration = 1000,
        Confirm ="Do you really want to display products?"
    };

ajaxunobtrusive2

Clicking OK will proceed the Ajax request while selecting Cancel will cancel it. We have used an Ajax.BeginForm helper method to pass the category value in the GetProductData action. What if we wanted to make the same thing through links? You can do this using the Ajax.ActionLink helper method like this.

@*Code ommited *@
<table style="background-color: lightcoral">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Description</th>
            <th>Category</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody id="productsTable">
        @Html.Action("GetProductData", new { selectedCategory = Model })
    </tbody>
</table>
<hr />
<div>
    @foreach (string category in Enum.GetNames(typeof(Category)))
    {
        <div class="ajaxLink">
            @Ajax.ActionLink(category, "GetProductData",
                new { selectedCategory = category },
                new AjaxOptions { UpdateTargetId = "productsTable" })
        </div>
    }
</div>
@* Code ommited *@

ajaxunobtrusive3

Click a category link and notice that you get the same Ajax result. The last thing I wanna show you is the CallBack functions OnBegin, OnSuccess, OnFailure, OnComplete. Add a script with functions in the GetProducts View and bind them to the Ajax links through the AjaxOptions as follow.

@using MVCUnobtrusiveAjax.Models
@model string
@{
    ViewBag.Title = "Get Products";
    AjaxOptions ajaxOptions = new AjaxOptions
    {
        UpdateTargetId = "productsTable",
        LoadingElementId = "loadingProducts",
        LoadingElementDuration = 1000,
        Confirm = "Do you really want to display products?"
    };
}

<script type="text/javascript">
    function OnAjaxRequestBegin() {
        alert("This is the OnBegin Callback");
    }
    function OnAjaxRequestSuccess(data) {
        alert("This is the OnSuccessCallback: " + data);
    }
    function OnAjaxRequestFailure(request, error) {
        alert("This is the OnFailure Callback:" + error);
    }
    function OnAjaxRequestComplete(request, status) {
        alert("This is the OnComplete Callback: " + status);
    }
</script>

<h2>Get Products</h2>

<div id="loadingProducts" style="background-color: cadetblue; display: none">
    <p>Loading Products...</p>
</div>

<table style="background-color: lightcoral">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Description</th>
            <th>Category</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody id="productsTable">
        @Html.Action("GetProductData", new { selectedCategory = Model })
    </tbody>
</table>
<hr />
<div>
    @foreach (string category in Enum.GetNames(typeof(Category)))
    {
        <div class="ajaxLink">
            @Ajax.ActionLink(category, "GetProductData",
                new { selectedCategory = category },
                new AjaxOptions { UpdateTargetId = "productsTable",
                OnBegin = "OnAjaxRequestBegin",
                OnFailure = "OnAjaxRequestFailure",
                OnSuccess = "OnAjaxRequestSuccess",
                OnComplete = "OnAjaxRequestComplete"})
        </div>
    }
</div>
@using (Ajax.BeginForm("GetProductData", ajaxOptions))
{
    <div>
        @Html.DropDownList("selectedCategory", new SelectList(
            new[] { "All" }.Concat(Enum.GetNames(typeof(Category)))))
        <button type="submit">Submit</button>
    </div>
}

Click a Category link and pay attention to the Ajax request life-cycle.

ajaxunobtrusive4

That’s it, this is the way to use Unobtrusive Ajax in ASP.NET MVC applications. In later post, we ‘ll see how to get JSON data from your actions methods when using Unobtrusive Ajax. You can download the MVCUnobtrusiveAjax project we have created from here.

In case you find my blog’s content interesting, register your email to receive notifications of new posts and follow chsakell’s Blog on its Facebook or Twitter accounts.

Facebook Twitter
.NET Web Application Development by Chris S.
facebook twitter-small
Advertisements


Categories: ASP.NET

Tags: , ,

12 replies

  1. I would like to thank you for the efforts you have
    put in writing this site. I really hope to check out the same high-grade blog posts by you later on as well.
    In truth, your creative writing abilities has inspired
    me to get my own blog now 😉

  2. I was so frustrated prior to working through your article. I even started reading your article and said “No – this doesn’t work!”. But then I stopped. I thought – This guy wouldn’t have made an article like this without having it working… So, I went back to basics. I followed each of your steps, duplicating them on my end as I went. Success.

    Thanks

  3. Hey Buddy,

    Thanks for the post..
    Easy to understand.. 🙂

  4. Thanks for the posting this article, but I have some issues while duplicating the steps you mentioned above.
    I am new to ASP.NET MVC and I was having problem with AJAX. I came across your article and followed each and every step, but still when I click submit, the partial view is rendered on a new page rather than on the same page.

  5. Hi there! I could have sworn I’ve been to this website before but
    after browsing through some of the post I realized it’s
    new to me. Anyways, I’m definitely happy I found it and I’ll be
    bookmarking and checking back frequently!

  6. Can I still download your code? The page says it can’t find the link

Trackbacks

  1. Retrieve JSON data from MVC Controllers in ASP.NET MVC | chsakell's Blog
  2. Comment on Unobtrusive Ajax in ASP.NET MVC by Christos S.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

mohitgoyal.co

Automating infrastructure one line at a time

Diary Of A Programmer

Because every day is worth noting

Chara Plessa

The purpose of this blog is to broaden my education, promote experimentation and enhance my professional development. Albert Einstein once said that “If you can’t explain it simply, you don’t understand it well enough” and I strongly believe him!

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Kumikoro

A Front End Developer's Blog

Muhammad Hassan

Full Stack Developer | ASP.NET | MVC | WebAPI | Advanced Javascript | AngularJS | Angular2 | C# | ES6 | SQL | TypeScript | HTML5 | NodeJS, MS candidate @LUMS, Grad & EX-Adjunct Faculty @NUCES-FAST, seasonal blogger & open-source contributor. Seattle, WA.

Software Engineering

Web development

IEvangelist

.NET, ASP.NET, C#, MVC, TypeScript, AngularJS

leastprivilege.com

Dominick Baier on Identity & Access Control

Happy DotNetting

In Love with Technology

Knoldus

Knols of experience to your advantage

knowshnet

Search - Read - Request - Share

Rahul's space

Learn, Share and Grow with me !

Dhananjay Kumar

Developer Evangelist @Infragistics | MVP @Microsoft

SQL Authority with Pinal Dave

SQL Server Performance Tuning Expert

Conficient Blog

Random bits of tech from @conficient

Code! Code! Code!

SOLID & KISS

Code Wala

Designing and coding

Microsoft Mentalist

A way to start with Microsoft Technologies

%d bloggers like this: