How to make the cache to refresh when the XML is changed?

1.1k Views Asked by At

I am using MvcSiteMapProvider 4.6.3, MVC 4. Using DI to config the Sitemap.

this.For<System.Runtime.Caching.ObjectCache>()
    .Use(s => System.Runtime.Caching.MemoryCache.Default);

this.For(typeof (ICacheProvider<>)).Use(typeof (RuntimeCacheProvider<>));
var rootCacheDependency = this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
    .Ctor<string>("fileName").Is(rootFileName);

var rootCacheDetails = this.For<ICacheDetails>().Use<CacheDetails>()
    .Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
    .Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
    .Ctor<ICacheDependency>().Is(rootCacheDependency);

var cacheDetails = new List<SmartInstance<CacheDetails>>();
var xmlSources = new List<SmartInstance<FileXmlSource>>(); 

How to make it automatically update the cache when the Sitemap xml is updated?

I am upgrading MvcSitemapProvider from v3 to v4. In version 3, it seems the sitemap is automatically refreshed.

I did set the cache expiration time to be 5 min, is this causing problem?

TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);

var rootCacheDetails = this.For<ICacheDetails>().Use<CacheDetails>()
            .Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
            .Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
            .Ctor<ICacheDependency>().Is(rootCacheDependency);

UPDATE

When I change the sitemap xml file the cache is not updated till 5 min the cache expire. I am using multiple sitemap xml files.

        var sitmapPath = HostingEnvironment.MapPath("~/Sitemaps");
        var sitemaps = new List<string>();
        if (sitmapPath != null)
        {
            sitemaps.AddRange(Directory.GetFiles(sitmapPath, "*.sitemap"));
        }

        foreach (var sitemapFileName in sitemaps)
        {
            var cacheDependencie = 
                this.For<ICacheDependency>()
                .Use<RuntimeFileCacheDependency>()
                .Ctor<string>("fileName")
                .Is(sitemapFileName);

            cacheDetails.Add(this.For<ICacheDetails>().Use<CacheDetails>()
            .Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
            .Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
            .Ctor<ICacheDependency>().Is(cacheDependencie));

            xmlSources.Add(this.For<IXmlSource>().Use<FileXmlSource>()
                .Ctor<string>("fileName").Is(sitemapFileName));
        }

Will this be the reason it's not working?

1

There are 1 best solutions below

0
On BEST ANSWER

I don't see a problem with the code you posted. However, it is the RuntimeFileCacheDependency that will make it reload when the XML is changed.

The RuntimeFileCacheDependency expects the fileName argument to be an absolute path. So you must convert it using HostingEnvironment.MapPath before providing it to the RuntimeFileCacheDependency constructor.

var rootFileName = HostingEnvironment.MapPath("~/root.sitemap");

Response to Your Update

The purpose of the cacheDetails object is to specify the caching policy for a single SiteMapBuilderSet instance. If you look further down in the (original) DI module, notice that the variable is passed to the constructor of this class.

// Configure the builder sets
this.For<ISiteMapBuilderSetStrategy>().Use<SiteMapBuilderSetStrategy>()
    .EnumerableOf<ISiteMapBuilderSet>().Contains(x =>
    {
        x.Type<SiteMapBuilderSet>()
            .Ctor<string>("instanceName").Is("default")
            .Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
            .Ctor<bool>("enableLocalization").Is(enableLocalization)
            .Ctor<bool>("visibilityAffectsDescendants").Is(visibilityAffectsDescendants)
            .Ctor<bool>("useTitleIfDescriptionNotProvided").Is(useTitleIfDescriptionNotProvided)
            .Ctor<ISiteMapBuilder>().Is(builder)
            .Ctor<ICacheDetails>().Is(cacheDetails); // <- caching specified here explicitly.
    });

This is what is used to expire the cache, but it is a completely separate mechanism from the part that specifies to use multiple files to build a SiteMap:

// Register the sitemap node providers
var siteMapNodeProvider = this.For<ISiteMapNodeProvider>().Use<CompositeSiteMapNodeProvider>()
    .EnumerableOf<ISiteMapNodeProvider>().Contains(x =>
    {
        x.Type<XmlSiteMapNodeProvider>()
            .Ctor<bool>("includeRootNode").Is(true)
            .Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
            .Ctor<IXmlSource>().Is(rootXmlSource);
        
        // NOTE: Each additional XmlSiteMapNodeProvider instance for the same SiteMap instance must
        // specify includeRootNode as "false"
        x.Type<XmlSiteMapNodeProvider>()
            .Ctor<bool>("includeRootNode").Is(false)
            .Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
            .Ctor<IXmlSource>().Is(childXmlSource1);
        x.Type<XmlSiteMapNodeProvider>()
            .Ctor<bool>("includeRootNode").Is(false)
            .Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
            .Ctor<IXmlSource>().Is(childXmlSource2);
        
        // Add additional XmlSiteMapNodeProviders here (with includeRootNode as "false")...
            
        // You only need this if you intend to use MvcSiteMapNodeAttribute in your application
        x.Type<ReflectionSiteMapNodeProvider>()
            .Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
            .Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
    });

// Register the sitemap builders
var builder = this.For<ISiteMapBuilder>().Use<SiteMapBuilder>()
    .Ctor<ISiteMapNodeProvider>().Is(siteMapNodeProvider);

This is how to specify multiple XML files for a single SiteMap, but it is also possible to make each XML file into its own SiteMap instance by passing each instance of XmlSiteMapNodeProvider to a separate SiteMapBuilder and a separate SiteMapBuilderSet as described in Multiple SiteMaps in One Application.

IMPORTANT: For multiple XML files to work on a single SiteMap instance, you must specify the same key for the root node of each SiteMap as shown at the bottom of this answer. But you cannot specify a node representing the same controller action in more than one XML file (other than the root node).

If you need more flexibility than this, I would suggest implementing your own XmlSiteMapNodeProvider or abandoning the idea of using XML altogether, since using ISiteMapNodeProvider or IDynamicNodeProvider is much more flexible.

Now, back to the caching. If you are indeed using multiple XML files in the same SiteMap instance, you need to use a RuntimeCompositeCacheDependency so each of the files will be considered a dependency for the same cache, but you must use a single instance of CacheDetails.

var rootCacheDependency =
    this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
        .Ctor<string>("fileName").Is(rootAbsoluteFileName);

var childCacheDependency1 =
    this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
        .Ctor<string>("fileName").Is(childAbsoluteFileName1);
        
var childCacheDependency2 =
    this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
        .Ctor<string>("fileName").Is(childAbsoluteFileName2);


var cacheDependency =
    this.For<ICacheDependency>().Use<RuntimeCompositeCacheDependency>()
        .Ctor<ICacheDependency[]>().Is(new ICacheDependency[] 
        { 
            (ICacheDependency)rootCacheDependency, 
            (ICacheDependency)childCacheDependency1,
            (ICacheDependency)childCacheDependency2             
        });


var cacheDetails =
    this.For<ICacheDetails>().Use<CacheDetails>()
        .Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
        .Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
        .Ctor<ICacheDependency>().Is(cacheDependency);