Scott Hanselman

Making a switchable Desktop and Mobile site with ASP.NET MVC 4 and jQuery Mobile

October 01, 2012 Comment on this post [27] Posted in ASP.NET | ASP.NET MVC | Javascript | Mobile
Sponsored By

I really enjoy working on and thinking about mobile websites. There's something about making an experience great on a pocket supercomputer phone that is so much more satisfying than a desktop. I actually got this blog looking nice on mobile devices back in 2006 when nobody was mobile except doctors and, well, all of us techies here on the blogs.

I've talked about the importance of a good mobile site before in posts like Create a great mobile experience for your website today. Please. However, some folks had asked me if I'd do a post on how to do a combination Desktop and Mobile site using ASP.NET MVC similar to the examples I used in my talks in Russia on mobile earlier this year. (There's video of those ASP.NET mobile presentations available)

When you start Visual Studio 2012 and go File | New ASP.NET MVC 4 app, there's an Internet Application template and a Mobile template. One gets you a standard desktop site - although with responsive design elements so it works on small screens - and the other gets you a jQuery Mobile application meant primarily for phones and tablets. Let's make one that switches between both.

We will do a small site in ASP.NET MVC for the Desktop, do some quick DB access, add jQuery Mobile and a View Switcher switch back and forth. I'll be using the Electric Mobile Studio from Electric Plum to simulate an iPhone. You can get a 7 day trial or you can get the Lite version of the Electric Plum Mobile Simulator with WebMatrix 2.

Quick CRUD Example

First, a model for DVDs.

public class DVD
{
public int ID { get; set; }
public string Title { get; set; }
public int Year { get; set; }
public Rating rating { get; set; }
}

public enum Rating
{
G, PG, PG13, R
}

Next, I scaffold out the Index, Create, Edit, Delete, etc. Unfortunately scaffolding doesn't do Enums (I'm sad) for my Movie Ratings so I add EditorFor() calls to my Create and Edits, and update my Index.

<div class="editor-label">
@Html.LabelFor(model => model.rating)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.rating)
@Html.ValidationMessageFor(model => model.rating)
</div>

Shared/EditorTemplates/Rating.cshtmlI could have used DropDownList I suppose, but I have always found that helper confusing. Instead, I'll create a Rating.cshtml that makes a dropdown. I could change this at some future point to be fancier and not even use a DropDown.

Aside: How do you guys usually handle Enums? I've seen it done a few ways. I'd like this to be more generic, but here's what I did for the Rating editor Template. Note the nullable ? as it has to work for both Create and Delete

@model MyApp.Models.Rating?
@using MyApp.Models

<select id="@ViewData.TemplateInfo.GetFullHtmlFieldId("")" name="@ViewData.TemplateInfo.GetFullHtmlFieldName("")">
@foreach (int rating in Enum.GetValues(typeof(Rating))) {
var name = Enum.GetName(typeof(Rating), rating);
<option value="@name" selected="@(Model.HasValue ? (int)Model == rating : false)">@name</option>
}
</select>

OK, so there's a basic Desktop CRUD app.

Editing a DVD List of DVDs

Making it Mobile

iPhone in the Visual Studio browser menuAs I mentioned, you've probably noticed when making an ASP.NET MVC application that you can choose a Mobile template with jQuery Mobile along with the standard responsive "desktop" Internet Application. However, there isn't a switchable template. That is, one that is the regular template on Desktops but switches to jQuery Mobile (or KendoUI, or whatever makes you happy) on mobile devices.

Using NuGet, install the jQuery.Mobile.MVC package. You can either right click on References, select Manage NuGet Packages, or you can use the Package Manager Console and type:

install-package jQuery.Mobile.MVC

This package will automatically bring in jQuery Mobile as well as:

  • A ViewSwitcher partial view and supporting Controller
    • This is what lets us switch manually between Desktop and Mobile
  • A basic _Layout.Mobile.cshtml and supporting stylesheet
  • A BundleMobileConfig.cs used with ASP.NET Optimization (the bundler for CSS and JS)

NOTE: There's nothing jQuery Mobile specific about the ViewSwitcher or the techniques here. You can happily change this package for any other popular mobile framework.

We start by adding the new Bundles in the Global.asax:

BundleConfig.RegisterBundles(BundleTable.Bundles);
BundleMobileConfig.RegisterBundles(BundleTable.Bundles); // <-- ADD THIS ONE

Since I installed the Electric Plum iPhone simulator, I'll select it from my browser dropdown in Visual Studio and run my app and navigate to /DVD.

The system sees that I'm on a mobile device and rather than using _Layout.cshtml it uses _Layout.mobile.cshtml.

This doesn't really look nice for a few reasons. First, I don't like the default style. Just go into _Layout.Mobile.cshtml and change the data-theme attribute to a value that makes you happy. I changed it from theme a to theme b.

Basic App in an iPhoneSame baisic app with a light background

Second, while I'm using the _Layout.Mobile.cshtml I'm still using the desktop Index.cshtml which looks lousy when displayed in the mobile layout. Remember that I only have an single Index.cshtml. If I had an Index.mobile.cshtml the system would use that page instead when rendering on a mobile device.

I can make an Index.Mobile.cshtml with simpler markup, change the table into an unordered list and add some jQuery Mobile specific attributes like this:

@model IEnumerable<MvcApplication2.Models.DVD>
@{
ViewBag.Title = "My DVDs";
}
<ul data-role="listview" data-filter="true" >
@foreach (var item in Model) {
<li>
<a href="@Url.Action("Details", new { item.ID })">
<h2>@Html.DisplayFor(modelItem => item.Title)</h2>
<p>@Html.DisplayFor(modelItem => item.Year) - @Html.DisplayFor(modelItem => item.rating)</p>
</a>
</li>
}
</ul>

Note the data-role and very basic elements like <ul>, <li>, <h2< and <p>. I also added the client side data-filter attribute to get some nice client-side searching. Hit refresh and now it's starting to look like a real mobile site.

See the ViewSwitcher at the top there? That's a partial view called _ViewSwitcher.cshtml.

A lovely jQuery Mobile example list of DVDs

By default it will let you switch back and forth between Desktop and Mobile but only on mobile devices. Why? Check out the first line of code:

@if (Request.Browser.IsMobileDevice && Request.HttpMethod == "GET")
{
<div class="view-switcher ui-bar-a">
@if (ViewContext.HttpContext.GetOverriddenBrowser().IsMobileDevice)
{
@: Displaying mobile view
@Html.ActionLink("Desktop view", "SwitchView", "ViewSwitcher", new { mobile = false, returnUrl = Request.Url.PathAndQuery }, new { rel = "external" })
}
else
{
@: Displaying desktop view
@Html.ActionLink("Mobile view", "SwitchView", "ViewSwitcher", new { mobile = true, returnUrl = Request.Url.PathAndQuery }, new { rel = "external" })
}
</div>
}

That first line checks if it's a mobile device making the request. Just surround that with /* comments */ and you'll be able to view and debug mobile layouts with your desktop browser without faking User Agents.

So far we've been talking about "mobile" and creating files with "mobile" in their file names and it's all been magic. Turns out we have lots of control over these things. Perhaps we want more than Index.mobile.cshtml but also, perhaps, Index.iPhone.cshtml and Index.WP7.cshtml.

Create lines like these in your Global.asax:

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("WP7") {
ContextCondition = ctx => ctx.GetOverriddenUserAgent().Contains("Windows Phone OS")
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone") {
ContextCondition = ctx => ctx.GetOverriddenUserAgent().Contains("iPhone")
});

You can affect the DisplayMode collection (of which "mobile" is one) by inserting in your own. Here I'm making two more specific mobile ones but you don't need to do mobile things to change DisplayModes.  A DisplayMode could be right to left for RTL languages, or "gold" and "bronze" for customers or whatever makes you happy, based on the ContextCondition you evaluate on each request.

There's lots of choices and you've got the flexibility to do things however you like. I'll show an example of a totally offline iPhone site using cache.manifest in a future post.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
October 01, 2012 19:28
Is there a reason that you are using a lower case of the Rating property? Even if the class name is Rating, you could have use Rating with a capital letter for the property.
October 01, 2012 19:35
Something everyone should be aware of when using MVC 4's mobile view support is that there's a bug in the RTM version that results in the wrong view being rendered for mobile devices after the view falls out of the resolution cache the first time. The tricky part is that you won't notice this in debug mode or until 15 minutes of inactivity in release mode, so it's incredibly easy to let the buggy behavior slip into production.

There's an easy fix on NuGet, which Rick Anderson covers here: http://blogs.msdn.com/b/rickandy/archive/2012/09/17/asp-net-mvc-4-mobile-caching-bug-fixed.aspx
October 01, 2012 19:37
Patrick - No reason, just goofed up.

Dave - Good point and thanks!
October 01, 2012 20:55
Yes... can't emphasize enough that the bug Dave mentions is a show-stopper. You'll need this package to fix the bug:

Microsoft ASP.NET MVC Fixed DisplayModes

jQuery Mobile has a lot of cool stuff, but I found it to be a little brittle when trying to get it to behave consistently. It also breaks if you have some other legitimate use of hash in your URL. It's a lot of CSS and script for what it does. I was originally going to use it for the mobile views in POP Forums, but eventually bailed on it and wrote something smaller that did just enough for my tastes (and looked slightly more metro instead of iOS).
October 01, 2012 23:12
Regarding handling Enum's, I ended up creating a series of extension methods for handling dropdowns in general. Some that handle any enumarable collection and some that dealt with Enums. Both intended to run in the controller and return a collection of SelectListItem, with the intent of being attached to the ViewModel. Most used func<> expressions to pass in expressions of what property to use as the value field or the text field. I keep making small tweaks as I go but I'd love some feedback on it.
Extention Methods for Enumerables and Enums in MVC (Github Gist)
October 02, 2012 4:36
For the enum can you not do something like

@Html.DropDownListFor(model => model.rating, new SelectList(Enum.GetValues(typeof(Rating)), model.rating))


from: http://coding-in.net/asp-net-mvc-3-method-extension/
October 02, 2012 17:18
Hi Scott,

It's not really related to this post, but do you know that your RSS feed (http://feeds.hanselman.com/ScottHanselman) writes LFLF to terminate HTTP headers and violates the HTTP protocol (it should put there CRLFCRLF)?
Reading it with HttpWebRequest results in a WebException being thrown.
October 02, 2012 17:36
Regarding Enum dropdowns, I created a simple extension method that returns a SelectList from an Enum that also uses DisplayAttribute for the dropdown text. So now I can populate my enum definitions with an attribute for each value for display purposes.

Here's the gist
October 02, 2012 17:56
Here's my extenstion method for displaying enum as dropdownlist

https://gist.github.com/90af11ad142b52b686ad/7e09c8a7d544a1134d635a3f88e30a2de6ff8dbb

Also you can split enum by capital letter.
October 02, 2012 19:35
Talking about MVC4, it would be nice to have an update to the ASP.NET MVC Application Upgrader ( http://aspnet.codeplex.com/releases/view/59008 ).

That upgrade recipe in the release notes ( http://www.asp.net/whitepapers/mvc4-release-notes#_Toc303253806 ) would be better encapsulated in a tool for us, lazy programmers :)
October 03, 2012 15:01
Regarding enums: I created some months ago some HTML helpers that generate DropDownlists for enums, with or without localization support:

http://ruijarimba.wordpress.com/2012/02/17/asp-net-mvc-creating-localized-dropdownlists-for-enums/

October 04, 2012 0:09
Great MVC article with jQuery Mobile on Microsoft Codeplex I designed a ASP.NET web forms version at
October 04, 2012 0:09
Great MVC article with jQuery Mobile on Microsoft Codeplex I designed a ASP.NET web forms version at http://jquerymobile.codeplex.com/
October 04, 2012 14:23
He men men Scottie, where is the source code !!!
October 05, 2012 0:13
Interesting to see what others did for their dropdowns, I thought I'd share a few reasons why I did mine the way that I did.

MVC already provides a dropdown extension method for creating dropdowns and I didn't see the point in creating a new when the one that is present works well enough (unless your going to create one for handling grouped dropdowns). So instead of an extention method(s) on the HtmlHelper, I decided that mine would would be extention method(s) for any collection or for Enums themselves, this makes them a more flexible.

Also I wanted to keep my Views using ViewModels exclusively and It didn't make sense to pass a collection of data objects to the view if only two fields are needed for a dropdown, and if you're going to reduce the objects sent to just the two fields why not use the class that MVC already provides, and that the view already provides HtmlHelper methods for, SelectListItem.

I also added an option into my extension methods to operationally add a SelectListItem at the beginning of the collection to act as the place holder, usually saying something like "Please pick one". This is useful in situations where you need to have it as a default on a Create Screen, but don't want to show it on the Edit screen. Also by having access to the resulting SelectListItems in the controller before they are sent to the view, I have the ability to remove ones that are not eligible to the user from security stand point. Maybe their current roles prohibit them from selecting a specific option, this way I can remove it from the collection before it's sent to the view.

I only really have two core extension methods in my class, (one for collections and one for Enums) the others are overloads or variations for things like passing in a strongly type Guid instead of having to have (Guid).ToString() all over since I use Guids mainly for primary keys. It looks like a lot but the Examples I have above each one make it easier to understand how they are each used. I also came across the need to split my Enums by Capital letters but ended up using an extension method I found in FubuCore's StringExtensions that I borrows for that :-)
October 18, 2012 19:53
Awful lot of magic going on here. How does it work out under the covers?

What is intercepting the request to determine to look for a view containing "mobile" in the file name?

How does the intercepting code tell the mobile view to use _Layout.mobile?

Inquiring minds ...
October 18, 2012 21:11
Aaron - Not much magic at all! You can check out how MVC picks views and how the ViewLocator works by looking at the source! It's just a piece of context that flows through the system and is applied during the hunt for the correct view.
January 30, 2013 19:26
Great article Scott, thanks for sharing!
February 07, 2013 14:55
Interessante artikel, een mobiele website is een goede aanvulling op je huidige website.
February 11, 2013 21:09
Hi Scott

I find that Jquery Mobile to be wanting. I tried to use for mobile.joemele.info only to stop finding it wont let me do what I want.

Jquery can make a greate small phone site not something suitable for a tablet.

I like your posts in general but it would be great to use jquery or sencha. I see what people have done with those frameworks and it blows away jquery mobile.
February 13, 2013 15:27
Hi Scott,

Is it possible to delete cookie (ASPXBrowserOverride) when we close the browser (session cookie kind of)?

Or can we mention a time to expiry?
February 13, 2013 17:02
Hi scott,
thanks for the post, I have a mvc4 web application developed with vs2010. in my 'BundleConfig' I have include to jquery, when I run the app It all fine. after installing the package from nuget following your post I can switch to device mode but firebug shows that no request for jquery file with web mode.
the file is there , and the bundle is the same as before ... all other scripts are downloading from scripts folder accept for jquery.
do you have any Idea ?


p.s - the email validation not working when I try to post my comment here
April 06, 2013 4:46
Dear Scot,
If i want to make same in WebFroms what effort will be required.
If you have any source code please let me know.


Regards,
-N
May 26, 2013 17:03
hi, i tried this but i can see my other pages, i can only see my home page. how do i fix that?
May 30, 2013 23:38
Hallo Scott, what is your opinion on following this type of approach compared to and in context to RWD. Also is this type of approach only possible with above technology or freely usable within other languages. My thinking is both has there place, business apps for example might not want to look or expose similar content to the mobile client.
June 25, 2013 0:00
Hi Scott,

This is almost completely unrelated, however, I was wondering if you see this as a viable solution to handling views based on user roles. The problem I have is creating tons of conditional code in my HTML to display content based on role. Right now, I haven't come up with a solution that I'm completely happy with but it looks like this could be cleaner solution.

Thoughts?

August 21, 2013 19:47
Can you please share the source code?

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.