Scott Hanselman

NuGet Package of the Week #10 - New Mobile View Engines for ASP.NET MVC 3, spec-compatible with ASP.NET MVC 4

September 5, '11 Comments [18] Posted in ASP.NET | ASP.NET MVC | Mobile | NuGet | NuGetPOW
Sponsored By

Desktop ASP.NET MVC Application next to the same application in a mobile browserI did some basic mobile view engine work for ASP.NET MVC for Mix in 2009 and then created what I thought was a better ASP.NET MVC Mobile ViewEngine in 2010. Unfortunately, the second one (the "better" one) had a caching bug that only showed itself in Release mode. This last month, Jon, John, Peter and I updated NerdDinner to MVC 3 with Razor and a pile of other new features. One of those new features was jQuery Mobile support and that meant we need to fix this bad Mobile View Engine. Additionally, ASP.NET MVC 4 will include actual supported Mobile Views support, so the pressure was on.

However, we wanted to make sure any new MVC 3 Mobile View sample was mostly compatible with whatever scheme ASP.NET MVC 4 uses. The original folder layout for my proposed ViewEngine was by folder but the final design was to use file names. That means instead of ~/Views/Home/Mobile/Index.cshtml, you'd have ~/Views/Home/Index.Mobile.cshtml. Of course, you can change this if you really want to yourself, but that's the default.

Alternate Views shown in Solution Explorer in subfolders Alternate Views shown in Solution Explorer separated by filename differences, not folders

Peter Mourfield jumped in and did the updated Mobile View Engines and we've put them on NuGet for you, Dear Reader.

Remember, these are for ASP.NET MVC 3. You don't need them when ASP.NET MVC 4 comes out, and the general idea will be that you will remove the Razor (or WebForms) ViewEngine and replace it with the mobile version which is a superset of functionality.

ViewEngines.Engines.Remove(ViewEngines.Engines.OfType<RazorViewEngine>().First());
ViewEngines.Engines.Add(new MobileCapableRazorViewEngine());
ViewEngines.Engines.Remove(ViewEngines.Engines.OfType<WebFormViewEngine>().First());
ViewEngines.Engines.Add(new MobileCapableWebFormViewEngine());

You can do this bit of work in Application_Start, or with the Web Activator like the MobileViewEngines.Razor.Samples does. The sample NuGet package includes both VB and C#, so you'll want to delete the one you won't use. You only need to use the ViewEngine you need, so if you aren't using WebForms, don't bother with those lines.

The whole ViewEngine that Peter made is only 81 lines of code so you can certainly change it to your taste. Peter and I put the source on BitBucket for changes, forks and fixes.

image

Just add the word Mobile in your views, like Index.Mobile.cshtml or Details.Mobile.aspx and those will be used when a mobile browser is detected. The detection  is using the standard Browser.IsMobileDevice call from ASP.NET, so consider using a browser database like http://51degrees.mobi (also on CodePlex, and NuGet).

Remember, this is a clean-room implementation (not derived from ASP.NET MVC  4) that has just basic mobile view overrides. I'm glad it doesn't have the release mode bug like my previous ones did, and we are using this implementation live on http://nerddinner.com. Modify the source if you need advanced support for multiple mobile views (like iPhone, BlackBerry, etc) other than just "mobile" like this one does. There are features that this basic ViewEngine doesn't have that a more sophisticated solution like ASP.NET MVC 4's or other folks' implementations could have like:

  • Browser Overrides: Forcing or "opting out" of mobile and using desktop
  • Device-specific custom layouts

Still, we've found it to be simple and useful on NerdDinner and we hope it's useful to you.

Related Links

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Tuesday, September 06, 2011 2:01:21 AM UTC
Hi Scott,

ScientiaMobile (owners of the wurfl.xml file) recently changed their license terms on 30 August 2011.

Essentially I think what this means is that products like 51Degrees (who makes use of the wurfl.xml file) cannot ship with a wurfl.xml later than the July 2011 snapshot (July snapshot still was still using the old open and free license). So further down the line your devices file will be out-of-date and won't have a list of the latest devices.

ScientiaMobile has changed their stance a little since they felt were getting a hard deal from clones and copycats. They felt they needed to protect their IP.

Furthermore the new license only permits that the wurfl.xml file to be used with ScientiaMobile official API's

http://wurfl.sourceforge.net/licence.php

"The WURFL data can only be used in connection with one of the official standard APIs released and supported by ScientiaMobile."

It also seems that if you want to receive the latest WURFL file then you need to subscribe to a commercial license.

http://51degrees.mobi/Support/Forum/tabid/65/forumid/1/threadid/1222/scope/posts/threadpage/1/Default.aspx

51 Degrees is a great product - But the move by ScientiaMobile kinda sucks.
Anon
Tuesday, September 06, 2011 2:49:41 AM UTC
Hi Scott,

If the structure of the views are the same for both WebForms and Razor would be nice, I think it should be consistent all the way down.

Cheers,
Ldsenow
Tuesday, September 06, 2011 8:14:47 PM UTC
Hey Scott,
I see that there are no options for rendering partialviews in the viewengine. Is this something you guys are planning to have in next iteration?

Also, I see that you do not have options if developers would like to add the mobile views into a folder(like /mobile/mobileview.cshtml)

The implementation seems to be easy though.

Thanks
Sujith
Tuesday, September 06, 2011 8:59:23 PM UTC
To me a mobile view engine is to MVC what GridView is to WebForms: great for selling the framework and a nice demo, not so great beyond basic applications.

For web sites we see a movement towards responsive design, where one design fits multiple screens by adjusting itself (client-side). This is handled first and foremost by stylesheets and master pages.

For web applications I doubt that a significant number of mobile views 'map' to the same controllers/actions to make a mobile view engine the right choice. In my experience the form factor and touch input require mobile views to focus on either different tasks, or perform same tasks in a different way. The way I understand it, that means different controllers and or actions, and thus by default different views, with no need for a mobile view engine.

In fact, as I write this, I would go so far as to say that the very name 'mobile view engine' highlights a problem around separation of concerns. The mobile aspect of the users is not in my mind something a view engine should deal with. The engine is part of the MVC infrastructure and mainly concerned with finding, parsing (and compiling) views.

Logically, I see a problem in this collection of view engine names: Razor, WebForms, Spark, NHaml, Xslt, Mobile. Can you spot the odd one out?
Mike
Wednesday, September 07, 2011 1:29:04 AM UTC
Mike - Agreed on 90% of your points. That's why in MVC 4 it's a first class thing and doesn't fit in that list. Mobile can be with any view. Also, to your point about mapping Views to Controllers, also agreed. We'll have more to show in this space soon, as well as patterns on how to do disconnected HTML5 applications with MVC.
Wednesday, September 07, 2011 2:29:02 PM UTC
interesting that the approach being used in mvc4 seems to (by default) only allow one "mobile" page per page... whereas your earlier attempt appears to support multiple mobile views, based on device type etc. Does this mean the mobile views in mvc4 are assumed to be able to handle every device type...?
Wednesday, September 07, 2011 8:49:10 PM UTC
No. MVC4 will do multiple. This new one does one, but can be changed in code by you.
Thursday, September 08, 2011 1:54:33 PM UTC
Partial views are not supported, should add FindPartialView to the view engine. Also, the urls generated on the address bar are not working, when clicked refresh or F5 on the browser the pages navigated to returns the first page.
Ken C. Len
Thursday, September 08, 2011 5:48:40 PM UTC
There's a recent slide somewhere from a recent web conference that shows that Razor has marginally worse performance than Web Forms for a typical page. So, no obvious difference there. (Would be interesting to try the stuff that really bogs down the Web Forms View Engine though, like deeply recursive partials..
Tuesday, September 13, 2011 4:40:54 AM UTC
I think the whole ViewEngine in ASP.NET MVC is a Design Smell and a classic example to apply the GOF Strategy pattern. Why? Now, lets say with your above example If I want add Theme support I would have to add MobileAndThemeCapableRazorViewEngine and MobileAndThemeCapableWebformViewEngine, see where I am going with this? Maybe a better solution to this problem is to create a Strategy e.g. ViewSelector which the ViewEngine should use to get the location. By default there can be "DefaultRazorViewSelector" and "DefaultWebformViewSelector" with current the behavior. Now, If I have custom logic to resolve the view path all I have to do is create a new Strategy and register it in the ViewEngine. Next, if I have complex logic like I said both Mobile And Theme then strategy can be itself extended with decorator/composite pattern.
Wednesday, September 14, 2011 9:09:25 PM UTC
Scott,

Any plans on adding some of those features (like the ability to have a mobile layout) to your version prior to the MVC4 release? That would be a very handy feature! I can do without the device specific stuff that MVC4 talks about for the time being, but think having a mobile layout option would be quite nice.

Thanks,

Jon
Jon Seeley
Thursday, September 15, 2011 5:48:59 PM UTC
I think this is jQueryMobile thing but if you have
@foreach (var item in Model)
{
<li>
<a href="@Url.Action("Bio", "Facebook", new { id = item.EmployeeID })"><h5>@string.Format("{0}", item.Employee)</h5>
@foreach (var office in item.Offices)
{
if (office.DisplayNumber.Length > 1)
{
<p>
@string.Format("{0} {1}", office.OfficeLocation, office.DisplayNumber)
<a href="@string.Format("tel:{0}", office.NumberToDial)">@office.DisplayNumber</a>
</p>
}
}
</a>
</li>
}

the <a href="@string.Format("tel:{0}", office.NumberToDial)">@office.DisplayNumber</a>

line messes things up. So how do you have a phone # displayed in this block? I think it's the inside of the tag that messes it up
Thursday, October 13, 2011 9:14:33 AM UTC
Hi Scott,

I've try to use this engine and i have some remarks about the functionality. First it seems that the current implementation doesn't search in the main Shared directory if I'm in an area and doesn't find the view in that area.
I've modify your implementation this way.


public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache){
ViewEngineResult result;
if(controllerContext.HttpContext.Request.Browser.IsMobileDevice){
result = FindView(controllerContext, viewName + ".Mobile", masterName, useCache);
}

// If we're looking for a Mobile view and couldn't find it try again without modifying the viewname
if (result == null || result.View == null)
{
result = FindView(controllerContext, viewName, masterName, useCache);
}
return result;
}


This worked for me and there is no need for the second function "NewFindView".
I would really like your opinion on this implementation.

Thanks.
Monday, October 24, 2011 12:25:48 AM UTC
Everything works perfectly when I run my test site locally.

However once I deploy this mvc 3 with the mobile files onto a production server with IIS7.5, and then I load the site from a mobile device I get the exception below which bombs W3WP, restarting it won't even fix it - I have to reboot the machine.

Faulting application name: w3wp.exe, version: 7.5.7601.17514, time stamp: 0x4ce7afa2
Faulting module name: clr.dll, version: 4.0.30319.239, time stamp: 0x4e1822f4
Exception code: 0xc00000fd
Fault offset: 0x0000000000026613
Faulting process id: 0xe0c
Faulting application start time: 0x01cc91e226c627ae
Faulting application path: c:\windows\system32\inetsrv\w3wp.exe
Faulting module path: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Report Id: 64d44af9-fdd5-11e0-9b0c-002590380257
Monday, October 24, 2011 12:57:32 AM UTC
Ok it works now. I had to remove this additional

ViewEngines.Engines.Insert(0, new MobileCapableRazorViewEngine());

inside Global.asax since we now have it in MobileViewEnginesBootstrapper.cs
Tuesday, October 25, 2011 11:25:11 PM UTC
What's the best way to tweak this to allow BB? Works great on iphone/ipad so far, but it is still showing non-mobile mvc Views on BB Torch.
Thursday, November 29, 2012 1:16:08 PM UTC
Hi

When I use this on a MVC3 project in combination with OutputCache (location="Server") and a long duration, whichever device hits the app first will fill the cache so that both desktop and mobile gets the same content.

Is there any workarounds for this or am I missing something obvious?

Thanks
Espen
Thursday, November 29, 2012 1:56:52 PM UTC
Seems like VaryByCustom was the answer: http://visitmix.com/writings/using-varybycustom-with-outputcache-in-asp-net-mvc-to-support-caching-for-logged-in-users
Espen
Comments are closed.

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