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

Error: Arity of open generic service type INotificationHandler<> does not equal arity of open generic implementation type #674

Closed
markmillercc opened this issue Nov 20, 2021 · 10 comments

Comments

@markmillercc
Copy link

Using AspNet Core DI and MediatR version 9.0, I am running into this issue after upgrading Microsoft.Extensions.Hosting from version 5 to 6.

Here's a simple reproduction, with dependencies:

  • MediatR (9.0.0)
  • MediatR.Extensions.Microsoft.DependencyInjection (9.0.0)
  • Microsoft.Extensions.Hosting (6.0.0) <--- This is the issue
public class Program
{
    static void Main(string[] args)
    {
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddMediatR(typeof(Program));
                services.AddTransient<INotificationHandler<MyEvent>, GenericEventHandler<MyEvent, MyCommand>>();
            })
            .Build()
            .Run();
    }
}

public class MyCommand { }

public class MyEvent : INotification { }

public class GenericEventHandler<TEvent, TCommand> : INotificationHandler<TEvent>
    where TEvent : INotification
{
    public Task Handle(TEvent notification, CancellationToken cancellationToken)
        => Task.CompletedTask;
}

This was working fine with Microsoft.Extensions.Hosting v 5.0.0. After upgrading to 6.0.0, the same code fails with error:

Arity of open generic service type 'MediatR.INotificationHandler1[TNotification]' does not equal arity of open generic implementation type 'Ventana.Communication.GenericEventHandler2[TEvent,TCommand]'. (Parameter 'descriptors')

My guess is that during AddMediatR(), it's attempting to do something like this:

services.AddTransient<INotifcationHandler<>, GenericEventHandler<,>>();

The error makes sense, although I don't understand why it was working before.

Any suggestions?

Can I tell MediatR to ignore certain types when auto-registering handlers? Or skip any auto-registration and just register all of my handlers explicitly (ugh)?

Neither of those are ideal - before upgrading, everything was working smooth and I had the benefit of using MediatR's auto registration for standard handlers, while I could also explicitly register my generic handlers.

Any help is appreciated, thanks!

@Adwelean
Copy link

Hello,

I have exactly the same error in .Net 6 but not directly with MediatR.

I'm trying to register a generic repository like this:

serviceDescriptors.AddTransient(typeof(IRepository<>), typeof(EntityRepository<,>));

and tada : Arity of open generic service type 'Bot.Core.Interfaces.Persistence.IRepository`1[TEntity]' does not equal arity of open generic implementation type 'Bot.Infrastructure.Persistence.Repositories.EntityRepository`2[TEntity,TId]'. (Parameter 'descriptors')

but if I register them one by one, it works :

serviceDescriptors.AddTransient<IRepository<User>, EntityRepository<User, int>>();

related issue : dotnet/runtime#61407

@markmillercc
Copy link
Author

markmillercc commented Nov 23, 2021

@Adwelean Thanks! Your link is super helpful - looks the root of the issue. I know this isn't a MediatR specific issue but it seems like this recent .Net update is going to cause some problems. The generic notification handler is a pattern I've used for a long time and it's always worked well for me until now. Am I the only one?

I don't mind registering my handlers manually; that's what I've always had to do for these. But now AddMediatR() is broken and I'd rather not implement my own version.

I have a few ideas to get around this but they're not pretty, and I thought I'd check here first to see if there's a known solution.

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Nov 24, 2021

jbogard/MediatR.Extensions.Microsoft.DependencyInjection#79 likewise shows an arity error, although not quite the same one; that one was thrown during service instantiation rather than registration.

If I understand correctly, Microsoft.Extensions.DependencyInjection has never been able to create instances of a service that was registered as an open generic type with a mismatching arity. MediatR.Extensions.Microsoft.DependencyInjection should not attempt to make such registrations.

@markmillercc
Copy link
Author

@AxSamyn
Copy link

AxSamyn commented Nov 16, 2023

Hello @markmillercc !
I'm a bit lost, you've marked the issue as solved but this is exactly the issue i'm facing.

I'm using .NET 6 and the MediatoR v12.1.1 but not the MediatR.Extensions.Microsoft.DependencyInjection.
If I'm right, these two were merged ?

I tried to do exactly what @Adwelean did, which is the following service registration :
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<,>))

Any help is welcome ;)

@markmillercc
Copy link
Author

Hi @AxSamyn

I don't think your issue is related to MediatR.

It's been a little while since I've looked at this, but if I recall the issue is that your registration is not valid. When the container attempts to resolve IGenericRepository<T> into GenericRepository<,> it won't know what types to use. How would it? It only has one type, T, but needs two.

Previously, .NET would allow this registration but would error when you try to resolve. The .NET 6 update just moved the error to the point of registration.

This would make more sense:

services.AddTransient(typeof(IGenericRepository<TGeneric>), typeof(GenericRepository<TGeneric, Concrete>))

So if you were to call provider.GetService<IGenericRepository<MyEntity>>() it knows to resolve into GenericRepository<MyEntity, Concrete>

I explain a similar scenario in readme for my repo here: https:/markmillercc/MediatRClub

Long story short, the "fix" for my original issue was not to allow such registrations, but to prevent MediatR from attempting to apply the registration because it would not be valid.

@AxSamyn
Copy link

AxSamyn commented Nov 17, 2023

Hi @AxSamyn

I don't think your issue is related to MediatR.

It's been a little while since I've looked at this, but if I recall the issue is that your registration is not valid. When the container attempts to resolve IGenericRepository<T> into GenericRepository<,> it won't know what types to use. How would it? It only has one type, T, but needs two.

Previously, .NET would allow this registration but would error when you try to resolve. The .NET 6 update just moved the error to the point of registration.

This would make more sense:

services.AddTransient(typeof(IGenericRepository<TGeneric>), typeof(GenericRepository<TGeneric, Concrete>))

So if you were to call provider.GetService<IGenericRepository<MyEntity>>() it knows to resolve into GenericRepository<MyEntity, Concrete>

I explain a similar scenario in readme for my repo here: https:/markmillercc/MediatRClub

Long story short, the "fix" for my original issue was not to allow such registrations, but to prevent MediatR from attempting to apply the registration because it would not be valid.

Ok thanks for pointing this out !

I'm new to the concept of dependency injection, and while i thought i mastered the basics, I'll go and check the documentation again, cause my initial assumption was that the dependency injection system would find a way to determine this, like some kind of magic... :)

More seriously, I wanted to avoid manual declaration of the different types linked to the interface.
Maybe I can find a way by creating classes that inherit from my GenericRepository, but that would directly specify the generic type.

Anyway, thanks again for the help !

@Adwelean
Copy link

Hi @AxSamyn

I don't think your issue is related to MediatR.

It's been a little while since I've looked at this, but if I recall the issue is that your registration is not valid. When the container attempts to resolve IGenericRepository<T> into GenericRepository<,> it won't know what types to use. How would it? It only has one type, T, but needs two.

Previously, .NET would allow this registration but would error when you try to resolve. The .NET 6 update just moved the error to the point of registration.

This would make more sense:

services.AddTransient(typeof(IGenericRepository<TGeneric>), typeof(GenericRepository<TGeneric, Concrete>))

So if you were to call provider.GetService<IGenericRepository<MyEntity>>() it knows to resolve into GenericRepository<MyEntity, Concrete>

I explain a similar scenario in readme for my repo here: https:/markmillercc/MediatRClub

Long story short, the "fix" for my original issue was not to allow such registrations, but to prevent MediatR from attempting to apply the registration because it would not be valid.

Ok thanks for pointing this out !

I'm new to the concept of dependency injection, and while i thought i mastered the basics, I'll go and check the documentation again, cause my initial assumption was that the dependency injection system would find a way to determine this, like some kind of magic... :)

More seriously, I wanted to avoid manual declaration of the different types linked to the interface.
Maybe I can find a way by creating classes that inherit from my GenericRepository, but that would directly specify the generic type.

Anyway, thanks again for the help !

You can use Source Generators for register your repository 😎

@sulton-max
Copy link

For anyone dealing with registration of request pipeline for void requests, I have this solution which worked for me

public class VoidRequestValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators) : IPipelineBehavior<TRequest, Unit>
    where TRequest : IRequest
{
    public async Task<Unit> Handle(TRequest request, RequestHandlerDelegate<Unit> next, CancellationToken cancellationToken)
    {
        // Validate request
        var context = new ValidationContext<TRequest>(request);
        var failures = (await Task.WhenAll(validators
                .Select(v => v.ValidateAsync(context, cancellationToken))))
            .SelectMany(result => result.Errors)
            .Where(error => error != null)
            .ToList();

        if (failures.Any())
            throw new AppValidationException(failures);

        return await next();
    }
}

registration

 builder.Services.AddMediatR(conf =>
        {
            conf.AddBehavior(typeof(IPipelineBehavior<,>), typeof(VoidRequestValidationBehavior<,>));
        });

Yes TResponse in VoidRequestValidationBehavior isn't used but removing it will this exception again

@Kartik018
Copy link

This solution works.

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

6 participants