Learn how to use Modernizr from the ASP.NET MVC3 Tools Update to store user data via HTML5 localStorage

Posted on April 18th, 2011

Mix11 brought about the release of the ASP.NET MVC3 Tools Update (Phil Haack has a blog post describing how to get up and running with the update) which added some awesome features like scaffolding for CRUD operations via the controller dialog tool, Entity Framework 4.1, and HTML 5 versions of project templates. The tool update also officially introduced Modernizr 1.7 into the MVC3 project template.

Modernizr is a small and simple JavaScript library that helps you take advantage of emerging web technologies (CSS3, HTML 5) while still maintaining a fine level of control over older browsers that may not yet support these new technologies.
www.modernizr.com

There are a ton of exciting features in the HTML 5 spec that make it appealing to web developers. One of those features is localStorage. The localStorage provides a way to persist data on the client side without using cookies. Lets take a look at how we can leverage Modernizr to use localStorage from an MVC3 application like el-motor.cz did, if it is available via the user agent, and if not then fall back to good old cookie storage.

Using the new MVC3 Tools Update we can create an Empty MVC3 project targeting the Razor engine and selecting the option to "Use HTML5 semantic markup".

New MVC3 Project dialog

Taking a look at our solution tree, we can see that the modernizr-1.7 scripts have been included for us.

Solution tree

Opening up the Views/Shared/_Layout.cshtml file, we can see that it has been pre-populated with some HTML5 semantic markup, including a script tag to include the modernizr code.




    
    @ViewBag.Title
    
    
    



    @RenderBody()


Since we created an empty project we don't actually have a controller with an action that will get rendered if we launch the site. We will create a standard HomeController with an Index action and associated Index.cshtml view to get us started.

Home controller in solution tree

The code for our HomeController.cs file will look like so:

using System.Web.Mvc;

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

The code for our Index.cshtml view will look like so:

@{
}

The empty project template did include a default Views/_ViewStart.cshtml file that references the _Layout.cshtml file. We can have our Index.cshtml view file above be empty and know that our Views/Shared/_Layout.cshtml file will get used (to learn more about how this works you can read my post on Single Layout for Areas with ASPX and Razor views (Part 2)). Having these in place will allow us to launch our site and get some content displayed so we can start playing around with Modernizr. If we RTFM (or documentation) for Modernizr we find out that we can check for the availability of localStorage by testing the javascript property Modernizr.localstorage. We can add a bit of jQuery to our _Layout.cshtml file to do a dirty test:




    
    @ViewBag.Title
    
    
    



    @RenderBody()
    

Doing an F5, we can see that Modernizr is detecting support for localStorage in IE9.

IE9 with localstorage support

Now lets test our code out in a user agent that doesn't support localstorage. We know that IE7 doesn't have support for it. If we hit F12 while we have our IE9 window open we can bring up the developer tools and set the "Browser Mode" to IE7 to test. Changing the "Browser Mode" in the developer tools will automatically initiate a page reload, but you can do a refresh if you like just to be sure. Surprisingly, Modernizr is reporting that localstorage is supported in IE7!

IE9 in IE7 mode

It turns out that IE8 and IE9 running in IE7 mode will actually support localstorage. From a user standpoint, this is kind of a cool thing. From a "developer tools" standpoint, this falls into the "gotcha" bag. Fortunately, we can open up the "Internet Options" for IE, go to the "Advanced" tab, and uncheck "Enable DOM storage" to turn off localstorage and thus test our code.

Internet Options dialog

If we "Apply" that change, then refresh our page, Modernizr will report back to us that localstorage is not available.

IE9 with localstorage disabled

Now that we know how to check for localstorage with Modernizr and how to set up our development environment to turn on and off localstorage in our user agent, we can start looking at how we can implement a solution that uses localstorage when available and falls back to cookies when not. Lets imagine that we are tasked with building a UX that allows users to toggle on/off help text that goes along with a list of links so that repeat users who are familiar with the links can disable the explanations that go along with them. We can use localstorage to persist the user's setting in user agents that support it, and use cookies when the user agent does not.

We need a list of links and associated help text. Those can be added to the html in our _Layout.cshtml file. We don't want our dirty test code showing up in our production application, so we will remove that and replace it with our links code. We will leave our @RenderBody()

  • Add a Widget

    This tool allows you to add a new company widget to our inventory.

  • View all Widgets

    See a list of all of our company widgets in our inventory.

We have given our

tags a CSS class named LinkHelpText. We can target these tags using jQuery to toggle on and off the display of them. In order to change our UX based on the user settings we are going to need to check a settings value and then toggle our display. We can write a javascript function that uses jQuery to do so.

function ToggleLinkHelpText(isVisible) {
    $('.LinkHelpText').toggle(isVisible);
}

Now we can add some html links right below our

    list of tool links that the user can click to show or hide the help text.

    
    

    And then add some jQuery to wire up click events that call our ToggleLinkHelpText function so we can see the functionality in action.

    $(function () {
        $('#ShowHelpLink').click(function () { ToggleLinkHelpText(true); });
        $('#HideHelpLink').click(function () { ToggleLinkHelpText(false); });
    });
    

    To top this step off, lets add a little bit of CSS in the tag to give our show and hide text that click-able feel.

    
    

    Our _Layout.cshtml file should now look like so:

    
    
    
        
        @ViewBag.Title
        
        
        
    
    
    
    
        @RenderBody()
        
    • Add a Widget

      This tool allows you to add a new company widget to our inventory.

    • View all Widgets

      See a list of all of our company widgets in our inventory.

    If we F5 at this point we will see our links with help text by default.

    Tool links with help text

    And if we click on "Hide Help" we can see that the help text gets hidden.

    Tool links with help text hidden

    We can click on "Show Help" to get the help text back. Our UX logic is functioning well so far. Now we need to persist the user setting. A javascript function named SetLinkHelpVisibility will handle the logic for storing our user setting and a javascript function named LinkHelpIsVisible will handle the logic to tell us if our user setting is configured to display the help or not. We will put a call to ToggleLinkHelpText in the SetLinkHelpVisibility function and refactor our click events for "Show Help" and "Hide Help" to call the new SetLinkHelpVisibility function. The logic for these two functions:

    function SetLinkHelpVisibility(isVisible) {
        if (Modernizr.localstorage) {
            localStorage['showHelp'] = isVisible;
            ToggleLinkHelpText(isVisible);
        }
        else {
            // Handle cookie storage here.
        }
    }
    
    function LinkHelpIsVisible() {
        if (Modernizr.localstorage) {
            if (localStorage['showHelp'] == 'false') {
                return false;
            }
            return true;
        }
        else {
            // Handle cookie retrieval here.
        }
    }
    

    The update to our click events:

    $('#ShowHelpLink').click(function () { SetLinkHelpVisibility(true); });
    $('#HideHelpLink').click(function () { SetLinkHelpVisibility(false); });
    

    This will get our user setting storage working for localStorage. The localStorage variable can be accessed like an associative array. The key that we use here is "showHelp" and we set the value to either true or false in our SetLinkHelpVisibility function. To check the value in our LinkHelpIsVisible function we test the value of the array position by key. If the key doesn't exist then the value returned will be null (no exception thrown). If the key exists then the value will be returned as a string. We could cast that to a bool and then do a check, but here we will just check the string for "false". The reason we do a check for false here and not true is to handle the case in which the user's localStorage has not been set (the test fails because of a null value). This way the help text will be visible by default because we will return true if the localStorage key is not set or if the value is not set to "false".

    With these pieces in place our _Layout.cshtml file should now look like so:

    
    
    
        
        @ViewBag.Title
        
        
        
    
    
    
    
        @RenderBody()
        
    • Add a Widget

      This tool allows you to add a new company widget to our inventory.

    • View all Widgets

      See a list of all of our company widgets in our inventory.

    Launching the site again (F5), we can test out our logic (if you are creating your own project while reading this, make sure you have your "Enable DOM storage" checked in your Internet Settings for this to work at this stage). On first launch, our help text is visible. If we click on "Hide Help" it is hidden and our localStorage flag is set. From here we can refresh the page and see that our setting persists. We can hit F12 to bring up our developer tools and change the Browser Mode to IE7, IE8 or IE9 Comparability View and our user setting still persists. We can even close our browser, end our debug session, and kill our running Visual Studio Development Server instance in the task tray (assuming you are using that and not IIS), then do another F5 and see that our setting has persisted.

    The sweet thing about localStorage is that we handled all of that logic with a little bit of javascript. Now we need to look at the cookie side of things. They could be handled with javascript as well, but it would either involve adding a JQuery extension or writing a chunky amount of javascript code. Lets take a look at how we could do the set cookie portion via an MVC3 action method and the get cookie value via some inline Razor syntax code. We need to add an action method to our HomeController class named SetLinkHelpVisibility that will take in a bool value, set a user cookie to store the value, and return null. Our update to our HomeController.cs file:

    using System.Web;
    using System.Web.Mvc;
    
    namespace Website.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
    
            public ActionResult SetLinkHelpVisibility(bool isVisible)
            {
                var cookie = new HttpCookie("showHelp", isVisible.ToString());
                cookie.Expires = DateTime.Now.AddYears(1);
                Response.Cookies.Add(cookie);
                return null;
            }
        }
    }
    

    We can call this action method from our javascript function SetLinkHelpVisibility using the jQuery .post() function, and upon the async completion we can call our javascript function ToggleLinkHelpText to update our UX.

    function SetLinkHelpVisibility(isVisible) {
        if (Modernizr.localstorage) {
            localStorage['showHelp'] = isVisible;
            ToggleLinkHelpText(isVisible);
        }
        else {
            $.post('/Home/SetLinkHelpVisibility', 
                { isVisible: isVisible },
                function () { ToggleLinkHelpText(isVisible); }
            );
        }
    }
    

    For our get cookie implementation we can add some C# code using the Razor syntax directly into our javascript function LinkHelpIsVisible.

    function LinkHelpIsVisible() {
        if (Modernizr.localstorage) {
            if (localStorage['showHelp'] == 'false') {
                return false;
            }
            return true;
        }
        else {
            @{
                var isVisible = true;
                if (Request.Cookies["showHelp"] != null 
                    && Request.Cookies["showHelp"].Value == "False")
                {
                    isVisible = false;
                }
            }
            return !('@isVisible' == 'False');
        }
    }
    

    Finally, we have our completed _Layout.cshtml file content:

    
    
    
        
        @ViewBag.Title
        
        
        
    
    
    
    
        @RenderBody()
        
    • Add a Widget

      This tool allows you to add a new company widget to our inventory.

    • View all Widgets

      See a list of all of our company widgets in our inventory.

    If we F5 with this in place and uncheck "Enable DOM Storage" in our "Internet Settings" we can test out our cookie storage. The developer tools also provide a way to inspect the current cookie data by selecting "View Cookie Information" under the "Cache" menu item. Doing the same usability tests that we did for the localStorage implementation, we can see that the cookie implementation functions the same from the user perspective.

    Just a little something to get you started using Modernizr and localStorage in ASP.NET MVC3. I leave it up to you to take it from here! If you are ready to dig deeper into localStorage you can learn more on the following sites:
    WC3 Web Storage draft

    Couple of things to note:

    • The localStorage does not expire like a cookie. If you need the content of it to reset or clear out you have to program that into your implementation.
    • Our set cookie implementation: because we are using JQuery to do an async call to our controller action there is a chance (albeit very slim) that the user could click to hide and then click to nav to a tool before the controller action is completed, resulting in the tool page not accessing the cookie value because it hasn't been set yet.

Discussion

Terrence
Terrence
19 Apr, 2011 06:54 PM

Great article, very well written. I will be using this in my next project.

19 Apr, 2011 07:42 PM

Terrence
Thanks for the feedback!

Paul
Paul
25 Apr, 2011 04:42 PM

Nice article, but it would have been much easier to handle the cookie with some simple JS. Just something simple like this:

var cookieJar = function () { var retObj = {}; retObj.setCookie = function (cname, value, expiredays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + expiredays); document.cookie = "path=/" + cname + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toUTCString()); };

retObj.getCookie = function (c_name) {
    if (document.cookie.length > 0) {
        c_start = document.cookie.indexOf(c_name + "=");
        if (c_start != -1) {
            c_start = c_start + c_name.length + 1;
            c_end = document.cookie.indexOf(";", c_start);
            if (c_end == -1) c_end = document.cookie.length;
            return unescape(document.cookie.substring(c_start, c_end));
        }
    }
    return "";
};
return retObj;

} ();

27 Apr, 2011 04:21 AM

Hi,

Can you please let me know the size limit of localstorage ? Say I am serializing a very object and storing that in localstorage will there be any problems because of this on performance side ?

Thanks, Thani

Abdullah
Abdullah
27 Apr, 2011 05:59 AM

Thanks for this great article. Very well written and managed.

27 Apr, 2011 03:50 PM

Thani
The specifications recommend an arbitrary limit of five megabytes (5 MB) per origin. Although ultimately this is up to each user agent (browser) to decide what they will provide and if they will provide a way for an end user to request more if needed.

28 Apr, 2011 07:09 AM

Great article. Thanks for sharing it with us

16 May, 2011 04:40 AM

Thank your post!

jc
jc
15 Oct, 2011 02:01 PM

A well written and presented article, thank you very much

طراحی سایت
06 Apr, 2012 01:02 PM

I learned so much from reading and following this artcle. Thanks mate, you learned me a lot.

No new comments are allowed on this post.