Monday, March 4, 2013

Clearing ARR Cached Content Programmatically

If you are taking advantage of Application Request Routing to serve cached copies of your dynamic web pages, you may find it necessary to come up with some programmatic way to remove a cached page. In our particular case, we fully leverage caching to reduce load times on our pages generated almost entirely by data, but at the same time we need to immediately expire pages if a dynamic configuration change was made by, say, one of our site designers.

Here is a screenshot of where this is done in Internet Information Services Manager, but there is no PowerShell commandlet or other command line tool that can do it.



It took a bit of figuring out, but ultimately it is pretty straightforward to do in code. Here is a sample console program that will clear any URL that you choose. I implemented the "ALL" keyword to clear everything. You should be able to easily adapt it into a tool or program of your own.


using Microsoft.Web.Administration;
using System;

namespace IISCacheManager
{
  class Program
  {
    static void Main(string[] args)
    {
      if (args.Length != 1)
      {
        Console.WriteLine("Incorrect Usage: Specify the URL that you want to flush the cache for. To delete everything, specify 'ALL'.");
        Environment.Exit(1);
      }

      var url = args[0] == "ALL" ? string.Empty : args[0];

      var m = new ServerManager();
      var x = m.GetApplicationHostConfiguration().GetSection("system.webServer/diskCache");
      var method = x.Methods["FlushUrl"].CreateInstance();
      method.Input.SetAttributeValue("url", url);
      method.Execute();
      Console.WriteLine("Item flushed successfully.");
    }
  }
}


Note that in order for this to work, you need to elevate permissions to run as an Administrator or you'll get some nice ACCESS DENIED errors. You also need to reference Microsoft.Web.Administration from the GAC. I also believe it is included in the Windows SDK.

Hope this helps someone. Happy Programming!

9 comments:

teamtamer said...

Thanks for your article, Paul.

On my SEO friendly site, the urls for Default pages end in "/".

I've implemented your solution - FlushUrl *mostly* works as described. However, when I delete a landing page (i.e. one ending in '/') - it promptly deletes cache for the "folder", and every cached response nested in it.

I'd like to delete the page cached as /full or /full.gzip , on it's own, but leave the folder intact. (I can't afford for all those pages to go out of cache at once).

Is there some way to target jsut these pages?

Miles said...

Hi teamtamer,

I can't try this right now, but maybe you can. What happens if you try flushing the URL that points to your default document. For example, say default.aspx is your default document and you flush http://www.somesite.com/default.aspx? Does that then clear just the root page?

teamtamer said...

Hi Paul.

Thanks for your thoughts. I ended up rewriting the incoming url to append '?default' to pages with '/'.
We raised an MS case, their ARR gurus confirmed it's how most companies get around the problem.
The trick is to *not* let the '?default' creep back out into the world. In a tiered caching solution, it propogates into ?default&default= and other nasty variants ...

Finally (but worth mentioning): your code lacks a test for the return code.
The "FlushUrl" method returns a long, which (suitably hex-encoded) correctly echos the error you'd see if you'd attempted to delete the url through ARR's 'Delete Specific cached Object' dialog. If you run the code with Admin privilege (as you suggest), the cache appears to clear correctly, irrespective of errors returned. I suspect that the errors arise, not from the target url (mypage), but the search for, and handling of, a range of underlying file names - mypage.full, mypage.full.gzip ... - that may or may not be there.

The 'Delete Specific Cache Object' implementation seems a bit green to me, but wasn't improved in the 2.5+ releases. I imagine deleting the default page in SEO friendly URL's is a common pain point.
Perhaps offering a 'delete folder?' argument to the Delete Specific Cached Object dialog - instead of assuming I want to delete the folder when I flush a url ending in '/' - might save some angst. I really can't afford to delete 100,000 items from cache because I updated the home page.

Dan Cash (teamtamer)

Suggest enhancement(?):
.
.
var method = x.Methods["FlushUrl"].CreateInstance();
method.Input.SetAttributeValue("url", url);
method.Execute();
var result = (long)method.Output["errorCode"];
if (result == 0) {
Console.WriteLine("Item flushed successfully.");
}
else {
Console.WriteLine("FlushUrl completed with errorCode {0}", result.ToString("X")
// Possible results:
// 0x8007005 Access denied
// 0x8007002 File not found
// 0x8007003 The system cannot find the path specified - eg because you provided the wrong domain name.

Console.WriteLine("Item flushed successfully."); // because it probably did, regardless of the error reported. Unless it didn't.
}
.
.



Andrey Stepanov said...

Hi Paul,

Thanks your article - seems it's the only one of this topic.

Yet, I'm having an exception when I run your script:

Unhandled Exception: System.Runtime.InteropServices.COMException: The request is not supported. (Exception from HRESULT: 0x80070032)
at Microsoft.Web.Administration.Interop.IAppHostMethodInstance.Execute()
at Microsoft.Web.Administration.ConfigurationMethodInstance.Execute()
at ARRCacheCleaner.Program.Main(String[] args) in C:\Users\andreys\Documents\Visual Studio 2010\Projects\ARRCacheCleaner\Program.cs:line 25

Could please please advise why it's happening?

Thanks,
Andrey

Miles said...

Teamtamer,

Thanks for your follow-up. I'll update my original post with your findings. We're troubleshooting with MS some of the errors we (intermittently) get when deleting pages and will share the result with you when we get information back from them.

Andrey,

I can't say for sure why the tool isn't working. I am using ARR 2.5 and not the 3.0 beta. Also it is necessary to run the tool as an administrator. Can you see that those things match?

Andrey Stepanov said...

Hi Paul,

Seems the problem is that I use IIS version 7.0 but according to this MSDN article IAppHostMethod interface is only supported in version 7.5 and higher.

Piedone said...

I'm getting random access denied errors, though most of the time this works. Do you guys experience the same?

Miles said...

Piedone,

We had this happen on our server as well. In our case, we had two application pools running. The cache clear request is processed by both. One had permission to that folder and the other didn't. You can fix the permissions pretty easily, but once you do that you'll get a not found error because one app pool deletes it and the other then can't find it. So it is best to make it so there is just one app pool running.

Hope that helps, and thanks for the Stack Exchange link!

Piedone said...

Wow, I've just seen your reply by chance, thank you for the tips!

In the meantime I've found out that a cache entry can also be locked by an ongoing request or similar. See: http://serverfault.com/questions/341276/application-request-routing-delete-cache-impossible-when-traffic-on-website and http://serverfault.com/questions/360107/handle-lock-on-disk-cache-by-arr-module-how-release-it-without-recycle-the-appl

And BTW I also have another issue with ARR, it seems cache files are kept indefinitely: http://serverfault.com/questions/826357/application-request-routing-not-cleaning-up-cache-files