jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
11k stars 1.16k forks source link

Unable to resolve service for type X while attempting to activate Handler #887

Closed tihomir-kit closed 1 year ago

tihomir-kit commented 1 year ago

Hi, I recently tried upgrading a solution to MediatR 12 which was previously working fine with with MediatR 11. Now I'm getting this error and I can't figure out what's wrong:

System.InvalidOperationException: Unable to resolve service for type 'XXX.Core.Auth.Token.IJwtTokenProvider' while attempting to activate 'XXX.Core.Application.Handlers.Account.Login+Handler'.

IJwtTokenProvider is just a simple service registered in DI: container.Register<IJwtTokenProvider, JwtTokenProvider>();

The Login command is in the XXX.Core.Application project. Program.cs is in XXX.WebAPI project.

If I try to inject and access IJwtTokenProvider inside AccountController (which normally dispatches the Login command), I can access the IJwtTokenProvider without problems. However, it seems there's some issue between MediatR and SimpleInjector when I inject IJwtTokenProvider inside the Login handler. Also, it's not IJwtTokenProvider specifically that's the problem, it's other injected services as well.

This is my Program.cs (.net6)

var container = ContainerFactory.CreateSimpleInjectorContainer();
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var appSettings = new AppSettings(builder.Configuration);
var appEnvironment = new AppEnvironment(appSettings);

builder.Host.UseDefaultServiceProvider((context, options) => options.ValidateOnBuild = false);

services.AddApplicationInsightsTelemetry();
services.AddIdentityServices(appEnvironment);
services.AddJwtAuthentication(appSettings);
services.AddWebApiServices(appSettings);
services.AddSwaggerServices(appSettings);
services.AddSimpleInjector(container);
services.AddMemoryCache();

if (appEnvironment.IsDev())
{
    services.AddMiniProfiler();
}

// .Initialize() is my extension method where DI bindings for all the layters of the solution are set up
container.Initialize(appSettings, appEnvironment); 

var app = builder.Build();

app.Services.UseSimpleInjector(container);

if (appEnvironment.IsDev())
{
    app.UseDeveloperExceptionPage();
}

app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseLLBLGen(appSettings);
app.UseRouting();
app.UseCors(AppConfigOptions.DefaultCorsPolicyName);
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger(appSettings); 

if (appEnvironment.IsDev())
{
    app.UseMiniProfiler();
}

app.UseEndpoints
(
    endpoints =>
    {
        endpoints.MapControllers();
    }
);

container.Verify();

app.Run();

This is my MediatR 11 config which works fine:

public static void RegisterPipeline(this Container container, Assembly[] assemblies)
{
    container.RegisterSingleton<IMediator, Mediator>();
    container.Register(typeof(IRequestHandler<,>), assemblies);

    container.RegisterHandlers(assemblies);
    container.RegisterBehaviors();

    container.Register(() => new ServiceFactory(container.GetInstance), Lifestyle.Singleton);
}

private static void RegisterHandlers(this Container container, Assembly[] assemblies)
{
    container.RegisterHandlers(typeof(INotificationHandler<>), assemblies);
    container.RegisterHandlers(typeof(IRequestExceptionAction<,>), assemblies);
    container.RegisterHandlers(typeof(IRequestExceptionHandler<,,>), assemblies);
}

private static void RegisterHandlers(this Container container, Type collectionType, Assembly[] assemblies)
{
    var handlerTypes = container.GetTypesToRegister
    (
        collectionType,
        assemblies,
        new TypesToRegisterOptions
        {
            IncludeGenericTypeDefinitions = true,
            IncludeComposites = false
        }
    );

    container.Collection.Register(collectionType, handlerTypes);
}

private static void RegisterBehaviors(this Container container)
{
    container.Collection.Register
    (
        typeof(IPipelineBehavior<,>),
        new[]
        {
            // NOTE: Add additional behaviors here. Order of binding = order of execution.
            typeof(RequestExceptionProcessorBehavior<,>),
            typeof(RequestExceptionActionProcessorBehavior<,>),
            typeof(AuthenticationBehavior<,>),
            typeof(AuthorizationBehavior<,>),
            typeof(ValidationBehavior<,>),
            typeof(MemoryCachedBehavior<,>),
            typeof(DbTransactionalBehavior<,>)
        }
    );
}

Mediatr 12 changes:

public static void RegisterPipeline(this Container container, IServiceCollection services, Assembly[] assemblies)
{
    container.RegisterSingleton<IMediator>(() => new Mediator(services.BuildServiceProvider()));
    container.Register(typeof(IRequestHandler<,>), assemblies);

    container.RegisterHandlers(assemblies);
    container.RegisterBehaviors();

    services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(Login).Assembly));
    // I also tried these two, but I'm under the assumption that I need to register the assembly where the Login handler is?
    // services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(Program).Assembly));
    // services.AddMediatR(config => config.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly()));
}

Any rough ideas on what I might be doing wrong?

Thank you.

jbogard commented 1 year ago

You shouldn't be registering IMediator like that. First, IMediator should absolutely not be singleton in a web application. It should be transient. You're also capturing probably the wrong IServiceCollection there. I don't know what your other Register methods do.

AddMediatR does TryAdd so your RegisterSingleton<IMediator> is messing it up.

tihomir-kit commented 1 year ago

Thanks for your reply.

I changed the IMediator registration from singleton to transient but I still get the same error. IServiceCollection comes from Program.cs from the first few lines:

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

I'm not sure what other IServiceCollection I could be capturing.

I will try to create a simplified solution of what I have and put it on GitHub over the weekend if you or someone else is willing and have the time to perhaps look at it at some point.

jbogard commented 1 year ago

When you bring in other DI containers, they'll often have their own implementations of things. In general, don't build the service provider yourself. But you shouldn't be registering IMediator at all, AddMediatR already does that. I would look at the docs for AddMediatR and what it registers, and ONLY register anything else that it doesn't.

tihomir-kit commented 1 year ago

Is this sample up to date?

I'm obviously misunderstanding something but that's where I got the IMediator singleton bit from (probably.. as the initial setup was with MediatR 8 maybe 2 years ago or something like that).

After the upgrade from 11 to 12 and after some googling around I found container.Register<IMediator>(() => new Mediator(services.BuildServiceProvider())); as a suggested fix for container.Register<IMediator, Mediator>(); no longer working. So that explains it (to me) why I was even doing that registraiton).

AddMediatR is also only being used in AspNetCore sample. Perhaps that's where I should focus on.

jbogard commented 1 year ago

No, none of those samples are up to date. It takes me a bit to update those since I really only use either the vanilla container or Lamar. Probably I'll come back through and update them to use the various extensions of Ms.Ext.DI. I'm sure there are folks NOT using MS.Ext.DI but I'm not one of them.

tihomir-kit commented 1 year ago

Ok, that helps. At least I know not to use that sample for now. :D Thnx!