Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

decorate generics by reflection #95

Closed
chinwobble opened this issue Aug 7, 2019 · 7 comments
Closed

decorate generics by reflection #95

chinwobble opened this issue Aug 7, 2019 · 7 comments
Labels
Milestone

Comments

@chinwobble
Copy link

chinwobble commented Aug 7, 2019

I read through all the documentation but couldn't quite get generic registration to work.

This first version works but the second one doesn't work.

services.Scan(x =>
                x.FromAssemblyOf<EventAEventHandler>()
                 .AddClasses(classes => classes.AssignableTo(typeof(IMessageProcessor<>)))
                 .AsImplementedInterfaces()
                 .WithTransientLifetime());

            services.Decorate<IMessageProcessor<EventAEventHandler>, AppInsightsMessageProcessorDecorator<EventAEventHandler>>();
            services.Decorate<IMessageProcessor<EventBEventHandler>, AppInsightsMessageProcessorDecorator<EventBEventHandler>>();
            services.Decorate<IMessageProcessor<EventCEventHandler>, AppInsightsMessageProcessorDecorator<EventCEventHandler>>();
            services.Decorate<IMessageProcessor<EventDEventHandler>, AppInsightsMessageProcessorDecorator<EventDEventHandler>>();

I tried to do but it couldn't get resolved.

services.Decorate(typeof(IMessageHandler<>), typeof(AppInsightsMessageProcessorDecorator<>))

Any help would be appreciated.

@khellang
Copy link
Owner

khellang commented Aug 7, 2019

Yeah, it probably doesn't work because the open generic decoration only works on open generic registrations. .AsImplementedInterfaces() will register closed generic service types.

What does the type signatures look like? 🤔

I think it might work if you also add .As(typeof(IMessageHandler<>)) in your scanning.

@chinwobble
Copy link
Author

so I have the following interfaces

interface IMessageProcessor<T> { }
class EventAProcessor : IMessageProcessor<EventA> { }
class AppInsightsMessageProcessorDecorator<T> : IMessageProcessor<T> { }

Then I try to register everything like this:

            services.Scan(x =>
                x.FromAssemblyOf<CreditLineCreatedEventHandler>()
                 .AddClasses(classes => classes.AssignableTo(typeof(IMessageProcessor<>)))
                 .As(typeof(IMessageProcessor<>))
                 .WithTransientLifetime());

            services.Decorate(typeof(IMessageProcessor<>), typeof(AppInsightsMessageProcessorDecorator<>));

This gives me the error

'The number of generic arguments provided doesn't equal the arity of the generic type definition.
Parameter name: instantiation'

@khellang
Copy link
Owner

khellang commented Aug 7, 2019

Hmm, so after looking at it a bit, it seems the problem is that it's trying to decorate the decorator (AppInsightsMessageProcessorDecorator<>) itself.

This test passes here:

[Fact]
public void Test()
{
    var provider = ConfigureProvider(services =>
    {
        services.Scan(x =>
            x.FromAssemblyOf<EventA>()
                .AddClasses(classes => classes
                    .AssignableTo(typeof(IMessageProcessor<>))
                    .Where(t => t != typeof(AppInsightsMessageProcessorDecorator<>))) // This is the magic line.
                .AsImplementedInterfaces()
                .WithTransientLifetime());

        services.Decorate(typeof(IMessageProcessor<>), typeof(AppInsightsMessageProcessorDecorator<>));
    });

    var processor = provider.GetRequiredService<IMessageProcessor<EventA>>();

    var decorator = Assert.IsType<AppInsightsMessageProcessorDecorator<EventA>>(processor);

    Assert.IsType<EventAProcessor>(decorator.Decoratee);
}

As you can see, I just excluded AppInsightsMessageProcessorDecorator<> from the scanning, which should make it all work.

@khellang
Copy link
Owner

khellang commented Aug 7, 2019

So, this all boils down to a limitation in Microsoft.Extensions.DependencyInjection, which forces open generic services to be registered with an open generic implementation type. This doesn't work for decoration, because it would result in a StackOverflowException.To circumvent that, we use ImplementationFactory instead, which fails this check.

I've added some code to check for this and give you a proper exception message instead of

The number of generic arguments provided doesn't equal the arity of the generic type definition.

This should be on NuGet as v3.0.3 shortly.

@chinwobble
Copy link
Author

nice. Is it possible to do some filter on the library side so that a type doesn't try to ecorate itself?

@khellang
Copy link
Owner

khellang commented Aug 7, 2019

Is it possible to do some filter on the library side so that a type doesn't try to ecorate itself?

Hmm... 🤔

Yes, it might be possible. Instead of throwing, it could silently skip open generic registrations if the decorator itself is open generic. The question is if that's desirable. It could be a bit confusing.

@khellang
Copy link
Owner

khellang commented Aug 7, 2019

I think I'll change from throwing to just skipping open generic services by default. In 99% of the cases, it'll probably do what you'd expect.

@khellang khellang added the Bug label Aug 7, 2019
@khellang khellang added this to the v3.1.0 milestone Aug 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants