Search code examples
c#dependency-injectionsimple-injectoropen-generics

How to use composite handler with nested open generic?


public interface IMessage { }
public interface ICommand : IMessage { }
public interface IEvent : IMessage { }

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

public class ItemCreatedEvent : IEvent {
    public string Name { get; set; }
}

public class ValidateMessageEnvelope<TMessage> {
    public TMessage Message { get; set; }
    public ValidateMessageEnvelope(TMessage message){
        Message = message;
    }
}

public class ValidateMessageEnvelopeHandler<TMessage> 
    : IMessageHandler<ValidateMessageEnvelope<TMessage>> {
    private readonly IMessageHandler<TMessage> _innerHandler;
    public ValidateMessageEnvelopeHandler(IMessageHandler<TMessage> innerHandler){
        _innerHandler = innerHandler;
    }
    public void Handle(ValidateMessageEnvelope<TMessage> message){
        _innerHandler.Handle(message.Message);
    }
}

public class SecureMessageEnvelope<TMessage> {
    public TMessage Message { get; set; }
    public string UserToken { get; set; }
    public SecureMessageEnvelope(TMessage message, string userToken){
        Message = message;
        UserToken = userToken;
    }
}

public class SecureMessageEnvelopeHandler<TMessage> 
    : IMessageHandler<SecureMessageEnvelope<TMessage>>
{
    private readonly IMessageHandler<TMessage> _innerHandler;
    public SecureMessageEnvelopeHandler(IMessageHandler<TMessage> innerHandler){
         _innerHandler = innerHandler;
    }
    public void Handle(SecureMessageEnvelope<TMessage> message){
         _innerHandler.Handle(message.Message);
    }
}

public class MessageLogDecorator<TMessage> : IMessageHandler<TMessage> 
    where TMessage : IEvent {
    private readonly IMessageHandler<TMessage> _messageHandler;
    public MessageLogDecorator(IMessageHandler<TMessage> messageHandler) {
        _messageHandler = messageHandler;
    }
    public void Handle(TMessage message){
        Console.WriteLine("Event Log: {0}",JsonConvert.SerializeObject(message));
        _messageHandler.Handle(message);
    }
}

public class CompositeMessageHandler<TMessage> : IMessageHandler<TMessage> {
    private readonly IEnumerable<IMessageHandler<TMessage>> _handlers;
    public CompositeMessageHandler(IEnumerable<IMessageHandler<TMessage>> handlers){
        _handlers = handlers;
    }
    public void Handle(TMessage message) {
        foreach (var messageHandler in _handlers) {
            messageHandler.Handle(message);
        }
    }
}

public class LogService :IMessageHandler<ItemCreatedEvent> {
    public void Handle(ItemCreatedEvent message) {}
}

public class ProjectionService: IMessageHandler<ItemCreatedEvent> {
    public void Handle(ItemCreatedEvent message) { }
}

public static class Extensions{
    public static SecureMessageEnvelope<TMessage> AsSecure<TMessage>(
        this TMessage message, string userToken){
        return new SecureMessageEnvelope<TMessage>(message, userToken);
    }
    public static ValidateMessageEnvelope<TMessage> AsValidatable<TMessage>(
        this TMessage message){
        return new ValidateMessageEnvelope<TMessage>(message);
    }
}

Register:

Container.RegisterManyForOpenGeneric(typeof (IMessageHandler<>),
    Container.RegisterAll,
    Assembly.GetExecutingAssembly());

// handle all ValidateMessageEnvelope<TMessage> messages
Container.RegisterOpenGeneric(typeof(IMessageHandler<>),
    typeof(ValidateMessageEnvelopeHandler<>)); 

// handle all SecureMessageEnvelope<TMessage> messages
Container.RegisterOpenGeneric(typeof(IMessageHandler<>),
    typeof(SecureMessageEnvelopeHandler<>));

// handle all IEvent messages
Container.RegisterDecorator(typeof(IMessageHandler<>), 
    typeof(MessageLogDecorator<>));

Event Call

var ev = new ItemCreatedEvent().AsSecure("token/1").AsValidatable();
    var handlerType = typeof(IMessageHandler<>).MakeGenericType(ev.GetType());
    foreach (dynamic handler in _container.GetAllInstances(handlerType)){
        handler.Handle((dynamic)ev);    
    }

return empty but must return two handler:

handler[0] = 
    new ValidateMessageEnvelopeHandler<SecureMessageEnvelope<ItemCreatedEvent>>(
        new SecureMessageEnvelopeHandler<ItemCreatedEvent>(
            new MessageLogDecorator<ItemCreatedEvent>(
                new LogService())));

handler[1] = 
    new ValidateMessageEnvelopeHandler<SecureMessageEnvelope<ItemCreatedEvent>>(
        new SecureMessageEnvelopeHandler<ItemCreatedEvent>(
            new MessageLogDecorator<ItemCreatedEvent>(
                new ProjectionService())));

although it would be nice as follows:

new ValidateMessageEnvelopeHandler<SecureMessageEnvelope<ItemCreatedEvent>>(
    new SecureMessageEnvelopeHandler<ItemCreatedEvent>(
        new CompositeHandler(
            new MessageLogDecorator<ItemCreatedEvent>(
                new LogService()),
            new MessageLogDecorator<ItemCreatedEvent>(
                new ProjectionService()))));

if I call "_container.GetInstance(handlerType)" instead of "GetAllInstances" an error occured:

There was an error in the registration of open generic type IMessageHandler. Failed to build a registration for type ValidateMessageEnvelopeHandler>. There was an error in the registration of open generic type IMessageHandler. Failed to build a registration for type SecureMessageEnvelopeHandler. The constructor of the type SecureMessageEnvelopeHandler contains the parameter of type IMessageHandler with name 'innerHandler' that is not registered. Please ensure IMessageHandler is registered in the container, or change the constructor of SecureMessageEnvelopeHandler.

When I register CompositeHandler () I have error. ( Container.RegisterOpenGeneric(typeof(IMessageHandler<>), typeof(CompositeMessageHandler<>));)

There was an error in the registration of open generic type IMessageHandler. Failed to build a registration for type ValidateMessageEnvelopeHandler>. Multiple observers of the ResolveUnregisteredType event are registering a delegate for the same service type: IMessageHandler>. Make sure only one of the registered handlers calls the ResolveUnregisteredType.Register method for a given service type.

How can I fix this?


Solution

  • Your configuration doesn't work because you are doing two things wrong:

    1. You didn't register the CompositeMessageHandler<T>
    2. You are calling GetAllInstances instead of GetInstance.

    When you call GetAllInstances you only get a collection of handlers that are registered using the RegisterManyForOpenGeneric, but in your example you are requesting an IMessageHandler<ValidateMessageEnvelope<SecureMessageEnvelope<ItemCreatedEvent>>>, but you never registered an IMessageHandler<ValidateMessageEnvelope<T>> as collection; the ValidateMessageEnvelopeHandler is registered as 'single item' registration.

    The regisration for the CompositeMessageHandler<T> is a bit more evolved, because you don't want the composite to be returned for all IMessageHandler<T> registrations. You don't want this, because if the message is a ValidateMessageEnvelope<T>, you want to return the ValidateMessageEnvelopeHandler<T> and if the message is a SecureMessageEnvelope<T>, you want to return the SecureMessageEnvelopeHandler<T>.

    So you need to add the following registration AFTER the registrations for the ValidateMessageEnvelope<T> and SecureMessageEnvelopeHandler<T>:

    container.RegisterOpenGeneric(
        typeof(IMessageHandler<>), 
        typeof(CompositeMessageHandler<>),
        Lifestyle.Singleton, 
        context => !context.Handled);
    

    By supplying a context => !context.Handled, you make sure that the CompositeMessageHandler<T> only gets applied if no other registration was applied first. Without this predicate, Simple Injector will detect that two open-generic registrations are applied to the same abstraction and Simple Injector will throw an exception.

    Instead of calling GetAllInstances, you should simply call GetInstance. For instance:

    var ev = new ItemCreatedEvent().AsSecure("token/1").AsValidatable();
    var handlerType = typeof(IMessageHandler<>).MakeGenericType(ev.GetType());
    
    dynamic handler = container.GetInstance(handlerType);
    
    handler.Handle((dynamic)ev);
    

    Do note though that your MessageLogDecorator<T> will not only get applied to the LogService and ProjectionService classes, but also to the CompositeMessageHandler<T> class. This might not be what you want. You might want to -only- wrap the CompositeMessageHandler<T>, or perhaps you want to wrap -everything but- the CompositeMessageHandler<T>. In the last case, you can change the MessageLogDecorator<T>'s registration to the following:

    container.RegisterDecorator(typeof(IMessageHandler<>),
        typeof(MessageLogDecorator<>), context =>
        {
            var type = context.ImplementationType;
            return !type.IsGenericType ||
                type.GetGenericTypeDefinition() != typeof(CompositeMessageHandler<>));
        });
    

    Or you might even be able to simplify it to the following:

    container.RegisterDecorator(typeof(IMessageHandler<>),
        typeof(MessageLogDecorator<>), 
        context => !context.ImplementationType.IsGenericType);
    

    In which case the decorator will only get wrapped around non-generic implementations.