Of late I have been using “service locator” similar to the below:
//=== class ServiceLocator ====
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Xml;
namespace PerfectStorm
{
/// <summary>
/// This is used to allow the creation of a class to be decoupled from the implementation.
/// The client only needs to know the name and have a supported interface or base class.
///
/// The only restriction this imposes on the created class is that it must have an empty
/// constructor.
///
/// The registry may be populated automatically from the appropriate config file or the
/// client may populate it itself. By default the full name of the class is used, but
/// if client-populated then any unique string identifier will do.
///
/// The beauty of this clarity of split is that the caller may not know any of the
/// implementation details. This ensures that the implementation can be replaced only
/// requiring a configuration setting.
///
/// </summary>
public static class ServiceLocator
{
static Dictionary<string, Type> _dict = new Dictionary<string, Type>();
// This will be called before the first method on the type.
static ServiceLocator()
{
XmlNode configNode = (XmlNode)ConfigurationManager.GetSection(“PerfectStorm.ServiceLocator”);
if (configNode != null)
{
foreach (XmlNode node in configNode.SelectNodes(“//PerfectStorm.ServiceLocator/assembly”))
{
LoadAssembly(node.Attributes[“name”].InnerText);
}
}
}
/// <summary>
///
/// </summary>
/// <param name=”Name”></param>
public static void LoadAssembly(string Name)
{
Assembly a = Assembly.Load(“Name”);
Type[] types = a.GetTypes();
foreach (Type t in types)
{
if (!t.IsAbstract)
{
// Only add if can be constructed.
if (t.GetConstructor(System.Type.EmptyTypes) != null)
{
AddType(t.FullName, t);
}
}
}
}
/// <summary>
/// Creates an instance of a named class that conforms to the supplied interface or base type.
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”name”></param>
/// <returns></returns>
public static T CreateInstance<T>(string name)
{
Type t = null;
if (_dict.ContainsKey(name))
{
t = _dict[name];
}
else
{
throw new Exception(string.Format(“ServiceLocator is Unable to create {0}”, name));
}
return (T)Activator.CreateInstance(t);
}
/// <summary>
///
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <remarks>Can only register classes with parameterless constructors.</remarks>
public static void Register<T>() where T : new()
{
Type type = typeof(T);
AddType(type.FullName, type);
}
/// <summary>
///
/// </summary>
/// <param name=”Name”></param>
/// <param name=”T”></param>
private static void AddType(string Name, Type T)
{
if (_dict.ContainsKey(Name))
{
_dict[Name] = T;
}
else
{
_dict.Add(Name, T);
}
}
/// <summary>
/// Empties the registry.
/// </summary>
/// <remarks>This has been included to assist unit testing.</remarks>
public static void Clear()
{
_dict.Clear();
}
}
}
This allows applications to load an assembly based upon config.
You will also need the Trivial section handler:
using System;
using System.Configuration;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
namespace PerfectStorm
{
/// <summary>
/// This allows the config section to be read as an XML node.
/// </summary>
/// <remarks>
/// This is identical code to that used in PerfectStorm.CodeGenLibrary but I don’t want to cause
/// a dependency between the two.
/// </remarks>
public class ServiceLocatorConfig : IConfigurationSectionHandler
{
/// <summary>
///
/// </summary>
/// <param name=”parent”></param>
/// <param name=”configContext”></param>
/// <param name=”section”></param>
/// <returns></returns>
public object Create(
object parent,
object configContext,
System.Xml.XmlNode section)
{
// This was based upon an idea that I got from:
// (http://alt.pluralsight.com/wiki/default.aspx/Craig/XmlSerializerSectionHandler.html)
return section;
}
}
}
This permit usages based upon config such as:
<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<configSections>
<section name=”PerfectStorm.ServiceLocator” type=”PerfectStorm.ServiceLocatorConfig, PerfectStorm.ServiceLocator” />
</configSections>
<PerfectStorm.ServiceLocator>
<assembly name=”My fully qualified name” />
</PerfectStorm.ServiceLocator>
</configuration>
This permits the use:
IYourInterface svc = ServiceLocator.CreateInstance<IYourInterface>(“fully qualified name of class”);
This means that the caller and implementer only need agree on the interface and everything else can be setup in config.
This has allowed me to make rapid changes to the architecture of an application (in one case to break a cyclic dependency and in another to replace a set of pointless local WCF calls with a call to an interface. This has been easy to retrofit to an existing application – probably because we have been working through some narrow interfaces (that is almost all remote calls went through one of three interfaces). I have found it useful to move the interfaces into a common assembly providing the service interfaces.
The only limitation of the ServiceLocator is that the classes created require a parameterless constructor.
I have started looking at Winsor (from the Castle project) as an alternative.
The following is a usage sample of Winsor:
using System;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using TestLib;
namespace TestProject
{
class Program
{
static void Main(string[] args)
{
IWindsorContainer container = new WindsorContainer(new XmlInterpreter());
IExecutor service = container.Resolve<IExecutor>();
// Or to be specific IExecutor service = container.Resolve<IExecutor>(“test”);
service.Execute();
Console.ReadLine();
}
}
}
This was controlled by the following config file:
<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<configSections>
<section
name=”castle”
type=”Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor” />
</configSections>
<castle>
<components>
<component id=”test”
service=”TestLib.IExecutor, TestLib”
type=”TestLibImplementation.Executor2, TestLibImplementation” />
</components>
</castle>
</configuration>
There is no reason why you can’t alter Winsor to use reflection to get all types from an assembly (in fact Binsor frequently does this).
Now this is very similar to the above Service Locator (With the right id name the usage would be identical).
I have more investigation of Winsor to do…