Display Mode Bug in ASP.NET MVC 4 and Workaround

        Introduction:


                    I have found an important bug in ASP.NET forums regarding the new display mode feature in ASP.NET MVC 4. If you are using ASP.NET MVC 4 Display Mode feature in your application or plan to use this feature in your application then it will be very important for you to be familiar with this bug, otherwise your application will not work as you expect. I and Rick Anderson have created some workaround for this bug. So, in this article, I will explain you the bug and also let you know that how to workaround this bug in ASP.NET MVC 4.


        Description:


                    For understanding this bug, create a new ASP.NET MVC 4 application. Then add a desktop and a mobile view(if you are new, then see this link). Now, just run your application and switch between the mobile and the desktop view. Then only use the desktop view for couple of minutes. Then switch to the mobile view, you will see the desktop view instead of the mobile view. This will clearly signal a bug in ASP.NET MVC 4. The reason of this bug is that both of your desktop and mobile view's location are cached(assuming that you have only 2 display modes) when your application access a view(desktop or mobile) first time. Unfortunately, the cache of view locations is sliding. So, if your appliction uses destop view most of time, then your mobile view location will expire sooner than the desktop view location. So, when your application required the mobile view, it will check the mobile view location from cache. Since the mobile view location cache is expired, the application will not able to find this and will use the desktop view location. See, this thread for more detail about this bug. You can easily work around this bug by using this code provided by Rick.                     


    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.
    }

                    This will workaround this bug. But the problem with this workaround is that the view location in cache will never expire, so it will increase the load of your server. Another issue with this workaround is that you need to restart the application pool when you add or delete a view. I have created another workaround of this issue. The workaround simply make all the view location's time period sync in cache. In our example, if the mobile view location in the cache is removed due to cache time expiration then it will remove the desktop view location from the cache as well. So, all the view location time period remain sync in cache. Here is the work around, 


    public class MyDefaultViewLocationCache : DefaultViewLocationCache, IViewLocationCache
    {
        public MyDefaultViewLocationCache(TimeSpan timeSpan): base(timeSpan)
        {
        }
        public MyDefaultViewLocationCache()
            : base()
        {
        }
        public new string GetViewLocation(HttpContextBase httpContext, string key)
        {
            var location = base.GetViewLocation(httpContext, key);
            if (location == null)
            {
                var cache = httpContext.Cache;
                RemoveAllCacheStartWith(key, cache);
            }
            return location;
        }
        private static void RemoveAllCacheStartWith(string key, System.Web.Caching.Cache cache)
        {
            var keyWithoutDisplayMode = key.Substring(0, key.Substring(0, key.Length - 1).LastIndexOf(':') + 1);
            var items = new List<string>();
            var enumerator = cache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var _key = enumerator.Key.ToString();
                if (_key.StartsWith(keyWithoutDisplayMode))
                {
                    items.Add(_key);
                }
            }
            foreach (string item in items)
            {
                cache.Remove(item);
            }
        }
        public new void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
        {
            base.InsertViewLocation(httpContext, key, virtualPath);
        }
    }


                    Just register this workaround inside Application_Start event in global.asax file,


    ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache =
                new MyDefaultViewLocationCache();


 


                    Update(15 Sep 2012): Microsoft have published a workaround package for this bug.

        Summary:


                    In this article, I explained you a very important bug in ASP.NET MVC 4. I have also showed you some work around. Hopefully you will enjoy this article too.



No Comments