问题
In my application I have a class named Message. There exists a property in the Message class with the name MessageType of type string. The MessageType property is used to alert the application as to what data schema will exist within the instance of the Message class. The Message class derives from an interface named IMessage.
As an example let's say we have an instance of the Message class who's MessageType property has the value of "com.business.product.RegisterUser".
Each MessageType schema has a corresponding message handler class (MessageHandler) which derives from the interface IMessageHandler. In the case of the above example, there would be a class named RegisterUserMessageHandler. All message handler classes derive from IMessageHandler. IMessageHandler defines a GetMessageType function which will return the derived message instance’s MessageType property.
During registration of the classes / components with the Castle Windsor IoC container I would like to set the Name property to the value of the Message instance’s MessageType property. I would also like to register these classes / components using the ‘register components by conventions’ method to accomplish this.
The ultimate purpose of this exercise is to allow my code to call the Resolve method using the MessageType property of the Message to obtain the correct message handler instance. For example:
string messageType = "com.business.product.RegisterUser";
IMessageHandler registerUserMessageHandler = _container.Resolve<IMessageHandler>(messageType);
I would appreciate any help to solve this. Thanks very much.
回答1:
Here's a solution I came up with, but it does have some drawbacks. You'll have to decide if the drawbacks are acceptable for your situation. However, the benefit is that configuration is based on convention and adding new IMessage
and IMessageHandler
types won't require any additional Windsor configuration.
I used a combination of Windsor's TypedFactoryFacility
along with a custom ITypedFactoryComponentSelector
. By using a factory, I remove the need for your code to directly call the container's Resolve
method. When a class needs to get an IMessageHandler
based on a message type, it can just have a dependency on the factory. Here's the factory:
public interface IMessageHandlerFactory
{
IMessageHandler GetMessageHandler(string messageType);
}
Since I'm using Windsor's TypedFactoryFacility
I don't need to implement this method, but when it comes to calling the GetMessageHandler
method, I'll "help" Windsor pick the proper component by defining a custom component selector:
public class HandlerTypeSelector : DefaultTypedFactoryComponentSelector
{
private readonly WindsorContainer container;
public HandlerTypeSelector(WindsorContainer container)
{
this.container = container;
}
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
if (method.Name == "GetMessageHandler")
{
var type = arguments[0].ToString();
var messageHandlers = container.ResolveAll<IMessageHandler>();
var single = messageHandlers.SingleOrDefault(h => h.GetMessageType() == type);
if( single != null)
return single.GetType().FullName;
}
return base.GetComponentName(method, arguments);
}
}
Registering all of this is as simple as:
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<IMessageHandlerFactory>().AsFactory(c => c.SelectedWith(new HandlerTypeSelector(container))),
AllTypes.FromThisAssembly().BasedOn<IMessage>().WithService.AllInterfaces(),
AllTypes.FromThisAssembly().BasedOn<IMessageHandler>().WithService.AllInterfaces()
);
Here's some test code I used to verify:
var sampleMessage = new RegisterUserMessage();
var factory = container.Resolve<IMessageHandlerFactory>();
var handler = factory.GetMessageHandler(sampleMessage.MessageType);
And the classes/interfaces I used to test with:
public interface IMessage
{
string MessageType { get; }
}
public interface IMessageHandler
{
string GetMessageType();
}
public class RegisterUserMessage : IMessage
{
public string MessageType
{
get { return "RegisterUser"; }
}
}
public class RegisterUserMessageHandler : IMessageHandler
{
public string GetMessageType()
{
return "RegisterUser";
}
}
public class RemoveUserMessage : IMessage
{
public string MessageType
{
get { return "RemoveUser"; }
}
}
public class RemoveUserMessageHandler : IMessageHandler
{
public string GetMessageType()
{
return "RemoveUser";
}
}
Now, the drawback: Inside the component selector, I resolve all IMessageHandler
's and then decide which one to use based on the name (since I didn't name the message handlers when registering them, they'll be registered in Windsor with their fully qualified name). If your message handlers are lightweight and/or there's not many of them, or they're static, this might not be an issue. If they are transient and/or have significant initialization code, resolving all of them every time you need a single one could be costly. In that case, I'd scrap this whole approach and go with something that is more configuration-heavy, but allows resolving a single message handler directly by name.
回答2:
Probably, this is a little bit late but, You could do like this:
foreach (string messageType in allMessages)
{
container.Register(
Component.For<IMessageHangler>()
.ImplementedBy(GetImplementationFor(messageType))
.Named(messageType);
);
}
Type GetImplementationFor(string messageType)
{
switch (messageType)
{
case "com.business.product.RegisterUser": return typeof(RegisterUserService);
// more ....
default: throw new Exception("Unhandled message type!");
}
}
// In work:
void ProcessMessage(IMessage message)
{
var handler = this.Container.Resolve<IMessageHandler>(message.MessageType);
handler.handle(message);
}
But make sure, that since this approach You will not be able to register any other component with any of message types names, e.g.
container.Register<IFoo>()
.ImplementedBy<FooHandler>()
.Named("com.business.product.RegisterUser");
will throw an exception, despite that IFoo is not IMessageHandler.
来源:https://stackoverflow.com/questions/8941055/setting-the-name-property-during-registration-of-a-component-using-castle-windso