locked
Using .NET MVC 4.0 VirtualPathProviders in a web farm scenario RRS feed

  • Question

  • User906742921 posted

    We have an application that relies heavily on a virtual file system that extends the .NET System.Web.Hosting.VirtualPathProvider architecture. We use chaining to search through... (A)contentresourceprovider (extends virtualpathprovider) (B)assemblyresourceprovider (ditto) (C)physical file system

    The vpp's are registered on app initialisation like so...

    HostingEnvironment.RegisterVirtualPathProvider(new AssemblyResourceProvider()); HostingEnvironment.RegisterVirtualPathProvider(new ContentResourceProvider());

    This problem is relative to the ContentResourceProvider (see below)

    using System;
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    using System.Web;
    using System.Web.Caching;
    
    namespace Custom.Web.Mvc
    {
        public class ContentResourceProvider : System.Web.Hosting.VirtualPathProvider
        {
            #region Methods 
    
            /// <summary>
            /// Gets a value that indicates whether a file exists in the virtual file system.
            /// </summary>
            /// <param name="virtualPath">The path to the virtual file.</param>
            /// <returns>
            /// true if the file exists in the virtual file system; otherwise, false.
            /// </returns>
            public override bool FileExists(string virtualPath)
            {
                var result = ContentResourceProvider.IsContentResourcePath(virtualPath) ? ((ContentResourceVirtualFile)this.GetFile(virtualPath)).Exists : Previous.FileExists(virtualPath);
                return result;
            }
    
            /// <summary>
            /// Creates a cache dependency based on the specified virtual paths.
            /// </summary>
            /// <param name="virtualPath">The path to the primary virtual resource.</param>
            /// <param name="virtualPathDependencies">An array of paths to other resources required by the primary virtual resource.</param>
            /// <param name="utcStart">The UTC time at which the virtual resources were read.</param>
            /// <returns>
            /// A <see cref="T:System.Web.Caching.CacheDependency"/> object for the specified virtual resources.
            /// </returns>
            public override CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
            {
                var result = ContentResourceProvider.IsContentResourcePath(virtualPath) ? new ContentResourceCacheDependency(virtualPath) : Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
                return result;
            }
    
            /// <summary>
            /// Gets a virtual file from the virtual file system.
            /// </summary>
            /// <param name="virtualPath">The path to the virtual file.</param>
            /// <returns>
            /// A descendent of the <see cref="T:System.Web.Hosting.VirtualFile"/> class that represents a file in the virtual file system.
            /// </returns>
            public override System.Web.Hosting.VirtualFile GetFile(string virtualPath)
            {
                var result = ContentResourceProvider.IsContentResourcePath(virtualPath) ? new ContentResourceVirtualFile(virtualPath) : Previous.GetFile(virtualPath);
                return result;
            }
    
            /// <summary>
            /// Returns a hash of the specified virtual paths.
            /// </summary>
            /// <param name="virtualPath">The path to the primary virtual resource.</param>
            /// <param name="virtualPathDependencies">An array of paths to other virtual resources required by the primary virtual resource.</param>
            /// <returns>A hash of the specified virtual paths.</returns>
            public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
            {
                var result = base.GetFileHash(virtualPath, virtualPathDependencies);
                return result;
            }
    
            /// <summary>
            /// Determines whether [is content resource path] [the specified virtual path].
            /// </summary>
            /// <param name="virtualPath">The virtual path.</param>
            /// <returns>
            /// 	<c>true</c> if [is content resource path] [the specified virtual path]; otherwise, <c>false</c>.
            /// </returns>
            public static bool IsContentResourcePath(string virtualPath)
            {
                var pattern = @"~?/\(ContentManagementResource\)";
                var result = Regex.IsMatch(virtualPath, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
                return result;
            }
    
            #endregion Methods 
        }
    }
    

    Basically this all works great and whenever we update content via our cms tools the virtual files and removed from cache and reopened/compiled into razor views. All good so far.... However we recently deployed this to a server farm and it seems the whole innate System.Web.Hosting virtual file provider thingy does not scale (obviously) it's relative to the particular server that stores the virtual files in memory etc. This means that if User A makes a change to a razor view via cms tools and the operation is carried out on Server A, user B making another change on Server B will then provide undesirable results despite the fact the content is coming from the same Database. This is because the virtual files systems caching the razor views are unique to the web server they are running on, likewise the cachedepedencies are only triggered on the relative server.

    I've tried using the distributed cache packages like AppFabric, Memcached, NCache. These aren't adequate because they depend on you manually writng code that inserts Serialized objects into the distributed cache. Firstly the .NET vpp memory stuff is effectively managed under the hood with strict design patterns enforced, also the CacheDependency class isn't serializable.

    Having done many hours of research I cannot find a solution online.

    Surely the whole .NET MVC 4.0 VirtualPathProvider/CacheDependency systems architecture must have a way of scaling across more then one server?

    I guess from my understanding of this matter I'm asking is there a way to scale the System.Web.Hosting cache memory across more than one server so that virtual content and Cache Dependencies attached to these files work as intended?

    Any help would be greatly appreciated.



    Tuesday, June 18, 2013 12:02 PM

All replies

  • User1779161005 posted

    If you're using SqlServer then look into its Sql Dependency API (and ASP.NET's SqlCacheDependency) --  this will raise a notification to the client app when the DB values have changed.

    Tuesday, June 18, 2013 12:30 PM
  • User906742921 posted

    We are using SisoDb which is a kind of noSQL DB framework.

    We also modify our filenames as we're using a multi-tenanted system so we have to enforce a kind of custom CacheDependecy methodology.

    These two things combined mean that the unfortunately the Sql Dependency API is no use to us.

    However, this whole framework is supposed to be extensible for any virtual file system type.

    Tuesday, June 18, 2013 12:37 PM
  • User-488622176 posted

    Am I reading correctly : are you using a noSQL DB framework with local databases to store data, in a webfarm environment?

    Friday, June 21, 2013 10:38 AM
  • User906742921 posted

    There is one SQL server that is used centrally in a web-farm.

    This SQL Server hosts our CMS databse that contains versions of virtual file content.

    SisoDB is a non-relational DB that serves versioned instances of virtual file content.

    We a hooking into this with our VirtualFileProvider (ContentResourceProvider).

    For more info about SisoDb see here http://www.sisodb.com/wiki/

    Wednesday, June 26, 2013 6:48 AM
  • User-488622176 posted

    If you have a tight relation between your local SisoDB instance (on server A) and the database data (central sql instance), you'll have an architectural design issue. The relational data can be alterned by other means then the SidoDB instance on a specific server...

    Friday, June 28, 2013 7:56 AM