Reputation: 40092
I am trying to inject a singleton of a service into an actor (Akka.NET) with ASP.NET Core's built-in DI container.
I have done the following in ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
// Build service provider
var provider = services.BuildServiceProvider();
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(provider.GetService<IMyService>()), "myactor");
}
The issue is that the instance of MyService in the actor is different from the instance that is injected into the rest of the application - i.e. it is not a singleton.
What am I doing wrong and is there a better way of doing this?
Upvotes: 3
Views: 1515
Reputation: 64298
That's because you create a separate IoC container inside your ConfigureServices
// Build service provider
var provider = services.BuildServiceProvider();
This line will create a new service provider (IoC container). When you resolve services from it, they are effectively singletons (since its not resolved from a scoped provider).
You shouldn't ever call .BuildServiceProvider()
inside your ConfigureServices
method, except when using 3rd party container and create it (i.e. when using Autofac).
Anyways, if you for some reason have to create the provider inside of ConfigureServices
you need to change signature of ConfigureServices
to
// Return value from void to IServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var provider = services.BuildServiceProvider();
// don't call services.AddXxx(..) after this point! The container is already created and its registrations can't be changed
...
return provider;
}
This will make ASP.NET Core use this container instead of creating its own one and passing that to Configure
Method.
While this may solve you immediate problem, its not very clean to do that kind of resolving inside ConfigureServices
and you should use the docs (or ask a separate question) on how to correctly use DI with Akka.NET (Sorry not familiar with it, I'm Microsoft Orleans user :)).
A slightly better (still not fully correct since it works around the idea of DI) way would be to delay the instantiation of the actor until Configure
method is called.
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(app.ApplicationServices.GetService<IMyService>()), "myactor");
}
or
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
// inject it in Configure
public void Configure(IApplicationBuilder app, IMyService myService)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(myService), "myactor");
}
This will initialize and resolve your services in Configure
.
P.S. keep in mind, you can't resolve scoped services from app.ApplicationServices
or the service provider, it will throw an exception. This may become an issue when you want to use DbContext which, by default is registered as scoped service.
You can also register it as scoped with an override to AddDbContext
, but be aware of "memory leaks", as the number of tracked objects grows, so will the memory consumption (and big number of tracked entities (>=10k) will decrease your tracker related operations significantly).
And with DbContext in mind, also keep in mind that EF and EF Core are not thread-safe, and can't be accessed by threads (or run multiple asynchronous operations, i.e. starting 5 queries w/o awaiting and then using await Task.WaitAll(...)
).
While an actor is guaranteed to only be accessed by a single thread at a single time, the services aren't if you scope them.
How well this works depends on the Task Scheduler implementation used by Akka.NET (again, not familiar with it's internals - i.e. Orleans abstracts persistence behind storage providers).
Upvotes: 3