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

How to scan and decorate an open generic interface used multiple times? #68

Closed
sander1095 opened this issue Nov 16, 2018 · 6 comments
Closed

Comments

@sander1095
Copy link

sander1095 commented Nov 16, 2018

Hello!

Let me start of by asking if my issue is possibly related to #39 , cuz I feel like it might be.

I discovered your project and I really like the possibilities it offers. I am having a tough time trying to get a functionality to work and I'd love to know if I am able to do this or not. (If I can, I'd like to know how)

My project uses commands and queries, and those commands are decorated. My controller has instances of these command/query handlers. When executing the commands, I'd like the decorators to be executed first, in order, until the actual commandhandler is executed.

A little overview of what I would like to accomplish:

public interface ICommandHandler<in TCommand> where TCommand : Command
{
    Task HandleAsync(TCommand command);
}

public class Command { }

public class ExampleCommand : Command
{
    public int Id {get; set; }
}

//Actual handler:
public class ExampleCommandHandler : ICommandHandler<ExampleCommand>
{
    // It has a repo to retrieve data from, this would be injected in the constructor

    public async Task HandleAsync(ExampleCommand command)
    {
         // Do stuff with the repo...
    } 
}

//First decorator
public class Decorator1CommandHandler : ICommandHandler<ExampleCommand> 
{
      private ICommandHandler<ExampleCommand> _innerHandler;
      private IFunctionality<ExampleCommand> _functionality;

      public Decorator1Commandhandler(...)
      {
           // Set properties
      } 

      public Task HandleAsync(ExampleCommand command)
      {
           // Calls the functionality that this decorator is built for. This ALSO has some more dependency injection in it's constructor...
           await _functionality.DoStuff(command);
 
           // Calls the 2nd decorator
           await _innerHandler.HandleAsync(command);           
      }
}

// 2nd decorator
public class Decorator2CommandHandler : ICommandHandler<ExampleCommand> 
{
      private ICommandHandler<ExampleCommand> _innerHandler;
      private IOtherFunctionality<ExampleCommand> _functionality;

      public Decorator2Commandhandler(...)
      {
           // Set properties
      } 

      public Task HandleAsync(ExampleCommand command)
      {
           // Calls the functionality that this decorator is built for. This ALSO has some more dependency injection in it's constructor...
           await _functionality.DoStuff(command);
 
           // Calls the ACTUAL command handler defined above.
           await _innerHandler.HandleAsync(command);           
      }     
}


// Usage of the command stuff
public class ValuesController : BaseController
{
     private readonly ICommandHandler<ExampleCommand> _commandHandler;;
     
     //Imagine this being an endpoint, and the commandhandler being injected in the constructor
     public async Task<string> Endpoint()
     {
          return await _commandHandler.HandleAsync(new ExampleCommand{ Id = 1});
     } 
}

**Startup**:

//This makes sure that every command handler is inserted in the DI container
services.Scan(scan => scan
    .FromAssembliesOf(typeof(IQueryHandler<,>))
        .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>)))
             .AsImplementedInterfaces()
             .WithTransientLifetime());

//This makes sure that every command handler has its own 2 decorators registered.
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator1CommandHandler<>));
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator2CommandHandler<>));

//Maybe I should decorate the decorate instead of decorating the commandhandler for a 2nd time?

Each command would have the following:

  • (NAME)Command
  • (NAME)CommandHandler
  • (NAME)CommandSecurityValidator (Decorator)
  • (NAME)CommandValidator (Decorator)

Sadly, I can't get this to work! At the moment of writing I can not tell you the exact errors I am getting, but I think that my scenario isn't possible.

If it is, please tell me what I am doing wrong!
If it is not possible, what would be a good container to get this to work anyway? I have some experience with autofac.

@khellang
Copy link
Owner

This is more or less what I'm doing in my unit tests, so I think it should be a supported scenario;

public void CanDecorateOpenGenericTypeBasedOnInterface()
{
var provider = ConfigureProvider(services =>
{
services.AddSingleton<IQueryHandler<MyQuery,MyResult>, MyQueryHandler>();
services.Decorate(typeof(IQueryHandler<,>), typeof(LoggingQueryHandler<,>));
services.Decorate(typeof(IQueryHandler<,>), typeof(TelemetryQueryHandler<,>));
});
var instance = provider.GetRequiredService<IQueryHandler<MyQuery, MyResult>>();
var telemetryDecorator = Assert.IsType<TelemetryQueryHandler<MyQuery, MyResult>>(instance);
var loggingDecorator = Assert.IsType<LoggingQueryHandler<MyQuery, MyResult>>(telemetryDecorator.Inner);
Assert.IsType<MyQueryHandler>(loggingDecorator.Inner);

I'll have to look into it a bit more. In the meantime, I'd appreciate a bit more details than just "it doesn't work" 😉

@khellang
Copy link
Owner

Hmm. You're registering types AssignableTo(typeof(ICommandHandler<,>)) (two arity generic type), while you're decorating typeof(ICommandHandler<>) (one arity generic type). Could that be the problem?

@sander1095
Copy link
Author

I am currently setting up an example project, but in your code you explicitly register a queryhandler, while i would like to register all of them automatically.

And the AssignableTo part is a typo, I meant to type ICommandHandler<>

@sander1095
Copy link
Author

sander1095 commented Nov 18, 2018

Here's my example project:

ScrutorDecorator.zip

The error I get now:

System.ArgumentException: 'The number of generic arguments provided doesn't equal the arity of the generic type definition. on Startup line 62

This is my code for registering my commandhandlers and decorators:

//Register all commandhandlers from the commandhandlers project
//TODO: Maybe this is an issue, since the decorators are also automatically registered and I only mean to register the actual
// non-decorated implementations of ICommandHandler here? 
services.Scan(scan =>
      scan.FromAssembliesOf(typeof(ICommandHandler<>))
        .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)))
            .AsImplementedInterfaces()
            .WithTransientLifetime());

//Register all decorators that use decorator1
services.Scan(scan =>
      scan.FromAssembliesOf(typeof(IDecorator1<>))
        .AddClasses(classes => classes.AssignableTo(typeof(IDecorator1<>)))
            .AsImplementedInterfaces()
            .WithTransientLifetime());

//Register all decorators that use decorator1
services.Scan(scan =>
      scan.FromAssembliesOf(typeof(IDecorator2<>))
        .AddClasses(classes => classes.AssignableTo(typeof(IDecorator2<>)))
            .AsImplementedInterfaces()
            .WithTransientLifetime());

//Decorate ALL commandhandlers with the decorator1 and decorator2 decorators
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator1CommandHandler<>)); //THIS IS LINE 62
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator2CommandHandler<>)); //Try to add the 2nd decorator onto the first one?

I hope you can help me out here ^^'

@fadil-khodabuccus-cko
Copy link

I am observing the exact same issue with versions > 2.1.2.

Version 2.1.2 is working fine using open-generics.

@sander1095
Copy link
Author

Hey everyone, I managed to get my example to work!

The fix is as follows:

//Register all commandhandlers from the commandhandlers project.
// Decorators are not registered because they are open generic types and commandhandlers are closed.
services.Scan(scan =>
      scan.FromAssembliesOf(typeof(ICommandHandler<>))
        .AddClasses(classes => 
        classes.AssignableTo(typeof(ICommandHandler<>)).Where(_ => !_.IsGenericType))
            .AsImplementedInterfaces()
            .WithTransientLifetime());

The difference between this bit and the one in my example zip is that now only the commandhandlers that are NOT generic are registered. In my old example the commandhandlers AND the decorators were registered.

The decorators are open generic types but the commandhandlers are closed. That is why the IsGenericType works!

I hope this helps someone!

#75 #39

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants