26

Closed

Multiple DisplayModes - Caching error, will show wrong View

description

Hi

I have large issue with one of my sites where i have implemted multiple views side by side using MVC4 DisplayModes.

Pages are like
MembershipPage.cshtml
MembershipPage.Mobile.cshtml
_Layout.cshtml
_Layout.Mobile.cshtml

The pages render just fine after having just started the page, however after some time mobile devices will start showing the desktop view but in the Mobile layout page.

What seems to be happening is that the RazorViewEngine has some kind of timed caching for the views, and since my desktop views are used way more than the mobile counterparts the mobile views timeout and the desktop ones are then served instead.

I have currently solved this by overriding the RazorViewEngine's FindPartialView and FindView and setting useCache to false. However this is naturally a huge performance issue.

I have checked the source code and to my opinion this is what is happening:

This is the cache being used, it has a default timeout of 15 minutes.
http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/3b39178d07d6#src%2fSystem.Web.Mvc%2fDefaultViewLocationCache.cs

The VirtualPathProviderViewEngine, uses this to cache the views, however the implementation of this does not take into account the fact that some views will timeout before other DisplayMode views.
http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/3b39178d07d6#src%2fSystem.Web.Mvc%2fVirtualPathProviderViewEngine.cs

Within the GetPathFromGeneralName all DisplayMode views are cached. However since they are not accessed every time a specific view is rendered some views will timeout before all timeout, resulting in wrong views being returned.


I have created a solution with an MVC site, this site has one View Index.cshtml and a Mobile counter part Index.Mobile.cshtml.

It is very important not to run in debug compilation <compilation debug="false" targetFramework="4.0" /> I have already set this in the web.config, the reason for this is that this code resides inside the VirtualPathProviderViewEngine constructor.

if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled) {
ViewLocationCache = DefaultViewLocationCache.Null;
} else {
ViewLocationCache = new DefaultViewLocationCache();
}

The solution also has a very simple winforms application that you simply need to run it will then show you the issue on a live site i have put up on http://mvcbug.flexybox.com. (AS I HAVE NOW PUBLISHED A LINK BOTH HERE AND IN THE APPLICATION, AND AS THIS ERROR DEPENDS ON WHO ACCESSES THIS LINK WITHIN 16 MINUTES YOU SHOULD HOST YOUR OWN SITE TO TEST THIS)

You will need to let it run for 16 minutes ( i have added a minute to ensure that the cache runs out ).

Initially i request both views, this is actually not neccesary but it shows you that the site does have a Mobile view. MVC will automatically cache all the views for the different displaymodes for the view that is requested. Meaning if you request just the Desktop view, the mobile view will also be added to the cache, this is why the mobile view works correctly as long as it is requested continually.

This application requests the desktop site every 15 seconds, this way the sliding expiration on the cache is used on the desktop view, which means the desktop view stays in the cache. After the full 16 minutes, the application will then request the mobile view which will actually return the desktop view because mvc will check the cache before checking for the actual views in the view folders.

In the MVC project there is also a ShortMemoryEngine (you just need to comment it in global.asax), i used this when creating the app because the only thing it does is replace the 15 minute cache with a 1 minute cache.

I have put the solution up here: http://mvcbug.flexybox.com/Content/MVC_CACHE_BUG.zip
Closed Jun 4, 2013 at 6:17 AM by kamehrot

comments

thuanto wrote Jul 17, 2012 at 12:08 AM

I would like to add that this has a high impact for me as my desktop and mobile views are very different and I end up an internal server error because the desktop layout has sections while the mobile layout doesn't. I cannot go to production because of this bug. The suggested workaround, disable cache, has a high cost.

mSchmidt wrote Jul 17, 2012 at 6:51 AM

Hi thuanto.

I have currently overridden mine with a 1 year cache, my site is not so big that caching all sites is an issue.
And when i change views i simply restart the application pool.
public class LongMemoryEngine : RazorViewEngine
{
    public LongMemoryEngine() 
    {
        var cache = new DefaultViewLocationCache(TimeSpan.FromDays(365));
        base.ViewLocationCache = cache;

    }
}
However still a fix is preferable.

ChadThiele wrote Jul 18, 2012 at 7:21 AM

I'd like to add... this is far more important than "low" priority. This essentially breaks MVC 4's mobile support. You'd be lying to developers by saying ASP.NET MVC 4 has mobile support. You either need to remove mobile support from the feature list of MVC 4, or fix this bug... it's that important.

RickAndMSFT wrote Jul 20, 2012 at 12:39 AM

To work around the mobile cache issue, you can either disable the cache or set the cache to never expire. If you set the cache to never expire, you will need to restart your application pool when your views change. The following code shows how to address the cache issue in the Application_Start method found in the Global.asax file.

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

// Cache never expires. You must restart application pool
// when your views change. A non-expiring cache can lead to 
// heavy server memory load.

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

// Alternative approach, disable caching.
ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache = 
    DefaultViewLocationCache.Null;

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

RickAndMSFT wrote Jul 20, 2012 at 6:38 PM

You can set the cache to never expire. If you set the cache to never expire, you will need to restart your application pool when add or delete a view (but not when you modify a view). The following code shows how to address the cache issue in the Application_Start method found in the Global.asax file.
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. 
}

jeffyjones wrote Aug 19, 2012 at 3:24 AM

Not that my two cents add all that much, but I agree with thuanto that this is a seriously big deal. While I realize I can pull the code and fix the bug, using my own custom build is not only suboptimal, but not something I can expect users of my open source project to do either. This does essentially break the mobile views, especially when they don't share the same sections that the normal views have.

I'm not sure how you plan to roll going forward with releases, now that you're open source, but I sure hope it's not a matter of waiting for v5.

happyspider wrote Aug 21, 2012 at 5:08 PM

We have the same issue with an application that uese the webforms viewengine.

msmolka wrote Aug 22, 2012 at 3:57 PM

I was supprised when it hit me. This is quite critical.

jeffyjones wrote Aug 24, 2012 at 6:52 PM

As it turns out, the fix mentioned to set the cache expiration does not appear to work for me, or at least, not indefinitely. I've had success in it lasting for quite some time, but then today I hit my site from a mobile and the bug reappeared.

This sure seems awfully critical to me, because a key feature is essentially completely broken. In the best case, you just get the wrong views served up, but in the worst case, the alternate views don't share the same sections from one layout to the next, so you end up with 500's.

I'd really like to hear what the dev team is considering in terms of a patch or update.

ibneadam wrote Aug 25, 2012 at 2:59 PM

We are having the same issue. This is a high impact on our business as well as SEO. Please fix it!

marcind wrote Aug 29, 2012 at 1:02 AM

Hi everyone. I'm looking into this issue. My hope is that we will be able to release some form of workaround that addresses this problem.
I will try my best to update this thread as the situation develops.

RobertjanTuit wrote Sep 5, 2012 at 8:20 PM

Is there any news on when this is going to get fixed? Even though setting cache to never might be a temporary work around, this is a major bug in the viewengine code, which will prohibit a lot of people (including us) from using any of this on our new websites.

marcind wrote Sep 6, 2012 at 4:25 PM

I do not have an exact date yet, but I am hoping that we will be able to ship a workaround next week.

chandrac wrote Sep 9, 2012 at 12:59 AM

This is a serious blocker. could folks from the aspnet team please respond to this bug with their feedback?

chandrac wrote Sep 9, 2012 at 1:02 AM

Just saw Marcin's comment about the ETA. Thank you very much. Looking forward to the fix.

marcind wrote Sep 10, 2012 at 5:23 PM

Quick update: the fix is undergoing testing right now. Thanks for the patience.

marcind wrote Sep 14, 2012 at 11:37 PM

Hi everyone. I'm very happy to announce that a workaround package that fixes this problem for MVC 4 has been updated to NuGet: http://nuget.org/packages/Microsoft.AspNet.Mvc.FixedDisplayModes. For most apps it should be sufficient to simply install this package into your project and the fix should be applied. For a small set of projects that customize the list of registered view engines (for example, in Global.asax) you should make sure that you are referencing Microsoft.Web.Mvc.FixedRazorViewEngine or Microsoft.Web.Mvc.FixedWebFormViewEngine, instead of the classes that ship in the core runtime binary.

marcind wrote Sep 15, 2012 at 12:21 AM

This has been fixed for the next version of MVC in http://aspnetwebstack.codeplex.com/SourceControl/changeset/af32aa809248.

ibneadam wrote Sep 15, 2012 at 2:51 AM

Thank you so much for that! We really appreciate your efforts!

jeffyjones wrote Sep 15, 2012 at 3:16 AM

When will the "next version" be a supported update? I know you guys are fairly agile and such, but for the purpose of communicating requirements to customers and other groups, it's helpful to say, "Oh, you need MVC v4.x."

marcind wrote Sep 15, 2012 at 6:09 AM

@jeffyjones I don't think we have a clear schedule when we will be releasing the next version of the runtime. However, you can apply this package: http://nuget.org/packages/Microsoft.AspNet.Mvc.FixedDisplayModes to your existing MVC 4 project to fix the problem in the meantime. Hope this helps.

jeffyjones wrote Sep 15, 2012 at 3:17 PM

You guys should really have a discussion about that. Big bang releases don't mesh well with open source with a public repo. I imagine you're hitting iterations at a regular interval... I would pick a regular release interval on top of that.

msmolka wrote Oct 3, 2012 at 12:37 PM

To use the fixed display modes, what else I should do? I've installed it but the only thing I can see is this added as reference. I cannot find anything related in code. No binding redirection nothing? So how to use it?

marcind wrote Oct 3, 2012 at 4:30 PM

@jeffyjones The team is working on a roadmap/schedule right now. We'll be posting something soon.

@msmolka In most cases you just need to install the package and it will automatically fix the display modes (you can check this by inspecting ViewEngines.Engines collection [http://msdn.microsoft.com/en-us/library/system.web.mvc.viewengines.engines.aspx])

mudithaa wrote Dec 19, 2012 at 12:35 PM

I'm still struggling to get it working. I have installed jquery mvc mobile and I can see the correct mobile view on an iphone but not on android. I have applied the fix as well.

jeffyjones wrote Dec 19, 2012 at 1:29 PM

jQuery Mobile has nothing to do with this bug. Your problem has nothing to do with it either. Either your Android phone is self identifying as something else, or you need to research .browser files and add one to make the phone appear as a mobile device.

AlexKaramushko wrote Dec 20, 2012 at 3:53 PM

Hello All,
Is this fix in MVC 4 version 4.0.20714.0 (RTM distributed thru MVC download)
Also, Nuget distribution has 4.0.20710.0 (Nuget package thru Visual Studio) (minor difference 714 vs. 710)
Is this fix in Nuget package?
We started seeing the original issue after deploying in production and running 3-4 weeks.
Our previous solution was to recycle Application Pool every 15 minutes - so cache is updated.
After upgrading to MVC4 RTM we reset Application Pool recycling back to default 1740 minutes and it seemed OK for a while (3-4 weeks) but now is back intermittently.
Meaning - instead of mobile view user can get desktop for some time, but unfortunately/fortunately it goes back to mobile (normal behavior). Application Pool manual recycle solves the issue. there are no errors in Event log (from .net or MVc or any othe issues). We are monitoring this issue since mid-summer so we can exclude pretty much our implementation.
Most likely we would need to go back to recycle Application Pool every 15 minutes.
Our server environment is IIS6 and OS is 2003, we will try to go with 2008/2012 and (corresponding IIS) as soon as we can, mostlikley in mid 2013 in mean time any information will be appreciated.
Thanks,
--Alex

pmourfield wrote Dec 21, 2012 at 8:56 PM

I'm seeing this issue as well. I see that it is fixed vNext but is there and ETA?

mcneany1 wrote Feb 21, 2013 at 12:50 PM

This may not work for others, but I have an alternate solution as well. What I did was set up two sites in IIS, one for my desktop version and the other to my mobile version. They point to the same directory in IIS. I then have both the mobile and web URLs stored in the web.config in a custom config section (you can use AppSettings if you want). Then I have an action filter attribute on each of my pages called 'BrowserRedirectPolicyAttribute'. I then have code to detect whether or not the URL is mobile. If so, I set the overridden browser to BrowserOverride.Mobile and allow the site to continue to the appropriate action. This will show the mobile site. If it's not the mobile site, it does a little more detection to determine whether it's a mobile device. If it is, then it shows a mobile "Redirect" page that allows the user to choose which site they want to go to.

This was my set up in my staging environment and I never ran into this issue. However, our production environment was set up as the same site in IIS with different host headers (I'll be having a talk with my server administrator) and I experienced this issue. I am going to separate the sites in IIS and I expect this issue to go away.

The reason why this works is because there is no shared cache between the two, as long as they are set up to use different app pools.

gkrilov wrote Mar 9, 2013 at 2:41 AM

Hey guys I see that is says fixed up to.. is there a fix available to download somewhere is will it be in the next version or MVC? does anyone know?

We are currently in the QA stages of one website that has web/mobile views and we are experiencing the exact same caching problem..

it runs fine and then suddenly mobiles start using the web views.. :(

jeffyjones wrote Mar 9, 2013 at 3:30 AM

gkrilov wrote Mar 12, 2013 at 1:37 PM

Thanks jeffy.. so apparently our project was branched off something that was created prior to the fall 2012 update seeing as it didn't have the display fix by default..

Applying the fix manually worked, thanks!

Shpaidaman wrote Nov 26, 2013 at 12:40 AM

Ran into this bug when we deployed our mobile site on top of MVC4.

This is easily the kind of bug that could only show up in a production environment, and Microsoft's answer is just "Upgrade to MVC 5"? That really isn't a great answer.

Not to mention that upgrading to MVC 5 requires .Net 4.5, another non-trivial upgrade for us.

jeffyjones wrote Nov 26, 2013 at 2:32 AM

The answer is not just upgrade... use the FixedDisplayModes package and you're all good.

Andreas12345678 wrote Apr 4 at 7:37 PM

When I installed the FixedDisplayModes package the it introduced:

EntrypointNotFoundException, An Exception of type System.EntryPointNotFoundException occured in System.Web.Mvc.dll but was not handled in user code.


public static IUnityContainer Initialise()
    {
        var container = BuildUnityContainer();

      // this is where the exception seems to have been thrown.
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        return container;
    }
Anybody know what this problem is about?