|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction
In this article I will try to show how you can extend the Castle Windsor inversion of control
container to dynamically select a component based on the current user type. For example if we
were going to develop a web application that is used by B2C (Consumer), B2P (Partner) and B2E (Employee) users.
The B2C users has their profile and password in a local database, the B2P users profile can only be accessed via
an external third party web service and the B2E users are in an LDAP server. Background
Castle Windsor is a inversion of control container that allows you to configure your components and their dependencies in a xml configuration file.
What we wantIn Castle Windsor you can configure your components by using xml. So it would be good if you could declare the user type a component is ment for in the xml. for example:<castle>
<components>
<component id="b2e.user.repository" service="ExtendingWindsor.IUserRepository,ExtendingWindsor"
type="ExtendingWindsor.EmployeeUserRepository,ExtendingWindsor" channel="B2E" />
<component id="b2c.user.repository" service="ExtendingWindsor.IUserRepository,ExtendingWindsor"
type="ExtendingWindsor.ConsumerUserRepository,ExtendingWindsor" channel="B2C" />
<component id="b2p.user.repository" service="ExtendingWindsor.IUserRepository,ExtendingWindsor"
type="ExtendingWindsor.PartnerUserRepository,ExtendingWindsor" channel="B2P" />
</components>
</castle>
The channel attribute in the above xml is not something that exists in the standard Castle Windsor configuration schema. However Castle Windsor allows for additional attributes so the only thing we need to do is to extend the container so that when it searches for a implementation of the IUserRepository interface it will choose the correct one. This is done in the GetHandler method in the NamingSubSystem so what we need to do is inherit from the DefaultNamingSubSystem and override this method. public override IHandler GetHandler(Type service)
{
IHandler[] handlers = base.GetHandlers(service);
if (handlers.Length < 2)
return base.GetHandler(service);
UserPrincipal user = (UserPrincipal) Thread.CurrentPrincipal;
foreach (IHandler handler in handlers)
{
string channel = handler.ComponentModel.Configuration.Attributes["channel"];
if (channel == user.Channel)
{
return handler;
}
}
// use default
return base.GetHandler(service);
}
What we need to do now is to replace the default NamingSubSystem with our own. To do this we need to look a little at the internals of Castle Windsor and we see that it is actually a wrapper around the Castle MicroKernel container. To exchange the NamingSubSystem of the underlying kernel in Castle Windsor is a little more problematic than it should because we can no longer use the nice constructur that just take in the the path to the xml file. We need to use the constructur that takes in an implementation of IKernel. This is what we have to do: IKernel kernel = new DefaultKernel();
kernel.AddSubSystem(SubSystemConstants.NamingKey, new ExtendedNamingSubSystem());
XmlInterpreter interpreter = new XmlInterpreter();
DefaultComponentInstaller installer = new DefaultComponentInstaller();
interpreter.Kernel = kernel;
interpreter.ProcessResource(interpreter.Source, kernel.ConfigurationStore);
WindsorContainer container = new WindsorContainer(kernel, installer);
container.Installer.SetUp(container, kernel.ConfigurationStore);
This is setup is a little more complex than what you normaly do: WindsorContainer container = new WindsorContainer(new XmlInterpreter())
But there is no constructor that takes an IKernel and a IConfigurationInterpreter. I have thought about submitting such a constructor as a patch to the castle team but have not gotten around to it. We are doneNow we can do this:IUserRepository repos = container.Resolve<IUserRepository>();
The container will dynamically select the correct component based on the current principal. This also works if you have a component that has a constructor dependency to the IUserRepository. You have to think about the lifestyle though so that you do not store a reference to a IUserRepository implementation in a singleton component. Alternative approachInstead of extending the DefaultNamingSubSystem yourself you can use the KeySearchNamingSubsystem which is included in the Castle Windsor release. This allows you to use meta data inserted in the component id to do dynamic component selection.History
|
||||||||||||||||||||||