Handling SharePointServiceLocator failures due to caching
The SharePointServiceLocator in the patterns and practices SharePoint library implements a dependency injection pattern for decoupling service providers from service consumers. It achieves this by mapping an interface to an implementing class. The consumer of the implementation only has a dependency on the interface, and the service locator has the responsiblity to construct an instance of a concrete type that implments the interface. This helps with unit testing (by making it easy to substitute a test implementation), and promotes loose coupling of code since a service consumer doesn't have to have any knowledge of the provider of that service. In more sophisticated cases the mapping can be looked up by key, and the serivce locator can provide different implementing classes depending upon the context. An additional feature is that the SharePointSerivceLocator will allow type mappings to be defined at a Farm or Site collection scope. A site collection scope will override a farm mapping. As a result different behavior for a specific site collection can be provided for situations where the business requires it for the service. For more inforation see The SharePoint Service Locator.
Since the service locator is called frequently to load types, the information mapping an interface to a type is cached in memory by the locator for both the farm and site collection level mappings. By default the site collection mappings are purged every minute (this is configurable) and the type mappings for the farm are not purged from the cache. If new type mappings are defined, then IIS must be reset to flush this cache. However the service locator also has a method, Reset, that will cause the cached values to be reset. We wanted to get a feature into the service locator to purge the cache and re-lookup the cached values when a type mapping can't be located, but unfortunately we ran out of time to get this feature implemented. The good news is that its relatively straightforward to create a wrapper class that will do this for you.
public class ServiceLocatorWrapper{ public static TService GetInstance<TService>() { TService instance; try { instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>(); } catch (ActivationException e) { LogFailure<TService>(e); SharePointServiceLocator.Reset(); instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>(); } return instance; }`` private static void LogFailure<TService>(Exception e) { ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>(); logger.TraceToDeveloper(e, string.Format("Failed to load type {0}, flushing service locator and retrying", typeof(TService).Name)); }}
The wrapper class implements a generic method for getting a type instance. The generic method just passes this through to service locator to create the instance. The wrapper also catches an ActivationException when thrown, which is thrown by the service locator if it can't find a type mapping. When this exception occurs, the wrapper invokes the Reset method which purges the cached settings, the re-attempts creating the instance. Since the mappings have been purged the settings are re-read from configuration and cached. Therefore any added type mappings will be picked up.
I'd highly recommend using this wrapper approach to improve robustness if you are using the SharePointServiceLocator.
The wrapper class can add a few additonal similar methods to handle all of the different ways service locator can create instances as the following code demonstrates.
public class ServiceLocatorWrapper
{
public static TService GetInstance<TService>()
{
TService instance;
try
{
instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>();
}
catch (ActivationException e)
{
LogFailure<TService>(e);
SharePointServiceLocator.Reset();
instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>();
}
return instance;
}
public static TService GetInstance<TService>(string key)
{
TService instance;
try
{
instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>(key);
}
catch (ActivationException e)
{
LogFailure<TService>(e);
SharePointServiceLocator.Reset();
instance = SharePointServiceLocator.GetCurrent().GetInstance<TService>(key);
}
return instance;
}
public static TService GetInstance<TService>(SPSite site)
{
TService instance;
try
{
instance = SharePointServiceLocator.GetCurrent(site).GetInstance<TService>();
}
catch (ActivationException e)
{
LogFailure<TService>(e);
SharePointServiceLocator.Reset();
instance = SharePointServiceLocator.GetCurrent(site).GetInstance<TService>();
}
return instance;
}
public static TService GetInstance<TService>(SPSite site, string key)
{
TService instance;
try
{
instance = SharePointServiceLocator.GetCurrent(site).GetInstance<TService>(key);
}
catch (ActivationException e)
{
LogFailure<TService>(e);
SharePointServiceLocator.Reset();
instance = SharePointServiceLocator.GetCurrent(site).GetInstance<TService>(key);
}
return instance;
}
private static void LogFailure<TService>(Exception e)
{
ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
logger.TraceToDeveloper(e, string.Format("Failed to load type {0}, flushing service locator and retrying", typeof(TService).Name));
}
}