Downloads
144827_code.zip

The web developer community has long supported mobile website development. Developers have used a variety of techniques, some client-based and some server-based, to accommodate the widest range of browser clients. In this article, I'll discuss a feature in ASP.NET MVC 4 called display modes that gives ASP.NET web developers more help in supporting a variety of browsers in their mobile web applications. I will explain how using display modes helps with mobile browser detection in web apps, then demonstrate how to use display modes in a sample ASP.NET MVC 4 web application. But first, we'll look briefly at some options that have been available to web developers for dealing with mobile websites.

Handling Mobile Websites

One technique, specific to the client, has been to create a responsive web design, using Cascading Style Sheets (CSS) to expand or contract the website depending on the available viewport. Sites like this have rolled their own CSS or used a framework such as Twitter Bootstrap or the 960 Grid System. Another technique is to create a flexible design with percentage-based widths, so that the page naturally expands or contracts as the viewport changes. More options exist, and all these options are valid client-side options for handling multiple-sized viewports.

We are not limited to solutions on the client to solve the problem, however. When it comes to server-side technologies, mobile development has always been an option in Web Forms and ASP.NET MVC. For instance, Scott Hanselman previously published a custom view engine for ASP.NET MVC 3 that provides mobile support. The view engine detects information about the browser making the request, and if it determines that the browser is a mobile browser, the view engine serves up a mobile view to the client.

The WURFL and 51Degrees.mobi frameworks also support Web Forms and MVC; each of these products can detect a mobile device and redirect to another page or perform another action. Common to the .NET Framework is the ability to detect a mobile browser using the HttpRequest.Browser.IsMobileDevice property. However, solutions like WURFL and 51Degrees.mobi use a custom database with device configurations that provide more advanced capabilities than the .NET Framework.

With ASP.NET MVC 4, the .NET Framework solves some of these problems through the display modes feature but goes further in its solution. The display modes feature provides an alternative view mechanism that is displayed to the user when a certain condition is met. Display modes are not restricted to mobile views, though that is the feature's primary use. A display mode uses a lambda expression, matching a condition on the HttpContextBase class. If the condition matches, the specific view to the display mode is served up from the view engine to the user. Note that we are not talking about separate controllers, just the views themselves.

Creating a Display Mode

As I mentioned, the most common scenario is a mobile view, whereby the display mode detects a mobile browser and serves up a mobile view. However, limiting use of a display mode to only serving up mobile views does a disservice to the feature. Display modes can serve a variety of purposes. A display mode could be a mode that checks the current user's authentication status and serves up an unauthenticated view to the anonymous user. A display mode could check a single type of device (such as an iPad).

From a more technical perspective, a display mode in ASP.NET MVC is represented by the IDisplayMode interface and subsequently the DefaultDisplayMode class. The MVC framework uses the DefaultDisplayMode class to represent both desktop and mobile browsers.

It might seem odd to use the same class to represent both types of browsers. However, the display mode uses a public ContextCondition property with a getter and setter, taking a lambda expression that specifies a matching condition. This lambda expression accepts a reference to HttpContextBase, returning a Boolean; this means that whatever condition you want to match on the current request is pretty easily achievable. By default, the mobile definition checks the HttpContextBase.GetOverriddenBrowser().IsMobileDevice property to see whether the device is a mobile device. GetOverriddenBrowser() is a new extension method on HttpContextBase for getting an instance of the browser.

There are two ways to create a custom display mode. The first way is to instantiate the DefaultDisplayMode class and set the ContextCondition property, as explained previously. The second way is to create a class that inherits from the DefaultDisplayMode class and set the ContextCondition property in the constructor. I chose the latter option, as shown in Listing 1.

Listing 1: Opera Mobile Emulator Display Mode

public class OperaMobiDisplayMode : DefaultDisplayMode                              {                              	public OperaMobiDisplayMode()                              		: base("Mobile")                              	{                              		ContextCondition = (context => IsMobile(context.GetOverriddenUserAgent()));                              	}                              	public static bool IsMobile(string useragentString)                              	{                              		return useragentString.IndexOf("Opera Mobi",                                                                               StringComparison.InvariantCultureIgnoreCase) >= 0;                              	}                              }                              


One shortcoming of the .NET Framework's support for checking a mobile device is that .NET does not support Opera Mobile Emulator. To support Opera Mobile Emulator for testing purposes, the display mode in Listing 1 loads a mobile view when ASP.NET MVC detects a user agent string containing "Opera Mobi". Display modes always have a fallback; this means that if a view does not match the naming convention set up by the display mode, the default display mode is used. Using this design, we can easily support and isolate displays for all kinds of devices, simply by checking the user agent or any other HTTP request parameter.

Now that we have the display mode, we can register it in the Application_Start event handler, as in Listing 2. The display mode is inserted into the DisplayModeProvider.Instance.Modes collection. Once the display mode is in the collection, any request by an Opera Mobile Emulator serves up a mobile version, as defined by the string sent to the base constructor.

Listing 2: Registering Display Modes

protected void Application_Start()                              {                              	DisplayModeProvider.Instance.Modes.Insert(0, new UnauthenticatedDisplayMode());                              	DisplayModeProvider.Instance.Modes.Insert(0, new OperaMobiDisplayMode())                              }                              


Display modes are executed in order, so it's important that you insert them in the correct order. Note there are two already defined in the Modes collection.

Let's look at another example, this time thinking out of the box. For instance, maybe you want to display a view for a user who is not authenticated on the website. A display mode can interrogate the current principal for its authentication status and display a separate view just for unauthenticated users. Take a look at the code sample in Listing 3; since the display mode is based upon HttpContextBase (the type of the sole parameter), we can interrogate the current principal for its authentication status.

Listing 3: Display Mode for Displaying a Separate View for Unauthenticated Users

public class UnauthenticatedDisplayMode : DefaultDisplayMode {                              public UnauthenticatedDisplayMode()                              		: base("Unauthenticated")                               	{                              		this.ContextCondition = ctx => ctx.User == null || ctx.User.Identity.IsAuthenticated == false;                              	}                              }                              


In the base constructor, note that we pass "Unauthenticated", which needs to be part of the name of the view (as in Home.Unauthenticated.cshtml).

Note that using a display mode in this way could lead to collisions; it becomes more difficult to display an unauthenticated mobile view, as two display modes cannot coexist. Furthermore, creating hybrid display modes can easily lead to organized chaos. Plan the use of this feature carefully.

Setting Up an Alternative View

An alternative view has the same name as an action method, but with an additional suffix. Let's set up an alternative view for the Index action method in the Home controller. With an already existing Index.cshtml in place, we can add a second view using the Add View screen in Visual Studio, shown in Figure 1.

144827_fig1_create_alternative_view-sm
Figure 1: Creating an Alternative View

To begin, right-click the view folder and create a new view. In the Add View dialog box, enter the name view.Mobile, and specify the appropriate model.

Now we have two views, Index.cshtml and Index.Mobile.cshtml, both linked to the Index action. You might wonder how mobile views work with layout pages. After all, a mobile view could refer to a separate mobile layout file. This is in fact how it can be done: An application can have _layout.cshtml as the root layout page, while using _layout.Mobile.cshtml as the mobile layout. This allows the application to point to the same layout page definition but use differing layouts, depending on the current display mode.

For setting up the markup of a mobile view, Microsoft includes jQuery Mobile, jQuery's mobile framework designed to provide a native experience on a mobile device. jQuery Mobile is easy to set up and straightforward to implement (you can find an example of using jQuery Mobile in my article "Get Started Using jQuery Mobile for Mobile Web Development"). However, you are not required to use jQuery Mobile. I chose not to use it, instead opting for a minimalist design. Going with a minimalist design ensures that the page size stays small and works on the largest variety of browsers available.

Testing Display Modes

The task of testing display modes can be complicated if you don't have a variety of devices to test on and accessible web space to host your application. Following are some utilities that can help you in this regard:

There are also various third-party utilities that are helpful with mobile testing -- for example, BrowserStack or Keynote MITE. Some available utilities emulate a browser within an existing browser session. Using these emulators gives you a better idea of what's going on in your application, although the emulator results are not a guarantee of how your website will look on the actual device.

Sample Application

Let's look at a sample application using customer-related data from the SQL Server AdventureWorks sample database. The application uses two views, one for desktop and one for mobile. For desktop browsers, the application displays a table structure that shows all the customer information in a single row. The table is one of the more common representations of user data, as it provides an efficient means for viewing data. Take a look at Listing 4.

Listing 4: Displaying a List of Customers in a Table

<table>                              	<thead>                              		<tr>                              			<th>ID</th>                              			<th>Account</th>                              			<th>Type</th>                              			<th>Sales Territory</th>                              			<th>Date</th>                              		</tr>                              	</thead>                              	<tbody>                              @foreach (var customer in customers)                              {                              		<tr>                              			<td>@customer.CustomerID</td>                              			<td>@customer.AccountNumber</td>                              			<td>@(customer.CustomerType == "S" ? "Store" : "Internet")</td>                              			<td>                              				@customer.SalesTerritory.Name                              			</td>                              			<td>                              				@customer.ModifiedDate.ToShortDateString()                              			</td>                              			<td>                              				@Html.ActionLink("View Sales", "View", "Sales", new { c = customer.CustomerID }, null)                              			</td>                              		</tr>                              }                              	</tbody>                              </table>                              


This table works great for desktop browsers, but it's too wide to display on a 480px screen. To give users a better mobile experience, the table is transformed into a vertical structure, using a series of div elements. Using a vertical flow ensures no vertical scrolling, fits the page easily within a 480px width, and can expand when the device rotates to landscape. Take a look at the alternative in Listing 5.

Listing 5: Customers View in Mobile Format

@foreach (var customer in customers)                              {                              	<div>                              		<div>                              			@customer.AccountNumber (ID# @customer.CustomerID)                              		</div>                              		<div>                              			Type: @(customer.CustomerType == "S" ? "Store" : "Internet")                              		</div>                              		<div>                              			Territory: @customer.SalesTerritory.Name                              		</div>                              		<div>                              			@customer.ModifiedDate.ToShortDateString()                              		</div>                              		<div>                              			@Html.ActionLink("View Sales", "View", "Sales", new { c = customer.CustomerID }, null)                              		</div>                              	</div>                              	.                              	.                              }                              


The default view renders using a table, as shown in Figure 2.

144827_fig2_render_view_desktop_browser-sm
Figure 2: Rendering the View for a Desktop Browser

For a mobile browser, the view transforms vertically using div elements, as in Figure 3.

144827_fig3_render_view_mobile_browser-sm
Figure 3: Rendering the View for a Mobile Browser (Opera Mobile Emulator)

Be aware that JavaScript's varying support for accommodating different types of devices will make debugging mobile web apps more difficult. Modern desktop browsers provide a lot of convenient information that informs developers of everything going on within the lifecycle of a page. jQuery is usually a safe bet for mobile web apps because of the amount of effort its developers spent on achieving cross-browser compatibility. An alternative strategy could be to use graceful degradation, whereby the view is set up as a server-side solution that jQuery "takes over," handling certain UI features on the client side.

I've chosen to use jQuery for the following example, which requests an action method and returns a partial view through a $.ajax call. The contents are returned as a string and injected into the UI. Listing 6 renders a table; the individual details of each sale load in the next row, dynamically populated upon the user's click of a link.

Listing 6: Loading Details Data Using Ajax

@foreach (var sale in sales)                              {                              <tr>                              	.                              	.                              	<td>                              		<a href="javascript:loadDetails(@sale.SalesOrderID)">Details</a>                              	</td>                              </tr>                              <tr id="Details@(sale.SalesOrderID)" style="display:none;">                              	<td colspan="8">                              	</td>                              </tr>                              }                              <script type="text/javascript">                              function loadDetails(header) {                              	var tr = $("#Details" + header.toString());                              	if (tr.is(":visible")) {                              		tr.hide("fast");                              	}                              	else {                              		tr.children(":first").html("Loading...");                              		tr.show("fast");                              		$.ajax({                              			type: "post",                              			url: '@Url.Action("Details", "Sales")',                              			data: { header: header },                              			success: function (d) {                              				tr.find("td").html(d);                              			},                              			error: function (ex) {  … }                              		});                              	}                              }                              </script>                              


Each details row has a unique ID, using the primary key as a unique-name generator. When the Details link is clicked, the details view is toggled. When the details view is displayed, the loadDetails method makes an Ajax request to load sales data and injects in the HTML response. The view could have rendered the additional markup on the server; however, doing that would increase the size of the page and could slow its rendering. It's more efficient to use Ajax to populate this extraneous content, which the user might not even view.

Points to Consider When Using Multiple Views

Let's look at what we just created. The application serves two views from a single controller's action method. This means we have two views with nearly identical code, now duplicated. The most challenging aspect of code duplication is the need to keep markup in sync while making the appropriate mobile view adjustments. The more display modes you have, the more maintenance required, which must be factored into any solution.

There are several ways to work around this challenge. A global helper can store snippets of markup in a centralized place, but this method does not always solve the duplicate-rendering problem. Partial views provide the same benefit and have the same drawbacks. The Html.Action method can have a different set of drawbacks, with not as many solutions. In some scenarios, I relied on using custom HTML helpers, components that more easily masked the complications of multiple views. The real complexity we developers face is that different data structures are needed for different viewports, making it harder to define a common user interface.

An interesting point to note from Listing 4 and Listing 5 is that both views implement the same rendering technique. A view can be built in a variety of ways: on the server, on the client loaded from an Ajax request to the server that returns UI markup, or via JSON and client-side binding through a framework like Knockout. With the first approach, the view renders on the server using a server-side model instance that has data. With the latter two options, the initial view renders without a server-side model.

It's important for both views to use the same rendering approach -- otherwise, the architecture becomes radically more difficult. For instance, if you wanted to use a client-side templating approach using Knockout on a desktop browser, but a server-side approach for mobile devices, the challenges of handling the two different responses can be handled, but doing so is much more difficult. It would require the action method to know the rendering technique used, which creates a tight coupling. For instance, the action method in Listing 7 is built with the mindset of a server-side rendering approach.

Listing 7: Action Setup for Server-Side Rendering

public ActionResult All(int page = 0)                              {                              	const int count = 20;                              	ViewBag.Page = page;                              	var ctx = new AdventureWorksEntities();                              	ViewBag.Sales = ctx.SalesOrderHeaders.OrderByDescending(i => i.ModifiedDate)                                                        .Skip(page * count).Take(count).ToList();                              	return base.View();                              }                              

If we were, alternatively, using a client-side rendering approach, we would not be loading the data at the time of rendering the view. Instead, a separate action method would return JSON data to the caller. With this approach, we find ourselves customizing the action methods to the response when the desktop view uses client-side rendering and the mobile view uses server-side rendering.

Each device has different hardware capabilities; not all devices have quad-core processors like the Google Nexus 7. Slower devices might render markup more slowly, especially for web applications that use a lot of JavaScript. There are various reasons for this: As JavaScript modifies the Document Object Model (DOM), the browser triggers a repainting of the page. More repaintings or reflows of the page mean the browser spends more time rendering the UI.

Finally, when desktop browsers were the predominant browsers in use, developers could get away with supporting only three to five browsers. Now developers have to factor in many more browsers and varying levels of support for browsers depending on the device's OS version.

The Power of Display Modes

As you've seen, ASP.NET MVC 4's display modes feature is a powerful tool for mobile development -- but the display modes feature is not limited to mobile web apps. You can use display modes to display any alternative view, which is tied to an existing controller. Defining a custom DefaultDisplayMode implementation enables an alternative display mode to support mobile devices, a specific device (like an iPad), unauthenticated users, or whatever other conditions you can think of. Display modes in MVC work by allowing you to define a second view (or additional views) with the same name but a different suffix matching what the display mode expects. A display mode is registered with the DisplayModeProvider instance.

Desktop browser applications tend to lay out the user interface horizontally, whereas for mobile devices with smaller viewports (especially when limited in portrait mode at 480px), you'll want to render content more vertically. Doing so renders the page on the client with minimal horizontal scroll. All view implementations can render using data on the server or client, but it's important to pick a consistent approach when creating the user interface. Using a different approach per display mode can work, but implementation is significantly more difficult.

Brian Mains is a Microsoft MVP and consultant with Computer Aid, where he works with nonprofit and state government organizations.