Dave New
Dave New

Reputation: 40092

Injecting a service singleton into actor (Akka.NET) in ASP.NET Core

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

Answers (1)

Tseng
Tseng

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.

Remarks regarding singletons, scopes and actors

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

Related Questions