Scrutor icon indicating copy to clipboard operation
Scrutor copied to clipboard

Scanning generics for mediatr handlers

Open atresnjo opened this issue 6 years ago • 18 comments

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>>();

atresnjo avatar Sep 03 '19 11:09 atresnjo

Hey! 👋

You should be able to register using this pattern:

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

khellang avatar Sep 03 '19 12:09 khellang

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

atresnjo avatar Sep 03 '19 12:09 atresnjo

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 avatar Sep 03 '19 12:09 khellang

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

khellang avatar Sep 03 '19 12:09 khellang

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.

atresnjo avatar Sep 03 '19 13:09 atresnjo

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).

khellang avatar Sep 03 '19 14:09 khellang

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

atresnjo avatar Sep 03 '19 14:09 atresnjo

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

Chris-ZA avatar Jan 18 '20 18:01 Chris-ZA

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.

atresnjo avatar Jan 21 '20 06:01 atresnjo

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

julealgon avatar Oct 26 '20 12:10 julealgon

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?

alexb5dh avatar Dec 21 '20 20:12 alexb5dh

Does Scrutor really support open generics?

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

khellang avatar Dec 22 '20 09:12 khellang

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 🙂

alexb5dh avatar Jan 06 '21 18:01 alexb5dh

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

julealgon avatar Jan 06 '21 20:01 julealgon

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

alexb5dh avatar Jan 06 '21 20:01 alexb5dh

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);
        }

Mariachi1231 avatar Sep 14 '21 20:09 Mariachi1231

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

yuessir avatar Aug 27 '22 12:08 yuessir