How can i do this better using Ninject IOC

2019-09-11 12:08发布

the concept is quite popular but I have not found the answer yet.

I have the following class structure

public interface IMessage 
{
}

public class MessageA : IMessage
{
}

public class MessageB : IMessage
{
}

public interface IMessageHandler<T>  where T : IMessage
{
    void Handle(TMessage message);
}

public interface MessageAHandler : IMessageHandler<MessageA>
{
}

public interface MessageBHandler : IMessageHandler<MessageB>
{
}

public class MessageProcessor
{
    public void Process(IMessage)
    {
        if (IMessage is MessageA)
        {
            // handler for messageA has to be invoked
        }
        else if (IMessage is MessageB)
        {
            // handler for messageB has to be invoked
        }
    }
}

Now I am using Ninject and dooing my bindings like

Bind<IMessageHandler<MessageA>>.To<MessageAHandler>();
Bind<IMessageHandler<MessageB>>.To<MessageBHandler>();

I want to do all the binding magic somehow in the binding module for choosing the handler. MessageProcessor class should just be passed something which it calls to handle the message.

What this something is and how it can be done in the binding module is what i cannot figure out. Can someone please help!

Thanks

1条回答
趁早两清
2楼-- · 2019-09-11 12:37

Use the ninject conventions extension https://github.com/ninject/ninject.extensions.conventions, select all types inheriting from typeof(IMessageHandler<>) and then use a custom IBindingGenerator implementation. For example:

        this.Kernel.Bind(x => x
            .FromThisAssembly()
            .IncludingNonePublicTypes()
            .SelectAllClasses()
            .InheritedFrom(typeof(IMessageHandler<>))
            .BindWith<MessageHandlerBindingGenerator>());

And at the Binding Builder use reflection to create the specific interface type IMessageHandler<XYZ> and use it like this.Kernel.Bind(interfaceType).To(type).

I can provide a complete implementation if you wish.

Okay i tried to keep it as simple as possible, if there is anything more complicated that you really need, then please tell me:

public interface IMessage { }
public class MessageA : IMessage { }
public class MessageB : IMessage { }

public interface IMessageHandler<T>
    where T : IMessage
{
    void Handle(T message);
}

public class MessageHandlerA : IMessageHandler<MessageA>
{
    public void Handle(MessageA message)
    {
        Console.WriteLine("Message A handled");
    }
}

public class MessageHandlerB : IMessageHandler<MessageB>
{
    public void Handle(MessageB message)
    {
        Console.WriteLine("Message B handled");
    }
}

public class MessageProcessor
{
    private static readonly MethodInfo GenericProcessMethod = typeof(MessageProcessor).GetMethod("ProcessGeneric");

    private readonly IResolutionRoot resolutionRoot;

    public MessageProcessor(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public void Process(IMessage message)
    {
        GenericProcessMethod.MakeGenericMethod(message.GetType())
            .Invoke(this, new object[] { message });
    }

    public void ProcessGeneric<TMessage>(TMessage message)
        where TMessage : IMessage
    {
        var handler = this.resolutionRoot.Get<IMessageHandler<TMessage>>();
        handler.Handle(message);
    }
}

public class Test
{
    private readonly IKernel kernel;

    public Test()
    {
        this.kernel = new StandardKernel();

        this.kernel.Bind(x => x
            .FromThisAssembly()
            .IncludingNonePublicTypes()
            .SelectAllClasses()
            .InheritedFrom(typeof(IMessageHandler<>))
            .BindSingleInterface());
    }

    [Fact]
    public void IntegrationTest()
    {
        var messageProcessor = this.kernel.Get<MessageProcessor>();

        messageProcessor.Process(new MessageA());
        messageProcessor.Process(new MessageB());
    }
}

(the [Fact] attribute comes from xUnit which i use to run the test.)

I left out the interface IMessageAHandler : IMessageHandler<MessageA> etc. interfaces, do you really need them? They make things more complicated. Either for the binding generator or the it gets more complicated to determine the type the message processor needs to use.

查看更多
登录 后发表回答