Simple injector open generic decorators

人走茶凉 提交于 2019-12-09 14:00:25

问题


I am trying to make use of some of the nice features in simple injector.

I am currently having problems with the decorators, they are not getting hit when I expect them too.

I am registering them like this:

container.RegisterManyForOpenGeneric(
      typeof(ICommandHandler<>),
      AppDomain.CurrentDomain.GetAssemblies());

container.RegisterDecorator(
      typeof(ICommandHandler<>),
      typeof(CreateValidFriendlyUrlCommandHandler<>),
      context => context.ServiceType == typeof(ICommandHandler<CreateProductCommand>)
 );

 container.RegisterDecorator(
      typeof(ICommandHandler<>),
      typeof(CreateProductValidationCommandHandler<>),
      context => context.ServiceType == typeof(ICommandHandler<CreateProductCommand>)
 );

I think I must be missing something as I am expecting that a call to ICommandHandler<CreateProductCommand> will invoke CreateValidFriendlyUrlCommandHandler<> and CreateProductValidationCommandHandler<> before running itself.

I have tried a different registration like this:

container.RegisterManyForOpenGeneric(
      typeof(ICommandHandler<>),
      AppDomain.CurrentDomain.GetAssemblies());

container.RegisterDecorator(
      typeof(ICommandHandler<>),
      typeof(CreateValidFriendlyUrlCommandHandler<>),
      context => context.ImplementationType == typeof(CreateProductCommandHandler)
 );

 container.RegisterDecorator(
      typeof(ICommandHandler<>),
      typeof(CreateProductValidationCommandHandler<>),
      context => context.ImplementationType == typeof(CreateProductCommandHandler)
 );

As I thought registering a decorator for ICommandHandler<CreateProductCommand> on the type ICommandHandler<CreateProductCommand> when the CreateProductValidationCommandHandler and CreateValidFriendlyUrlCommandHandler implement ICommandHandler<CreateProductCommand> may well hit a bit of a circular reference.

But changing that made no difference.

Here is my CreateProductValidationCommandHandler<TCommand>:

public class CreateProductValidationCommandHandler<TCommand> 
    : ICommandHandler<CreateProductCommand>
{
    private readonly ICommandHandler<TCommand> decorated;
    private readonly IValidationService validationService;

    public CreateProductValidationCommandHandler(
        ICommandHandler<TCommand> decorated,
        IValidationService validationService)
    {
        this.decorated = decorated;
        this.validationService = validationService;
    }

    public void Handle(CreateProductCommand command)
    {
        if (!validationService.IsValidFriendlyName(
            command.Product.ProductFriendlyUrl))
        {
            command.ModelStateDictionary.AddModelError(
                "ProductFriendlyUrl", 
                "The Friendly Product Name is not valid...");

            return;
        }

        if (!validationService.IsUniqueFriendlyName(
            command.Product.ProductFriendlyUrl))
        {
            command.ModelStateDictionary.AddModelError(
                "ProductFriendlyUrl", 
                "The Friendly Product Name is ...");

            return;
        }
    }
}

And this is my CreateValidFriendlyUrlCommandHandler<TCommand>:

public class CreateValidFriendlyUrlCommandHandler<TCommand>
    : ICommandHandler<CreateProductCommand>
{
    private readonly ICommandHandler<TCommand> decorated;

    public CreateValidFriendlyUrlCommandHandler(ICommandHandler<TCommand> decorated)
    {
        this.decorated = decorated;
    }

    public void Handle(CreateProductCommand command)
    {
        if (string.IsNullOrWhiteSpace(
            command.Product.ProductFriendlyUrl))
        {
            command.Product.ProductFriendlyUrl = 
                MakeFriendlyUrl(command.Product.Name);
        }
    }
}

回答1:


The problem is that Simple Injector will never be able to wrap an ICommandHandler<T> implementation with one of your decorators, because there is an unresolvable generic type TCommand. You would have noticed this if the Handle method of your decorators would call the decorated instance. For instance:

public class CreateValidFriendlyUrlCommandHandler<TCommand>
    : ICommandHandler<CreateProductCommand>
{
    private readonly ICommandHandler<TCommand> decorated;

    public CreateValidFriendlyUrlCommandHandler(
        ICommandHandler<TCommand> decorated)
    {
        this.decorated = decorated;
    }

    public void Handle(CreateProductCommand command)
    {
        // This won't compile since CreateProductCommand and
        // TCommand are not related.
        this.decorated.Handle(command);
    }
}

This code won't compile, since the decorator's Handle method takes an CreateProductCommand argument, while the decorated instance takes a TCommand argument, which isn't specified (and nowhere is stated that CreateProductCommand is a TCommand).

In fact you didn't create a decorator at all. A decorator wraps an instance of the same interface that it implements. You wrap an ICommandHandler<TCommand> while you implement an ICommandHandler<CreateProductCommand>. The only way you would get this to work is when you explicitly specify the TCommand to be a CreateProductCommand, as follows:

ICommandHandler<CreateProductCommand> handler = 
    new CreateValidFriendlyUrlCommandHandler<CreateProductCommand>(
        new CreateProductCommandHandler()
    );

Still, there is no way for Simple Injector to 'guess' that this TCommand should be a CreateProductCommand and that's why your 'decorator' didn't get wrapped.

Long story short: ditch the TCommand:

public class CreateValidFriendlyUrlCommandHandler
    : ICommandHandler<CreateProductCommand>
{
    private ICommandHandler<CreateProductCommand> decorated;

    public CreateValidFriendlyUrlCommandHandler(
        ICommandHandler<CreateProductCommand> decorated)
    {
        this.decorated = decorated;
    }

    public void Handle(CreateProductCommand command)
    {
        // logic here
    }
}

Or make it generic with a type constraint:

   public class CreateValidFriendlyUrlCommandHandler<TCommand>
        : ICommandHandler<TCommand>
        where TCommand : CreateProductCommand
    {
        private ICommandHandler<TCommand> decorated;

        public CreateValidFriendlyUrlCommandHandler(
            ICommandHandler<TCommand> decorated)
        {
            this.decorated = decorated;
        }

        public void Handle(TCommand command)
        {
            // logic here
        }
    }

or remove the type constraint and allow handling any type of command, not only CreateProductCommand.

Note that if you are defining many decorators that can only handle one specific type of command handler, you might want to reconsider your strategy. There might be a problem in your design.



来源:https://stackoverflow.com/questions/13435701/simple-injector-open-generic-decorators

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!