Petrus Theron
Petrus Theron

Reputation: 28886

ASP.NET MVC 4 Mobile Display Modes Stop Working

Mobile display modes in ASP.NET MVC 4 stop serving the correct views after about an hour of uptime, despite browser overrides correctly detecting an overridden mobile device.

Recycling the application pool temporarily solves the problem.

The new browser override feature correctly allows mobile devices to view the desktop version of a site, and vice-versa. But after about an hour of uptime, the mobile views are no longer rendered for a mobile device; only the default desktop Razor templates are rendered. The only fix is to recycle the application pool.

Strangely, the browser override cookie continues to function. A master _Layout.cshtml template correctly shows "mobile" or "desktop" text depending on the value of ViewContext.HttpContext.GetOverriddenBrowser().IsMobileDevice, but the wrong views are still being rendered. This leads me to believe the problem lies with the DisplayModes.

The action in question is not being cached:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]

I am using 51Degrees for mobile detection, but I don't think this should affect the overridden mobile detection. Is this a bug in DisplayModes feature for ASP.NET MVC 4 Beta & Developer Preview, or am I doing something else wrong?


Here is my DisplayModes setup in Application_Start:

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
    ContextCondition = context =>
        context.GetOverriddenBrowser().IsMobileDevice
        && (context.Request.UserAgent.IndexOf("iPhone", StringComparison.OrdinalIgnoreCase) >= 0
        || context.Request.UserAgent.IndexOf("Android", StringComparison.OrdinalIgnoreCase) >= 0
        || !context.Request.Browser.IsMobileDevice)
    });

/*  Looks complicated, but renders Home.iPhone.cshtml if the overriding browser is
    mobile or if the "real" browser is on an iPhone or Android. This falls through
    to the next instance Home.Mobile.cshtml for more basic phones like BlackBerry.
*/

DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile")
{
    ContextCondition = context =>
        context.GetOverriddenBrowser().IsMobileDevice
});

Upvotes: 28

Views: 12533

Answers (7)

user3591848
user3591848

Reputation: 1

So guys here is the answer to all of your worries..... :)

To avoid the problem, you can instruct ASP.NET to vary the cache entry according to whether the visitor is using a mobile device. Add a VaryByCustom parameter to your page’s OutputCache declaration as follows:

<%@ OutputCache VaryByParam="*" Duration="60" VaryByCustom="isMobileDevice" %>
Next, define isMobileDevice as a custom cache parameter by adding the following method override to your Global.asax.cs file:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if (string.Equals(custom, "isMobileDevice", StringComparison.OrdinalIgnoreCase))
        return context.Request.Browser.IsMobileDevice.ToString();

    return base.GetVaryByCustomString(context, custom);
}

This will ensure that mobile visitors to the page don’t receive output previously put into the cache by a desktop visitor.

please see this white paper published by microsoft. :)

http://www.asp.net/whitepapers/add-mobile-pages-to-your-aspnet-web-forms-mvc-application

Thanks and Keep coding.....

Upvotes: 0

marcind
marcind

Reputation: 53191

This is a known issue in MVC 4 (Codeplex: #280: Multiple DisplayModes - Caching error, will show wrong View). This will be fixed in the next version of MVC.

In the meantime you can install a workaround package available here: http://nuget.org/packages/Microsoft.AspNet.Mvc.FixedDisplayModes.

For most applications simply installing this package should resolve the issue.

For some applications that customize the collection of registered view engines, you should make sure that you reference Microsoft.Web.Mvc.FixedRazorViewEngine or Microsoft.Web.Mvc.FixedWebFormViewEngine, instead of the default view engine implementations.

Upvotes: 21

Lee Englestone
Lee Englestone

Reputation: 4667

Can you not just do this?

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    // Code removed for clarity.

    // Cache never expires. You must restart application pool
    // when you add/delete a view. A non-expiring cache can lead to
    // heavy server memory load.

    ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache =
        new DefaultViewLocationCache(Cache.NoSlidingExpiration);

    // Add or Replace RazorViewEngine with WebFormViewEngine
    // if you are using the Web Forms View Engine.
}

Upvotes: 0

user1436475
user1436475

Reputation: 11

Possibly a bug in ASP.NET MVC 4 related to caching of views, see:

http://forums.asp.net/p/1824033/5066368.aspx/1?Re+MVC+4+RC+Mobile+View+Cache+bug+

Upvotes: 1

user1501346
user1501346

Reputation: 11

I had a similar issue and it turned out to be a bug when mixing webforms based desktop views with razor based mobile views.

See http://aspnetwebstack.codeplex.com/workitem/276 for more info

Upvotes: 1

KenL
KenL

Reputation: 875

If you want all mobile devices to use the same mobile layout you can use

DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile") 
{ 
    ContextCondition = context => 
        context.GetOverriddenBrowser().IsMobileDevice 
}); 

And of course you need to make a view in the shared layout folder named _Layout.Mobile.cshtml

If you want to have a separate layout for each type of device or browser you need to do this;

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android")
            {
                ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
                    ("Android", StringComparison.OrdinalIgnoreCase) >= 0)
            });

            DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
            {
                ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
                    ("iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
            });

            DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Mobile")
            {
                ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
                   ("IEMobile", StringComparison.OrdinalIgnoreCase) >= 0)
            });

And of course you need to make a view in the shared layout folder for each named

_Layout.Android.cshtml _Layout.iPhone.cshtml _Layout.Mobile.cshtml

Upvotes: 0

hoserdude
hoserdude

Reputation: 829

I can't speak for this particular stack (I'm still in MVC2) but check your output caching setup (either in your controllers or views - and in your web.config in your app and at the machine level). I've seen it work initially for the first few users and then a desktop browser comes in right around the time ASP decides to cache, then everyone gets the same view. We've avoided output caching as a result, hoping this would get addressed later.

Upvotes: 0

Related Questions