This post is over 6 months old. Some details, especially technical, may have changed.

Mobilize! Adapting the ASP.NET MVC Project Template for jQuery Mobile

Image courtesy of the awesome Threadless

Having tinkered with jQuery Mobile and MVC a bit recently but I wanted to do a bit more.  The first app I wrote was read only which is actually pretty easy to accomplish regardless of technology/framework.  So I wanted to try a little something different, something a kin to a real application.  However time and coders block left me with little inspiration for creating the worlds next amazing mobile app.  Sitting there with a new ASP.NET MVC project open in Visual Studio I couldn't think of what to write.  Then it struck me... don't write anything.  The MVC starter skeleton project was sitting right there why not just mobilise it?  So I did.  Without modifying any server side code and only tweaking the views I found I was able to create a fully jquery-mobilified version of the template.  Here's what I did.

Clean House

We need to strip some of the dead weight out of our project including some of the mark up and resources,

Resources

First things first I dropped the current bundle of scripts (MS Ajax, jQuery 1.4.1, Validate etc.) and the Site.css.  Next I added the jQuery Mobile assets,

  • jQuery 1.4.3 (jquery-1.4.3.js)
  • jQuery Mobile 1.0a1 (jquery.mobile-1.0a1.js)
  • jQuery Mobile CSS (jquery.mobile-1.0a1.css)
  • jQuery Mobile images

In the following project structure

Site.Master

Next I cut down the Site.Master to it's bare minimum and add the HTML5 DocType and the references to jQuery Mobile ,

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<!DOCTYPE html>
<html>
    <head id="Head1" runat="server">
        <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
        <link href="../../Content/jquery.mobile-1.0a1.css" rel="stylesheet" type="text/css" />
        <script src="../../Scripts/jquery-1.4.3.min.js" type="text/javascript"></script>
        <script src="../../Scripts/jquery.mobile-1.0a1.min.js" type="text/javascript"></script>
    </head>
    <body>
        <asp:ContentPlaceHolder ID="MainContent" runat="server" />
    </body>
</html>

I could have included the basic markup for a full page but I wanted to keep it as flexible for now.  I also removed the LogOnUserControl for now as I want to just push it into the main page.

Marking Up

The next step was to tweak the markup of the pages so they comply with jQuery Mobile and can be mobilised correctly.  Most pages are marked up in the same way so rather than list them all I'll just list the highlights.

Home.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div data-role="page">
	    <div data-role="header">
		    <h1>Home Page</h1>
            <% if (!Request.IsAuthenticated) { %>
                <a href="<%=Url.Action("LogOn", "Account")%>" data-icon="forward" class="ui-btn-right">Logon</a>
            <% } else { %>
                <a href="<%=Url.Action("LogOff", "Account")%>" data-icon="back" class="ui-btn-right">Log Off</a>
            <% } %>
	    </div>
	    <div data-role="content">
		    <h2><%: View.Message %></h2>
	    </div>
    </div>
</asp:Content>

I have a top toolbar button for Login/Log Off (depending on context) and the View.Message from the old page is included as the content of the page.

Logon.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Mvc.Mobile.Template.Models.LogOnModel>" %>

<asp:Content ID="loginTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Log On
</asp:Content>

<asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
    <div data-role="page">
	    <div data-role="header">
		    <h1>Log On</h1>
            <a href="<%=Url.Action("Register", "Account")%>" data-icon="gear" class="ui-btn-right"  data-rel="dialog" data-transition="pop">Register</a>
	    </div>
	    <div data-role="content">
            <p>Please enter your username and password</p>
            <% using (Html.BeginForm()) { %>
                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.UserName) %>
                    <%: Html.TextBoxFor(m => m.UserName) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.UserName) %>
                    </div>
                </div>
                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.Password) %>
                    <%: Html.PasswordFor(m => m.Password) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.Password) %>
                    </div>
                </div>
                <div data-role="fieldcontain">
                    <%: Html.CheckBoxFor(m => m.RememberMe) %>
                    <%: Html.LabelFor(m => m.RememberMe) %>
                </div>
                <input type="submit" value="Log On" data-theme="b"/>
            <% } %>
	    </div>
    </div>
</asp:Content>

Gives us the login form and a link to the Register screen.  I marked the Register screen as a Dialog just to be a wee bit different.  The Register screen itself is marked up as a normal page (the framework handles the displaying of a dialog automagically).  I also added a different theme to the login button and a "pop" transition to the Register dialog through the data- attributes.

One other thing I had to do was add a "fieldcontain" wrapper to the field blocks as there was some layout issues.  This is documented in the jQuery Mobile docs.

Register.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Mvc.Mobile.Template.Models.RegisterModel>" %>

<asp:Content ID="registerTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Register
</asp:Content>

<asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" runat="server">
    <div data-role="page">
	    <div data-role="header">
		    <h1>Register</h1>
            <a href="<%=Url.Action("Index", "Home")%>" data-icon="grid" class="ui-btn-right">Home</a>
	    </div>
	    <div data-role="content">
            <p>Passwords are required to be a minimum of <%: View.PasswordLength %> characters in length.</p>
            <% using (Html.BeginForm()) { %>
                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.UserName) %>
                    <%: Html.TextBoxFor(m => m.UserName) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.UserName) %>
                    </div>
                </div>

                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.Email) %>
                    <%: Html.TextBoxFor(m => m.Email) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.Email) %>
                    </div>
                </div>

                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.Password) %>
                    <%: Html.PasswordFor(m => m.Password) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.Password) %>
                    </div>
                </div>

                <div data-role="fieldcontain">
                    <%: Html.LabelFor(m => m.ConfirmPassword) %>
                    <%: Html.PasswordFor(m => m.ConfirmPassword) %>
                    <div>
                        <%: Html.ValidationMessageFor(m => m.ConfirmPassword) %>
                    </div>
                </div>

                <input type="submit" value="Register" data-theme="b" />
            <%} %>
	    </div>
    </div>
</asp:Content>

The most complex screen in the app.  Nothing special added only some different markup.

Running It

That was it.  After adapting the rest of the pages (via manipulating markup)  I was left with a mobilized version of the MVC project template.  Again I didn't have to make any code changes - validation still worked, forms still worked, login, register, change password - all worked as expected (screen shots from Chrome which has a few issues with rounded corners on fields in jQuery Mobile).

Published in .NET on October 25, 2010