left-icon

ASP.NET MVC 4 Mobile Websites Succinctly®
by Lyle Luppes

Previous
Chapter

of
A
A
A

CHAPTER 11

Still Using MVC 3?

Still Using MVC 3?


“I’ll have what she’s having!”
    Anonymous customer in When Harry Met Sally

There are a lot of projects out there that have been developed with MVC 3, and if you are still working with MVC 3, you may be feeling a little left out right now and want some of what everyone else is having. Don’t worry about it—you can achieve the same mobile-friendly effects using existing MVC 3 technology, and position yourself to move right into MVC 4 without a big disruption.

Speed Bump: MVC 3 and MVC 4 side by side

You can easily install MVC 4 alongside MVC 3 and it is not supposed to break anything. However, there is one little speed bump that you may run into.

When MVC 4 is installed, it installs its new files for your system in the C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4 folder, so it does keep the new MVC files separate from the ASP.NET MVC 3 folder that it sits beside. However, it also installs a new v2.0 folder inside the ASP.NET Web Pages folder next to the v1.0 folder: C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0.

This folder just so happens to contain a few files that MVC 3 uses with the same name but a different version.

When you go back and build your existing MVC 3 project after installing MVC 4, you may get this compile error:

c:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1360,9): warning MSB3247: Found conflicts between different versions of the same dependent assembly.

If you dig around for a while, you’ll find that your MVC 3 project references the System.Web.Pages and System.Web.Helpers DLLs, but without specifying a version. The quickest fix is to open up your project file in Notepad and make a couple of quick edits that put everything back in its proper place and your project will start working properly once again.

Project Definition File Before: (*.csproj)

<Reference Include="System.Web.WebPages">

  <Private>True</Private>

</Reference>

<Reference Include="System.Web.Helpers">

Project Definition File After: (*.csproj)

<Reference Include="System.Web.WebPages, Version=1.0.0.0, Culture=neutral,

  PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

  <SpecificVersion>True</SpecificVersion>

</Reference>

<Reference Include="System.Web.Helpers, Version=1.0.0.0, Culture=neutral,

  PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

  <Private>True</Private>

</Reference>

Once you’ve fixed that little bug, you should be able to run your MVC 3 projects normally again and everything works side by side. However, you will have to apply this fix to each and every MVC 3 project you open.

Back to the MVC 3 Project

Let’s get back to our topic at hand: how do you convert your MVC 3 project to use the mobile-friendly techniques that we are using in our fancy new MVC 4 project? As it turns out, there is not much we need to add to make this work in MVC 3.

The first thing to do is to install the jQuery.Mobile package using NuGet, just like we do in MVC 4. Using the Package Manager Console command line, run the command Install-Package jQuery.Mobile.

Since the DisplayModeProvider code is a new feature in MVC 4, we will have to replicate that functionality in our MVC 3 project. In the root of your project, create a new class file called MobileCapableRazorViewEngine.cs, and put the following code into that class:

using System;

using System.IO;

using System.Web;

using System.Web.Mvc;

namespace YourApplicationNameSpace

{

  // In Global.asax.cs Application_Start you can insert these

  // into the ViewEngine chain like so:

  //

  // ViewEngines.Engines.Insert(0, new

  //   MobileCapableRazorViewEngine());

  //

  // or

  //

  // ViewEngines.Engines.Insert(0,

  //  new MobileCapableRazorViewEngine("iPhone")

  //  {

  //    ContextCondition = (ctx =>

  //     ctx.Request.UserAgent.IndexOf(

  //      "iPhone", StringComparison.OrdinalIgnoreCase) >= 0)

  //  });

  public class MobileCapableRazorViewEngine : RazorViewEngine

  {

    public string ViewModifier { get; set; }

    public Func<HttpContextBase, bool> ContextCondition

      { get; set; }

    public MobileCapableRazorViewEngine()

      : this("Mobile", context =>

        context.Request.Browser.IsMobileDevice)

    {

    }

    public MobileCapableRazorViewEngine(string viewModifier)

      : this(viewModifier,

        context => context.Request.Browser.IsMobileDevice)

    {

    }

    public MobileCapableRazorViewEngine(string viewModifier,

      Func<HttpContextBase, bool> contextCondition)

    {

      this.ViewModifier = viewModifier;

      this.ContextCondition = contextCondition;

    }

    public override ViewEngineResult FindView(

     ControllerContext controllerContext,

     string viewName, string masterName, bool useCache)

    {

      return NewFindView(controllerContext, viewName, 

        null, useCache, false);

    }

    public override ViewEngineResult FindPartialView(

      ControllerContext controllerContext,

      string partialViewName, bool useCache)

    {

      return NewFindView(controllerContext, partialViewName,

        null, useCache, true);

    }

    private ViewEngineResult NewFindView(

      ControllerContext controllerContext,

      string viewName, string masterName, bool useCache,

      bool isPartialView)

    {

      if (!ContextCondition(controllerContext.HttpContext))

      {

        // We found nothing and we pretend we looked nowhere.

        return new ViewEngineResult(new string[] { });

      }

      // Get the name of the controller from the path.

      string controller = controllerContext.RouteData

        .Values["controller"].ToString();

      string area = "";

      try

      {

        area = controllerContext.RouteData.DataTokens["area"]

         .ToString();

      }

      catch

      {

      }

      // Apply the view modifier.

      var newViewName = string.Format("{0}.{1}", viewName,

        ViewModifier);

      // Create the key for caching purposes.         

      string keyPath = Path.Combine(area, controller,

        newViewName);

      string cacheLocation =

        ViewLocationCache

          .GetViewLocation(controllerContext.HttpContext,

          keyPath);

      // Try the cache.         

      if (useCache)

      {

        //If using the cache, check to see if the location

        //is cached.                             

        if (!string.IsNullOrWhiteSpace(cacheLocation))

        {

          if (isPartialView)

          {

            return new ViewEngineResult(CreatePartialView(

              controllerContext, cacheLocation), this);

          }

          else

          {

            return new ViewEngineResult(

              CreateView(controllerContext, cacheLocation,

               masterName),

                this);

          }

        }

      }

      string[] locationFormats = string.IsNullOrEmpty(area) ?

        ViewLocationFormats : AreaViewLocationFormats;

      // For each of the paths defined, format the string and

      // see if that path exists. When found, cache it.         

      foreach (string rootPath in locationFormats)

      {

        string currentPath = string.IsNullOrEmpty(area)

          ? string.Format(rootPath, newViewName, controller)

          : string.Format(rootPath, newViewName, controller,

            area);

        if (FileExists(controllerContext, currentPath))

        {

          ViewLocationCache.InsertViewLocation(

            controllerContext.HttpContext,

            keyPath, currentPath);

          if (isPartialView)

          {

            return new ViewEngineResult(CreatePartialView(

              controllerContext, currentPath), this);

          }

          else

          {

            return new ViewEngineResult(CreateView(

              controllerContext, currentPath, masterName),

                this);

          }

        }

      }

      // We found nothing and we pretend we looked nowhere.

      return new ViewEngineResult(new string[] { });

    }

  }

}

This code is also available on NuGet by running the following command:

PM> Install-Package MobileViewEngines

If you install the NuGet package, it won’t automatically include the code in your project, but the code will be in the Packages folder alongside your project, so you can copy it from there.

Now that you have the view engine available for use, edit the Global.asax.cs file and update the Application_Start function with the following code (which looks amazingly similar to what we did for MVC 4!):

protected void Application_Start()

{

  AreaRegistration.RegisterAllAreas();

  RegisterGlobalFilters(GlobalFilters.Filters);

  RegisterRoutes(RouteTable.Routes);

  ViewEngines.Engines.Insert(0,

  new MobileCapableRazorViewEngine("Phone")

  {

    ContextCondition = (ctx =>

      ctx.Request.UserAgent.IndexOf("iPhone",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("iPod",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("Droid",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("Blackberry",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.StartsWith("Blackberry",

          StringComparison.OrdinalIgnoreCase))

  });

  ViewEngines.Engines.Insert(0,

  new MobileCapableRazorViewEngine("Tablet")

  {

    ContextCondition = (ctx =>

      ctx.Request.UserAgent.IndexOf("iPad",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("Playbook",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("Transformer",

          StringComparison.OrdinalIgnoreCase) >= 0 ||

      ctx.Request.UserAgent.IndexOf("Xoom",

          StringComparison.OrdinalIgnoreCase) >= 0)

  });

}

That’s it—you now have a code base that is almost functionally equivalent to what we have created using the MVC 4 mobile functionality with the DisplayModeProvider. There are a few differences, but you should be able to start creating a very mobile-friendly website using this code base. The jQuery.Mobile features should all be the same, and most of the layout files should be the same. There are a few things like the bundling technology that are not available in MVC 3, so you will have to list each of your style sheets and JavaScript files in your layout files (or minify and concatenate them yourself).

The following figure is an example of an MVC 3 application with a screenshot of the code for the layout page, a screenshot of the resulting familiar, blue, tabbed, default layout for the desktop, and the updated phone layout with the title changed to Phone Home Page.

MVC 3 App with Desktop and Phone Layouts

When you combine this technique with the things you learned about earlier in the book, you should be able to start making your MVC 3 applications nearly as mobile-friendly as MVC 4 applications!

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.