NuGet Package of the Week #10 - New Mobile View Engines for ASP.NET MVC 3, spec-compatible with ASP.NET MVC 4
I 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.
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.
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
- ASP.NET MVC 3 Mobile Capable View Engines on NuGet - "MobileViewEngines"
- Mobile View Engines Razor Sample - "MobileViewEngines.Razor.Sample"
- 51Degrees - my preferred mobile browser database "51degrees.mobi" with ASP.NET support
- WURFL - mobile browser device database ASP.NET API
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.
About Newsletter
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
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
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?
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
@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
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.
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
ViewEngines.Engines.Insert(0, new MobileCapableRazorViewEngine());
inside Global.asax since we now have it in MobileViewEnginesBootstrapper.cs
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
Comments are closed.
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.