Home > Net >  ASP.Net MVC: How to distinguish between two pages with same virtual absolute path when removing one
ASP.Net MVC: How to distinguish between two pages with same virtual absolute path when removing one

Time:06-09

Let's say I have a multilingual website with 2 languages (EN and FR) with the following URLs:

englishwebsite.com
frenchwebsite.com

And let's say that I'm using output caching for the home page like such:

[OutputCache(Duration = 3600, VaryByCustom = "domain")]
public ActionResult HomePage(RenderModel model)
{
    [...]
}

And

public override string GetVaryByCustomString(HttpContext context, string arg)
{
    if (arg == "domain") {
        if (HttpContext.Current.Request.Url.Host == "frenchwebsite.com")
        {
            return "fr";
        }
        else
        {
            return "en";
        }
    }
    [...]
}

By using that override of GetVaryByCustomString, I'm able to have a different cached version for the 2 domains.

However, if a change is made to one of the homepages and I try to invalidate the caching version of that page, I run into a problem. If I try to use the method RemoveOutputCacheItem (MSDN Page), I need to pass the virtual absolute path which is going to be the same for the two homepages (ie. "/"). So, both cached pages will be invalidated even if only one of them has changed.

So my question would be :

Is there a way to invalidate the cached version of a page that has the same virtual absolute path than another page without invalidating both?

CodePudding user response:

Building further upon what you already know and have.

Since there's no public access to the cache keys set up by the OutputCacheAttribute - it's all internal and private, see source code at GitHub - we can't clear the cache via those ones.


As suggested and without too much rework of your current setup, you can make use of a separate OutputCacheProvider per domain.

To indicate the OutputCacheProvider that should be used for a request you need to override GetOutputCacheProviderName in global.asax.cs.
The below example matches the logic you already have.

public override string GetOutputCacheProviderName(HttpContext context)
{ 
    return HttpContext.Current.Request.Url.Host.Equals(
        "frenchwebsite.com", StringComparison.OrdinalIgnoreCase
        ) ? "fr" : "en";
}

Both named providers (and the default one) must be configured in web.config, specifying its type (namespace class and assembly).
In this simplified example, the en one is also the default.

<configuration>
  <system.web>    
    <caching>
      <outputCache defaultProvider="en" enableOutputCache="true">
        <providers>
          <add name="en"
            type="MyWebApp.Caching.CustomOutputCacheProvider, MyWebApp" />
          <add name="fr"
            type="MyWebApp.Caching.CustomOutputCacheProvider, MyWebApp" />
        </providers>
      </outputCache>
    </caching>
    <!-- Other settings ... -->
  </system.web>
  <!-- Other settings ... -->
<configuration>  

The above custom MyWebApp.Caching.CustomOutputCacheProvider - see code below - is a very basic one that just uses the Cache of ASP.NET. Since the same single (by default memory) cache is being used for both the en and fr content, the cache keys are being prefixed with the provider name.

Note that this is rather a proof of concept implementation, which you might need to fine-tune. A minimal test run showed that this can work.

namespace MyWebApp.Caching
{
    public class CustomOutputCacheProvider : OutputCacheProvider
    {
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            return HttpRuntime.Cache.Add(
                NormalizeKey(key), entry, null, utcExpiry, TimeSpan.Zero, CacheItemPriority.Normal, null
                );
        }

        public override object Get(string key) 
        { 
            return HttpRuntime.Cache.Get(NormalizeKey(key));
        }

        public override void Remove(string key)
        {
            HttpRuntime.Cache.Remove(NormalizeKey(key));
        }

        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            HttpRuntime.Cache.Insert(
                NormalizeKey(key), entry, null, utcExpiry, TimeSpan.Zero
                );
        }

        private string NormalizeKey(string key)
        {
            return $"{Name}::{key}";
        }
    }
} 

As there will be a separate cache per domain (via the prefixed cache keys), the VaryByCustom = "domain" can be omitted from the OutputCacheAttribute; just use e.g.

[OutputCache(Location = OutputCacheLocation.Server, Duration = 60)]  

You can of course apply a VaryByCustom for any other distinction you need to make.


In order to clear e.g. the en content of the home page, you pass the name of the corresponding provider as 2nd argument to the RemoveOutputCachItem method.

Response.RemoveOutputCacheItem("/", "en");

Alternatively you might set up a caching strategy yourself with well known and predicatable cache keys being used to both insert into and remove from the cache. But that looks like too much for what you ask for.

CodePudding user response:

If you configure different providers for each domain, you could use the RemoveOutputCacheItem(String, String) overload of the method.

I never did that before, but maybe it points into the right direction?

  • Related