Even though Akka.NET supposedly supports dependency injection through its DI libraries (e.g. Akka.DI.Unity), it’s not very good. In the documentation you’ll see examples like:
Context.DI().Props<MyActor>()
That Props
call doesn’t take any arguments so there’s no way to actually inject parameters into the call. Let’s say we have this actor:
public class MyActor : ReceiveActor { public MyActor( string a, IFoo b, IBar c ) { // ... } }
Further say we want to specify a
but let Unity resolve IFoo
and IBar
from configuration. If we were just creating the object normally we would write:
var myActor = UnityContainer.Resolve<MyActor>( new ParameterOverride( "a", "ABC" ) );
But that’s not how we create actors. We create actors using ActorOf
and specify Props
. We do this so that Akka.Net can recreate the actors if they fail.
If we were to create the actor normally without dependency injection we would write something like:
var myActorRef = actorRefFactory.ActorOf( Props.Create( () => new MyActor( "ABC", ??, ?? ) );
If we do that, we aren’t getting our interfaces from Unity.
So let’s try dependency injection. If we were to use Akka.DI.Unity we would have:
var myActorRef = actorRefFactory.ActorOf( Context.DI.Props<MyActor>() );
But then there’s no way to specify our parameter.
We’re stuck. We can specify all parameters or none but can’t specify some and let unity take care of the others like we usually want.
Here’s a way you can do it that doesn’t involve the Akka.DI libraries:
IIndirectActorProducer
It turns out that Props
is not the only way to create an actor. If you dive into the source code, you can see that there’s a IIndirectActorProducer
class that will create an actor without Props
by implementing a Produce
method.
Here’s an implementation that takes a IUnityContainer
and the ResolverOverride
s in its constructor and uses them to create the actor when called:
/// <summary> /// A <see cref="IIndirectActorProducer" /> that uses a <see cref="IUnityContainer" /> to resolve instances. /// </summary> /// <remarks> /// This is only used directly by the <see cref="UnityActorRefFactory"/> /// </remarks> /// <typeparam name="TActor"></typeparam> internal sealed class UnityActorProducer<TActor> : IIndirectActorProducer where TActor : ActorBase { /// <summary> /// The resolver overrides. /// </summary> private readonly ResolverOverride[] _resolverOverrides; /// <summary> /// The unity container. /// </summary> private readonly IUnityContainer _unityContainer; /// <summary> /// The constructor. /// </summary> /// <param name="unityContainer">The unity container.</param> /// <param name="resolverOverrides">The resolver overrides.</param> public UnityActorProducer( IUnityContainer unityContainer, params ResolverOverride[] resolverOverrides ) { this._unityContainer = unityContainer.CreateChildContainer(); this._resolverOverrides = resolverOverrides; } /// <summary> /// See <see cref="IIndirectActorProducer.ActorType"/> /// </summary> public Type ActorType => typeof( TActor ); /// <summary> /// See <see cref="IIndirectActorProducer.Produce" /> /// </summary> /// <returns></returns> public ActorBase Produce() { // Create the actor using our overrides return this._unityContainer.Resolve<TActor>( this._resolverOverrides ); } /// <summary> /// SEe <see cref="IIndirectActorProducer.Release" /> /// </summary> /// <param name="actor"></param> public void Release( ActorBase actor ) { // Do nothing } }
We can create Props
using the producer like this:
/// <summary> /// Creates <see cref="Akka.Actor.Props" /> using the <see cref="IUnityContainer" /> and any <see cref="ResolverOverride" />s provided. /// </summary> /// <typeparam name="TActor"></typeparam> /// <param name="unityContainer"></param> /// <param name="resolverOverrides"></param> /// <returns></returns> public Props Props<TActor>( IUnityContainer unityContainer, params ResolverOverride[] resolverOverrides ) where TActor : ActorBase { // Use a UnityActorProducer to create the object using the container and resolver overrides. return Akka.Actor.Props.CreateBy<UnityActorProducer<TActor>>( unityContainer, resolverOverrides ); }
So creating our actor would look like this:
var myActorRef = Context.ActorOf( Props<MyActor>( unityContainer, new ParameterOverride( "a", "ABC" ) ) );
It uses our resolver overrides for the first parameter and the unity configuration for our additional parameters, just as we want.