Simple Ajax scenario in ASP.NET MVC Part 3: graceful degradation

This follows part 1 and part 2.

Let’s continue improving our previous example by adding more requirements. This time supporting clients without JavaScript aka graceful degradation for our Ajax feature.

Graceful degradation in web design refers to supporting less capable clients correctly. In our case, using Ajax will currently only work in browsers with JavaScript enabled.

While that is most browsers, the best scenario possible is to support all browsers, even those who have disabled JavaScript or do not support it.

Supporting clients without JavaScript is sometimes required for conforming to standards or for accessibility reasons.

Changing our previous example

If we disable JavaScript and try our current version of our example application our filtering feature doesn’t work anymore (as well as our jQuery DataTables plugin).

jsdisabled

We are currently using the onclick event on a checkbox. Since this won’t submit the form without JavaScript we need to show the input element we had hidden in Part 2 when JS is disabled.

We still want to have that element hidden if JavaScript is enabled so instead of relying on style=”display: none” we will use JavaScript to hide the input element.

using (Ajax.BeginForm("FilterDepartmentsAjax",
                       new AjaxOptions
                       {
                           HttpMethod = "POST",
                           InsertionMode = 
                               InsertionMode.Replace,
                           UpdateTargetId = 
                               "department-table",
                           OnSuccess = "tableUpdated",
                       }))
{
    <label>Filter: </label>
    @Html.CheckBoxFor(model => model.Filtered, 
                      new { onclick = "$(this).parents('form:first').find(':submit')[0].click();" })
    <input id="input-filter" type="submit" value="Submit" />
}

<div id="department-table">
    @{ Html.RenderPartial("DepartmentsTablePartial", Model); }    
</div>

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#input-filter').hide();
            $('#deparment-table').DataTable();
        });

        function tableUpdated() {
            $('#deparment-table').DataTable().draw();
        }
    </script>    
}

Now our button is shown only when JavaScript is disabled.

The next problem is that we want to return a PartialView when we are dealing with an Ajax request and a View when we are dealing with a regular request.

Otherwise we will either get only the partial view with JavaScript disabled or loose our jQuery DataTables functionality when we return a whole view.

The correct this we change the FilterDepartmentsAjax method return type to ActionResult and use if (Request.IsAjaxRequest()) like so:

[HttpPost]
public ActionResult FilterDepartmentsAjax(
                       DepartmentsModel model)
{
    var updatedModel = new DepartmentsModel
    {
        Filtered = model.Filtered,
        Departments = GetDepartments(model.Filtered),
    };

    if (Request.IsAjaxRequest())
        return PartialView("DepartmentsTablePartial", 
                           updatedModel);

    return View("Index", updatedModel);
}

Problem solved.

You can find the whole project on GitHub and the specific changeset for this blog post.

 

Simple Ajax scenario in ASP.NET MVC Part 2: integrating jQuery DataTables with our Ajax call

This follows part 1.

My previous example of filtering a table was pretty basic. Let’s complicate things a little bit. While online examples are often simplistic, programming a real application isn’t.

Let’s integrate the common jQuery DataTables plugin to our table. Doing so will provide many functionalities but will break our last example.

Let’s see how and what we can do to fix it.

First install jQuery DataTables. You can get the NuGet package, download the files online and include them manually in your project or link to the CDN.

For the sake of simplicity I will link to the CDN in my _Layout.cshtml file.

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, 
          initial-scale=1.0">
    <title>@ViewBag.Title - ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

    <link rel="stylesheet" 
          type="text/css" 
          href="//cdn.datatables.net/1.10.12/css/jquery.dataTables.css">

And later in this same file…

    @Scripts.Render("~/bundles/jquery")
    <script type="text/javascript" 
            charset="utf8" 
            src="//cdn.datatables.net/1.10.12/js/jquery.dataTables.js">
    </script>
    @Scripts.Render("~/bundles/bootstrap")

Now let’s call the plugin on the page with our existing table by adding this at the end of Index.cshtml:

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#deparment-table').DataTable();
        });
    </script>    
}

And adding an id to our table in our DepartmentsTablePartial.cshtml partial view.

<table id="deparment-table" class="table table-striped">

As we can see we now have a full featured data table with filtering, sorting and paging.

datatables1

Sadly when we make our Ajax call to get all the data instead of a filtered subset we loose the DataTables functionalities.

datatables2

This is because jQuery DataTables isn’t aware of our Ajax update. We are rewriting the content inside the div but we aren’t making the plugin aware of this fact. Luckily there is a function available in the plugin API to handle that scenario.

We need to change our Ajax Helper BeginForm call to include an OnSuccess function:

@using (Ajax.BeginForm("FilterDepartmentsAjax",
               new AjaxOptions
               {
                   HttpMethod = "POST",
                   InsertionMode = InsertionMode.Replace,
                   UpdateTargetId = "department-table",
                   OnSuccess = "tableUpdated",
               }))

And our Scripts section will now contain a tableUpdated function.

<script type="text/javascript">
    $(document).ready(function () {
        $('#deparment-table').DataTable();
    });

    function tableUpdated() {
        $('#deparment-table').DataTable().draw();
    }
</script>    

It might be counter-intuitive that we aren’t using the Ajax functions of DataTables but this is because we our handling the Ajax call through our MVC controller. The MVC Ajax Helper is already updating the table and we just want DataTables to take notice of it.

DataTables ajax.reload() expect a JSON data source and we have none to give it. We simply want to data table to redraw itself.

If we start out site again everything is now working:

datatables3

As before you can find the code on GitHub here and the specific change set here.

Simple Ajax scenario in ASP.NET MVC: filtering a table

I will present a very simple example of how to use Ajax in ASP.NET MVC. In this example we will filter a grid by making an Ajax request. Here is what the result will look like:

table

I have started by creating a new ASP.NET MVC project in Visual Studio 2015.

newproject

The first step is to add the Microsoft.jQuery.Unobstrusive.Ajax package via NuGet:

nugetajax

Once this package has been added, modify your BundleConfig.cs file located in App_Start to add the jquery.unobtrusive-ajax.js file to the jQuery bundle (or another bundle of your choice).

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js",
            "~/Scripts/jquery.unobtrusive-ajax.js"));

This bundle is referenced by default in the _Layout.cshtml file in the default project created by Visual Studio. If you aren’t using this default project template, add the bundle to your layout or your page.

Next I create two model classes in the project:

namespace Ajax2.Models
{
    public class DepartmentModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
using System.Collections.Generic;

namespace Ajax2.Models
{
    public class DepartmentsModel
    {
        public bool Filtered { get; set; }
        public IEnumerable<DepartmentModel> Departments { get; set; }

        public DepartmentsModel()
        {
            Departments = new List<DepartmentModel>();
        }
    }
}

DepartmentModel is the model that will be used for each line of the table. DepartmentsModel will be our view model for our page. In this view model we have a collection of all our departments to be displayed in our table and whether we apply some filtering.

Create or modify a view (I modified the Index of the Home controller) likewise:

@model Ajax2.Models.DepartmentsModel

@{
    ViewBag.Title = "Home Page";
}

<br />

@using (Ajax.BeginForm("FilterDepartmentsAjax",
                   new AjaxOptions
                   {
                       HttpMethod = "POST",
                       InsertionMode = InsertionMode.Replace,
                       UpdateTargetId = "department-table"
                   }))
{
    <label>Filter: </label>
    @Html.CheckBoxFor(model => model.Filtered)
    <input type="submit" value="Submit" />
}

<div id="department-table">
    @{ Html.RenderPartial("DepartmentsTablePartial", 
                          Model); }    
</div>

The first thing to note is we are using the AjaxHelper. Alternatively we could write JavaScript code ourselves, the AjaxHelper is just a convenience. When choosing to write the JavaScript code ourselves, we must use jQuery which is what the helper is using.

The important bits is that we want to replace an element identified by the id department-table.

When we call this form, the contents returned by the Ajax action on the controller will replace the contents of the div. We will replace the div’s content with a partial view, so we might as well reference it right now.

Note that it is possible not to render the partial view before making the first Ajax call which can make sense in some scenarios.

The partial view is nothing special:

@model Ajax2.Models.DepartmentsModel

<table class="table table-striped">
    <thead>
        <tr>
            <th>Id</th>
            <th>Name</th>
        </tr>
    </thead>

    <tbody>
        @foreach (var department in Model.Departments)
            {
            <tr>
                <td>@department.Id</td>
                <td>@department.Name</td>
            </tr>
        }
    </tbody>
</table>

Note that it contains the same model as it’s parent view. This is convenient in our scenario, but a different model could be used instead.

Now for the controller:

using Ajax2.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Ajax2.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var model = new DepartmentsModel
            {
                Filtered = true,
                Departments = GetDepartments(filtered: true),
            };

            return View(model);
        }

        [HttpPost]
        public PartialViewResult FilterDepartmentsAjax(DepartmentsModel model)
        {
            var updatedModel = new DepartmentsModel
            {
                Filtered = model.Filtered,
                Departments = GetDepartments(filtered: model.Filtered),
            };

            return PartialView("DepartmentsTablePartial", 
                               updatedModel);
        }

        private IEnumerable<DepartmentModel> GetDepartments(bool filtered)
        {
            var departments = new List<DepartmentModel>
            {
                new DepartmentModel { Id = 1, 
                                      Name = "Sales" },
                new DepartmentModel { Id = 2, 
                                      Name = "Accounting" },
                new DepartmentModel { Id = 3, 
                                      Name = "Marketing" },
                new DepartmentModel { Id = 4, 
                                      Name = "HR" },
                new DepartmentModel { Id = 5, 
                                      Name = "IT" },
                new DepartmentModel { Id = 6, 
                                      Name = "Customer S." },
            };

            if (filtered)
                return departments.Where(x => x.Id < 4);

            return departments;
        }
    }
}

GetDepartments is meant to be a placeholder. Normally in a real application we would get the data from a data source or a service.

Our FilterDepartmentsAjax must be decorated with the [HttpPost] attribute, return a PartialViewResult and use a return statement that returns a return PartialView rather than a view.

A UI improvement

Rather than have a submit button, it would be better to have the table update when the user check’s the filter checkbox.

Normally we could have just called this.form.submit() on the checkbox likewise

@Html.CheckBoxFor(model => model.Filtered, 
                  new { onclick = "this.form.submit();" })

but sadly this doesn’t work with Ajax. Courtesy of this stackoverflow answer we can submit it likewise:

@Html.CheckBoxFor(model => model.Filtered, 
                  new { 
                     onclick = "$(this).parents('form:first')
                                       .find(':submit')[0]
                                       .click();" 
                  })

And then hiding the existing input element (which must remain on the page).

Conclusion

I have posted the code on GitHub and you can find it here.

Configuring HTTP Response caching in ASP.Net and ASP.Net MVC

Configuring HTTP Response caching in ASP.Net and ASP.Net MVC

Also known as simply HTTP caching or Response caching.

About HTTP Response Caching

When requesting a web page from a site, the browser interprets the HTML it receives from the server. It turn, it uses this new information to make more requests for other files; css, JavaScript, images, etc.

Here are the requests as shown by Firefox’s Developer Tools’ network tab when starting a sample ASP.Net / ASP.Net MVC application (it could be any website):

cache1

cache1a

Here we can see the additional files requested and served for displaying the page: Site.css, bootstrap.min.css, etc.

If we hit F5 to reload the same page once again, we get a slightly different result:

cache2

cache2a

This time we can see the same files being requested, with two differences. The HTTP return codes for the subsequent files are 304 instead of 200 (more on that later) and the total for all the 6 requests has gone from 0.11s to 0.07s.

The HTTP status code 200 is the OK status code. In this case it means the GET request was successful and that we have obtained the requested file. Since this was the first time accessing the site we had to download each file.

On the other hand, on the second page load we had HTTP status code 304 which is the NOT MODIFIED status code. When a browser requests some files it keeps them in it’s local cache. This cache is on the user’s machine and managed by the browser.

When receiving the original files the browser checks their http headers which contain amongst other things information about caching. When making a second request it sends (depending on the caching configuration in the headers) some information to validate if the files have been changed and needs to be downloaded again.

If the server can ascertain the files have not changed it returns a 304 result and the browser doesn’t download the file on the subsequent requests saving us some time.

If some of the files have changed, it will download them again. To know if a file has changed the browser will either send a last modification date or an ETag (a sort of fingerprint). We can test that by modifying one of our files, in my case Site.css, and reloading again.

cache3

Now we can see that the server was able to know that Site.css had changed and needed to be downloaded again by the client.

We can also test, by clearing our local cache, by doing a Ctrl-F5 or by starting a private browsing session that in the case where there is no cache anymore the files are downloaded again and that we get a 200 result like in the first image.

In the case where we used the local cache and got 304s, even tough the files haven’t been downloaded a second time we still had to make requests and wait for responses. We can do away with those requests altogether by configuring our http headers. This means that while getting a 304 is better than downloading the whole file, we can do even better by not making any requests at all.

Configuring your ASP.Net or ASP.Net MVC application for HTTP Response caching

What we’ve seen so far applies to all web servers and clients. Let’s see now how to configure our caching for an ASP.Net application running on IIS.

In an ASP.Net or ASP.Net MVC application it is possible to configure this through the web.config file. Here I will show you how to configure this for static files. Take note that these settings do not apply to Visual Studio’s built-in IIS server, only to applications deployed on an IIS instance. You may need to deploy your web application on IIS to test if you have been successful in applying your settings.

The following settings are for IIS 7. For IIS 6 you must use different configuration elements as mentioned here.

<system.webServer>
  <staticContent>
    <clientCache cacheControlCustom="public" 
                 cacheControlMode="UseMaxAge" 
                 cacheControlMaxAge="1.00:00:00" />
  </staticContent>
</system.webServer>

Here we have configured our static files so that the browser will use the cached version for 1 day. MaxAge indicates how long the cached files will be used. The other way to configure this is to use httpExpires which instead specifies a date up until which the cached content will be used.

After configuring this we can run our test again. But this time by navigating on another page that uses the same .css and .js files.

clientcache

As we can see the static content files haven’t been requested. The browser won’t request those files until the max age expires. In the Transferred column, instead of seeing some byte count we see cached which indicates the cached version has been used.

We need to navigate to another page to see those results because reloading the same page with the Network tab will always result in a 304 in the Firefox Developer Tools.

Back to our web.config file. We can also use the location element to target specific folders. This allows us to use various caching policies on different files.

<location path="Content">
  <system.webServer>
    <staticContent>
      <clientCache cacheControlCustom="public" 
                   cacheControlMode="UseMaxAge" 
                   cacheControlMaxAge="1.00:00:00" />
    </staticContent>
  </system.webServer>
</location>

Be careful if you set a MaxAge or httpExpires too far in the future or you might get some problems as you change those files and clients aren’t aware of the changes since they are not making any request.

One way to get around that is to change the name of the file. Say the client has cached Site_ASDF9494.css if you change to Site_BWDF9596.css the browser will treat this as a different file and download it again whatever the caching directives may be. It is possible to append a hash or some form of fingerprint to your files. Better yet, if you are using the bundling facilities in ASP.Net or ASP.Net MVC, a cache busting identifier is appended for you behind the scenes so you don’t have to do anything.

Details on cacheControlCustom

cacheControlCustom allows to specify some custom parameters of which public and private¬† are of particular importance. These indicate whether the files are to be treated as public (for example your site’s css file) or private (for example some file served to an authenticated user). The reason is that while we have only talked about browser caching up until now, caching can also happen at other levels, a CDN or a proxy for example. These will not cache private files but may cache public files.

By forgetting to specify this we may cause some sensitive information to be displayed to incorrect users. If some files contain content specific to a particular user and these files are cached on a proxy, they could be served to several different users who are behind this same proxy.

Make sure to be mindful of this one as the default value is public unless specified otherwise.

Conclusion

As modern sites can make a lot of requests, even when taking into account bundling and CDNs, it is important to be aware of how to reduce these requests to a maximum while still keeping a control of our files for future updates. Response caching is a good way of achieving this.