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".
Taking a look at our solution tree, we can see that the modernizr-1.7
scripts have been included for us.
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.
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.
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!
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.
If we "Apply" that change, then refresh our page, Modernizr will report back to us that localstorage
is not available.
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 tag in place for now since we will be adding some jQuery in a bit.
@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.
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.
- Show Help
- Hide Help
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.
- Show Help
- Hide Help
If we F5 at this point we will see our links with help text by default.
And if we click on "Hide Help" we can see that the help text gets 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.
- Show Help
- Hide Help
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.
- Show Help
- Hide Help
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.
No new comments are allowed on this post.
Discussion
Terrence
Great article, very well written. I will be using this in my next project.
Justin Schwartzenberger
Paul
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()); };
} ();
thanigainathan
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
Thanks for this great article. Very well written and managed.
Justin Schwartzenberger
ramin
Great article. Thanks for sharing it with us
thiet ke website
Thank your post!
jc
A well written and presented article, thank you very much
I learned so much from reading and following this artcle. Thanks mate, you learned me a lot.