khellang / Scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
MIT License
3.66k stars 239 forks source link

Scanning generics for mediatr handlers #96

Open atresnjo opened 5 years ago

atresnjo commented 5 years ago

Hi. I have a generic mediatr request handler which looks like this:

  public class GetOneByIdHandler<T> : IRequestHandler<GetOneByIdRequest<T>, T> where T : IBaseEntity
    {
        private readonly IAsyncRepository<T> _asyncRepository;

        public GetOneByIdHandler(IAsyncRepository<T> asyncRepository)
        {
            _asyncRepository = asyncRepository;
        }

        public async Task<T> Handle(GetOneByIdRequest<T> request, CancellationToken cancellationToken)
        {
            return await _asyncRepository.GetOneByIdAsync(request.Id);
        }
    }

Right now I have to add the handler for every DTO manually. How can I automate this with Scrutor? Thanks!

services.AddTransient<IRequestHandler<GetOneByIdRequest<MyDto>, MyDto>, GetOneByIdHandler<MyDto>>();

khellang commented 5 years ago

Hey! 👋

You should be able to register using this pattern:

https://github.com/khellang/Scrutor/blob/9f095b0557ab69f31292344f97d16facd343ee4d/test/Scrutor.Tests/ScanningTests.cs#L194-L197

atresnjo commented 5 years ago

Hey! 👋

You should be able to register using this pattern:

https://github.com/khellang/Scrutor/blob/9f095b0557ab69f31292344f97d16facd343ee4d/test/Scrutor.Tests/ScanningTests.cs#L194-L197

Hey! Thanks for the help. But sadly that didnt work. I think I am missing something, but with that pattern how is scrutor supposed to know which DTOs to register?

grafik

khellang commented 5 years ago

Just look at the registrations in the services collection. It should be registered as IRequestHandler<,>, so you need to resolve IRequestHandler<GetOneByIdRequest<ApplicationUser>, ApplicationUser>.

khellang commented 5 years ago

If you want it registered as itself, you can use services.AddScoped(typeof(GetOneByIdHandler<>));

atresnjo commented 5 years ago

Just look at the registrations in the services collection. It should be registered as IRequestHandler<,>, so you need to resolve IRequestHandler<GetOneByIdRequest<ApplicationUser>, ApplicationUser>.

I checked the services collection and only the generic handler GetOneByIdHandler is missing. Other non-generic handlers are added.

khellang commented 5 years ago

Ah, I think I know what's happening now. Because GetOneByIdHandler<T> and IRequestHandler<in TRequest, TResponse> have different generic arity, the type is filtered out when trying to register using .AsImplementedInterfaces().

You either have to add .AsSelf() in addition, or just register the type explicitly, like this:

services.AddScoped(typeof(GetOneByIdHandler<>));

They should both end up doing the same (wrt. this specific type).

atresnjo commented 5 years ago

Ah, I think I know what's happening now. Because GetOneByIdHandler<T> and IRequestHandler<in TRequest, TResponse> have different generic arity, the type is filtered out when trying to register using .AsImplementedInterfaces().

You either have to add .AsSelf() in addition, or just register the type explicitly, like this:

services.AddScoped(typeof(GetOneByIdHandler<>));

They should both end up doing the same (wrt. this specific type).

Sadly not. ✌️ The IRequestHandler implementations which can be seen in the second screenshot are missing.

"Message": "Handler was not found for request of type MediatR.IRequestHandler2[KKH.MedDelivery.Application.Commands.Core.Requests.GetOneByIdRequest1[KKH.MedDelivery.Domain.MedicalPartner],KKH.MedDelivery.Domain.MedicalPartner]. Register your handlers with the container. See the samples in GitHub for examples."

Adding with scrutor: grafik

Adding manually: grafik

Chris-ZA commented 4 years ago

Hello. I am trying to do something very similar. @atresnjo, did you ever figure this out?

atresnjo commented 4 years ago

Hello. I am trying to do something very similar. @atresnjo, did you ever figure this out?

Hey @Chris-ZA

Sorry. I never figured it out. So if you ever figure it out feel free to post! :) thanks.

julealgon commented 4 years ago

@Chris-ZA / @atresnjo , did you consider using this native dependency injection package instead of scanning with Scrutor?

alexb5dh commented 3 years ago

Encountered similar issue while trying to register open generic via Scan.

Using interfaces like that:

public interface ISingleton { }

public interface IAppCache<out TService>{}

public class AppCache<TService>: IAppCache<TService>, ISingleton {}

and registering via:

services.Scan(scan => scan.FromAssemblyOf<ISingleton>().AddClasses(c => c.AssignableTo<ISingleton>()).AsSelfWithInterfaces().WithSingletonLifetime())

trying to fetch the service:

provider.GetRequiredService<IAppCache<object>>()

doesn't work.

Version 3.3.0. Does Scrutor really support open generics?

khellang commented 3 years ago

Does Scrutor really support open generics?

Absolutely, there a lots of tests in this repo showing that it works 😅

alexb5dh commented 3 years ago

Absolutely, there a lots of tests in this repo showing that it works 😅

Got it. Looks like I will need to dig deeper to the source code to figure out why my case doesn't wan't to 🙂

julealgon commented 3 years ago

@alexb5dh , have you seen my post above? Have you considered using that package instead of manually registering the handlers?

alexb5dh commented 3 years ago

@julealgon unfortunately my case is not related to MediatR only, but to custom services also, one of which is provided in the example.

Mariachi1231 commented 3 years ago

Encountered similar issue while trying to register open generic via Scan.

Using interfaces like that:

public interface ISingleton { }

public interface IAppCache<out TService>{}

public class AppCache<TService>: IAppCache<TService>, ISingleton {}

and registering via:

services.Scan(scan => scan.FromAssemblyOf<ISingleton>().AddClasses(c => c.AssignableTo<ISingleton>()).AsSelfWithInterfaces().WithSingletonLifetime())

trying to fetch the service:

provider.GetRequiredService<IAppCache<object>>()

doesn't work.

Version 3.3.0. Does Scrutor really support open generics?

Try to use AsImplementedInterfaces, I think your sample does not work because of AsSelfWithInterfaces lifecycle constraints. You cannot create a single instance of one type that will cover open generic types. See #84

For example, seems the next test works as expected, besides the fact that there will be separate instances of singleton per every exposed type/interface.

        [Fact]
        public void Issue96_()
        {
            var sp = ConfigureProvider(sc =>
            {
                sc.Scan(scan
                    => scan
                        .FromAssemblyOf<ISingleton>()
                            .AddClasses(c => c.AssignableTo<ISingleton>())
                            .AsImplementedInterfaces()
                            .WithSingletonLifetime());
            });

            var result = sp.GetService<IAppCache<object>>();

            Assert.NotNull(result);
            Assert.IsAssignableFrom<AppCache<object>>(result);
        }
yuessir commented 2 years ago

any update? eg: ConcreteClass:IConcrete<Foo< T >,T>

khellang commented 2 months ago

I wonder if this is the same bug as https://github.com/khellang/Scrutor/issues/104?