Good Day
I am running a small web garden with StateSession cache. The obvious problem is that when you want to clear the cache, the cache is only cleared on the workerprocess that is handling the call.
In a specific AppPool there will be at least one User Interface Application and one Web Service Application. The Web Services do the majority of the caching, so they have methods to clear their cache.
What I would like to do is create a method that will get input (the AppPool Name) and then it will iterate through the current w3wp processes and get the required pools back. That can be done and I have methods of getting that information.
Where I am getting stuck is I have the AppPool Name as well as the Applications that are running in that Worker Process, but I am lost as to how to use that information to execute the “ClearCache()” method in a specific Web Service application.
I am sure that it can be done with Reflection, but I think I am missing something obvious.
Currently I am just using a console application to get something that works. This can then be moved to a better solution in due time.
Please advise if there is a way to use to current information to execute the required method.
Below is the test application as it stands at the moment.
Thank you.
Jaco
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Management;
using System.DirectoryServices;
using System.Collections;
namespace CacheCleaner
{
class Program
{
const string defaultAppPoolMetabasePath = "IIS://localhost/W3SVC/AppPools/DefaultAppPool";
static void Main(string[] args)
{
//show a list of all the processes.
//GetListOfProcesses();
//Kill all the worker processes
//KillW3WP();
//Refresh the application pool
//RefreshAppPool(defaultAppPoolMetabasePath);
//get a list of all the Applications in the DefaultAppPool
//GetApplicationPoolInformation(defaultAppPoolMetabasePath);
//get the apps in the apppool
EnumerateApplicationsInPool(defaultAppPoolMetabasePath);
Console.WriteLine("\n\nEnd of process. Press Enter.");
Console.ReadLine();
}
/// <summary>
/// Show all the processes that are currently running on the system
/// </summary>
static void GetListOfProcesses()
{
foreach (Process p in Process.GetProcesses())
{
Console.WriteLine("{0} {1}", p.Id, p.ProcessName);
}
}
/// <summary>
/// This will kill ALL the worker processes.
/// Pretty much an iisreset call.
/// Not good for Production sites :)
/// </summary>
static void KillW3WP()
{
foreach (Process p in Process.GetProcessesByName("w3wp"))
{
Console.WriteLine("Closing " + p.ProcessName);
p.Kill();
p.WaitForExit();
if (p.HasExited)
{
Console.WriteLine("Closed");
}
else
{
Console.WriteLine("NOT Terminated");
}
}
}
/// <summary>
/// Refresh a specific application pool
/// </summary>
/// <param name="metabasePath"></param>
static void RefreshAppPool(string metabasePath)
{
using (DirectoryEntry applicationPool = new DirectoryEntry(metabasePath))
{
applicationPool.Invoke("Recycle");
}
}
/// <summary>
/// Get the Name of the worker process (AppPool Name)
/// </summary>
/// <param name="metabasePath"></param>
static void GetApplicationPoolInformation(string metabasePath)
{
var scope = new ManagementScope(String.Format(@"\\{0}\root\cimv2", Environment.MachineName));
var query = new SelectQuery("SELECT * FROM Win32_Process where Name = 'w3wp.exe'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject process in searcher.Get())
{
//get just the name of the application
var startIndex = process["CommandLine"].ToString().IndexOf("-ap ") + 5; //remove the -ap as well as the space and the "
var endIndex = process["CommandLine"].ToString().IndexOf("-", startIndex) - 2; //remove the closing "
var appPoolName = process["CommandLine"].ToString().Substring(startIndex, endIndex - startIndex);
var pid = process["ProcessId"].ToString();
Console.WriteLine("{0} - {1}", pid, appPoolName);
}
}
}
/// <summary>
/// Get the applications in the pool
/// From http://msdn.microsoft.com/en-us/library/ms524452%28v=vs.90%29.aspx
/// </summary>
/// <param name="metabasePath"></param>
static void EnumerateApplicationsInPool(string metabasePath)
{
Console.WriteLine("\nEnumerating applications for the {0} pool:", metabasePath);
try
{
DirectoryEntry entry = new DirectoryEntry(metabasePath);
if (entry.SchemaClassName == "IIsApplicationPool")
{
object[] param;
param = (object[])entry.Invoke("EnumAppsInPool", null);
foreach (string s in param)
{
Console.WriteLine("{0}", s);
//I am sure that I should be able to use this application name
//with Reflection to be able to execute a method...
}
Console.WriteLine("Done.");
}
else
Console.WriteLine("Failed in EnumerateApplicationsInPool; {0} is not an app pool", metabasePath);
}
catch (Exception ex)
{
Console.WriteLine("Failed in EnumerateApplicationsInPool with the following exception: \n{0}", ex);
}
}
}
}
There’s a few ways to approach this, some bad, some good.
Bounce each worker process. This is pretty effective and will force a cache reload but will result in a very poor experience for end users because existing requests will be stopped in their tracks (if you’re updating a database without any transaction protection then you could see your data go inconsistent). You’ll also see a loss of session state if you’re using
InProcstate management.Recycle each pool – better than bouncing the pool because existing requests will still be permitted to complete. However you’ll still see a loss of session state if you’re using
InProcstate management.Use a Cache Dependency – many caches are populated from a SQL Database. ASP.NET supports a feature called
SqlCacheDependency. You can use this to refresh your cached data should the source data in the database change.Use a file-based Cache Dependency – if your cache data is sourced from (for example) an XML data file then you can cache that data and create a cache dependency on this file. When the file is updated the cache is invalidated and you reload the data. You could automate this with a
FileSystemWatcherbecause the CacheDependency class itself doesn’t provide any change notification events and you’d need to checkCacheDependency.HasChangedon every request.With regard to calling
ClearCache()directly on the cache object in each worker process, there is no direct way to do that using reflection. You might be able to do this using the .NET Debugging API’s and inject some code to clear the cache but you’re talking about walking up to a process, attaching to it and pretending to be a debugger. I wouldn’t really fancy writing that myself when there are other mechanisms such as Cache Dependencies available.