Mediatr: [Question] Can IMediatR be registered as Singleton?

Created on 18 Apr 2018  Â·  20Comments  Â·  Source: jbogard/MediatR

I'm using a library that registers a type I define (that consumes IMediatr) as Singleton. Mediatr is registered in the ServiceCollection extensions as Scoped, so I'm getting an InvalidOperationException: Cannot consume scoped service 'MediatR.IMediator' from singleton 'Xyz.Abc' exception.

I can circumvent the ServiceCollection extensions and do my own registration if need be, just wanted some advice on lifetime.

FYI: This is for an ASP.NET Core web api style app.

Thanks!

Most helpful comment

The general rule of thumb is singletons can only depend on other singletons, all the way down. This is true really for any DI container. If you don't want this behavior, you have two options:

  • Don't make your service a singleton
  • Resolve dynamically through a scoped container on-demand (inside a method or otherwise)

This is also true for any DI container that supports scoped/nested containers. It's exactly what ASP.NET Core does per request - so in your case, your choices are a bit limited.

There's nothing preventing you from registering IMediator as a singleton, HOWEVER, this forces every handler, and every handler's dependencies, to be singleton.

You could also provide your own implementation of IMediator to do what the ASP.NET Core code does above - create a scope and resolve handlers from that scope. I don't because I don't want to couple the Mediator implementation to scopes, and I have the control of just registering all the dependencies as Transient.

All 20 comments

Does Xyz.Abc need to be registered as a singleton?

I've asked the library implementor over there too, though trying to make it scoped leads me to make it's parent scoped and then get stuck anyway... https://github.com/graphql-dotnet/graphql-dotnet/issues/478#issuecomment-382300574

You could create your own scope inside the singleton instance and retrieve instances from it. For example:

public MySingletonService(IServiceProvider serviceProvider)
{
    _serviceProvider = serviceProvider;
}

public void Foo()
{
    using (var scope = _serviceProvider.CreateScope())
    {
        var scopedProvider = scope.ServiceProvider;
    }
}

Yea I'm having an issue with there being no scope I think. I created an example repo of the "out-of-the-box" configuration that exhibits my initial problem. https://github.com/benmccallum/GraphQLAndMediator

If I change MyQuery to scoped, then I get the cannot create from root scope error. I've tried wrapping the .UseGraphQLHttp<MySchema>(...) middleware in a scope without luck. I think maybe it's because there is a custom resolver function IDependencyResolver which may not have a scope.

Will keep playing around and plug your example above in there. Thanks!

Thanks for the tips, I've ended up creating instances of IMediator within a scope I create inside my GraphQL resolvers. I'm still interested to find out how compatible IMediator is as a singleton though if anyone has any info. Thanks again.

The problem with a singleton instance of Mediator is the reverse of your problem: you'll end up with errors about resolving scoped services in request handlers from within a singleton Mediator.

Ah yes of course, because AddMediatR scans for and adds the request handlers as scoped anyway, so it's not quite reversed as you say but if I make IMediator a singleton it just fails one layer further down with the same issue.

Actually they are added as Transient, so I think that'd be ok.

The same problem applies to transient services as well, you'll end up with singleton instances of them. Plus transient services can consume scoped services.

I'm not across MediatR's implementation. I thought that an IRequestHandler would be created each time one is needed by MediatR from fresh. But sounds like you're saying they are only grabbed once from the service provider and re-used (hence they're lifetime is "hoisted" to the creator IMediator that's a singleton).

They are, but they will be resolved from the root service provider which means you effectively turn them into singletons as they won't be disposed until the application stops. This could lead to all sorts of problems when you're depending on services being disposed to close database connections etc.

The general rule of thumb is singletons can only depend on other singletons, all the way down. This is true really for any DI container. If you don't want this behavior, you have two options:

  • Don't make your service a singleton
  • Resolve dynamically through a scoped container on-demand (inside a method or otherwise)

This is also true for any DI container that supports scoped/nested containers. It's exactly what ASP.NET Core does per request - so in your case, your choices are a bit limited.

There's nothing preventing you from registering IMediator as a singleton, HOWEVER, this forces every handler, and every handler's dependencies, to be singleton.

You could also provide your own implementation of IMediator to do what the ASP.NET Core code does above - create a scope and resolve handlers from that scope. I don't because I don't want to couple the Mediator implementation to scopes, and I have the control of just registering all the dependencies as Transient.

I forgot to mention - transients can depend on singletons. Just not the other way around.

Thanks @jbogard and @henkmollema, appreciate the detailed descriptions and help here. I'll just create the IMediator in my own scope as needed within the singleton. I made a nice little wrapper that takes a context and a Func creates the scope, grabs the IMediator from that scope and then runs given Func with mediator instance.

What would be the problem if the Mediator asked the container every time it needed an object? Wouldn't that allow it to be a singleton?

Nope, because that container instance could be a separate, scoped instance.

On Mon, May 28, 2018 at 3:35 PM Andre Carlucci notifications@github.com
wrote:

What would be the problem if the Mediator asked the container every time
it needed an object? Wouldn't that allow it to be a singleton?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/257#issuecomment-392599531,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMpGqJggnYpLoleDz83O_W_xvSr16ks5t3F-NgaJpZM4TZlQq
.

In that very unusual usage, the user could just register the IMediator as scoped as well, couldn't he?

For anyone who is still looking at this.

Makes sense to keep Mediator Scoped to limit domain of faults. You can persist mediator in your singleton by running:

var _mediator = (IMediator)serviceProvider.CreateScope().ServiceProvider.GetService(typeof(IMediator));

Store this object in your singleton and the initialized service will be persistent with your singleton service.

Alternatively you can create a function that returns you the mediator, call and keep based on your own disposing requirements.

public IMediator GetMediator(IServiceScopeFactory serviceProvider)
{
   return (IMediator)serviceProvider.CreateScope().ServiceProvider.GetService(typeof(IMediator));
}

I have tried to use this "CreateScope" on my Singletons, well, worked. But since my Requests uses DbContext (with npgsql), I have problems connecting to database... for the first run, everything is ok, after that, I receive a lot of error from npgsql. So, I changed my graphql schema to Scoped.. and now everything works as expected.
I wanted to make it work as Singleton, but couldn't make it happen.

It shouldn’t all be singleton. I don’t think you want DbContexts sticking
around forever.

On Thu, Dec 13, 2018 at 7:46 AM Rodrigo Boratto notifications@github.com
wrote:

I have tried to use this "CreateScope" on my Singletons, well, worked. But
since my Requests uses DbContext (with npgsql), I have problems connecting
to database... for the first run, everything is ok, after that, I receive a
lot of error from npgsql. So, I changed my graphql schema to Scoped.. and
now everything works as expected.
I wanted to make it work as Singleton, but couldn't make it happen.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/257#issuecomment-446973661,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMqiDHzvGXVxJuJf4fNKr528Irts-ks5u4lpDgaJpZM4TZlQq
.

Was this page helpful?
0 / 5 - 0 ratings