Akka.NET, ASP.NET Core, Hosted Services, and Dependency Injection
Akka.Hosting trivializes integrating Akka.NET with everything else in .NET.
We first introduced Akka.Hosting a couple of years ago and released it to market one of the major pillars of Akka.NET v1.5. Without any exaggeration, it is the single best usability improvement we have ever made to Akka.NET and we are furiously rewriting all of the official Akka.NET documentation and training courses to prioritize it.
In that same vein, we’re going to introduce how you can use Akka.Hosting to easily integration Akka.NET with:
- Microsoft.Extensions.DependencyInjection - injecting things into actors and actors into things;
- ASP.NET Core / SignalR / gRPC / etc - anything that can be injected via MSFT.EXT.DI really; and
- Running Akka.NET in headless
IHostedServices
for things like stand-alone Windows Services.
Read on.
Integrating Akka.NET with Microsoft.Extensions.DependencyInjection
Akka.Hosting leverages Akka.DependencyInjection in order to make it possible to start actors with a blend of DI-injected and non-injected arguments from the IServiceProvider
.
I covered all of the guts of this in our new video, “Everything You Wanted to Know about Dependency Injection and Akka.NET”
However, a simple example using Akka.Hosting (from Akka.Templates
, which you should install):
using Akka.Hosting;
using AkkaConsoleTemplate.App;
using Microsoft.Extensions.Hosting;
var hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices((context, services) =>
{
services.AddAkka("MyActorSystem", (builder, sp) =>
{
builder
.WithActors((system, registry, resolver) =>
{
var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor");
registry.Register<HelloActor>(helloActor);
})
.WithActors((system, registry, resolver) =>
{
var timerActorProps =
resolver.Props<TimerActor>(); // uses Msft.Ext.DI to inject reference to helloActor
var timerActor = system.ActorOf(timerActorProps, "timer-actor");
registry.Register<TimerActor>(timerActor);
});
});
});
var host = hostBuilder.Build();
await host.RunAsync();
In this instance, we aren’t directly registering any services aside from Akka.NET itself using the IServiceCollection
- the AddAkka
method is how we register our ActorSystem
, ActorRegistry
, and a background IHostedService
that will be responsible for managing the lifecycle of your actors and the ActorSystem
inside the .NET IHost
.
However, take note of the following call:
.WithActors((system, registry, resolver) =>
{
// IActorRef
var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor");
// registers IActorRef with key `HelloActor`
registry.Register<HelloActor>(helloActor);
})
This registers the IActorRef helloActor
into the ActorRegistry
, which makes this type accessible via the IRequiredActor<TKey
dependency injection service:
public class TimerActor : ReceiveActor, IWithTimers
{
private readonly IActorRef _helloActor;
public TimerActor(IRequiredActor<HelloActor> helloActor)
{
_helloActor = helloActor.ActorRef;
Receive<string>(message =>
{
_helloActor.Tell(message);
});
}
protected override void PreStart()
{
Timers.StartPeriodicTimer("hello-key", "hello", TimeSpan.FromSeconds(1));
}
public ITimerScheduler Timers { get; set; } = null!; // gets set by Akka.NET
}
In this instance, the constructor of the TimerActor
takes a DI’d argument of IRequiredActor<HelloActor>
- and this syntax will also work for ASP.NET Core, SignalR, gRPC, IHostedService
, and more - which we’ll see a bit later.
The ActorRegistry
is an Akka.Hosting-specific construct aimed at making it easier to inject actors directly into non-actor things, but it also works perfectly well with Akka.DependencyInjection and actors that can be started via DI.
And here’s how we do just that - inject DI’d arguments into actors:
.WithActors((system, registry, resolver) =>
{
var timerActorProps =
resolver.Props<TimerActor>(); // uses Msft.Ext.DI to inject reference to helloActor
var timerActor = system.ActorOf(timerActorProps, "timer-actor");
registry.Register<TimerActor>(timerActor);
});
The resolver
argument corresponds to the DependencyResolver
from Akka.DependencyInjector, a razor-thin wrapper around the IServiceProvider
- we simply use it to create the Props<TimerActor>
and that will cause this actor to resolve any arguments not specified inside the Props()
method from the IServiceProvider
directly.
Integrating Akka.NET with ASP.NET Core
Once you’ve gotten onboard with Akka.Hosting, integrating Akka.NET with ASP.NET Core is trivial - as I show in “Using Akka.NET and ASP.NET Core Together”
This bit of code comes from our “SQL Sharding” example project - a Razor Pages application that uses Akka.Cluster.Sharding and Akka.Cluster Singletons to communicate with another service running in a separate process:
var builder = WebApplication.CreateBuilder(args);
// [config stuff]
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddAkka("SqlSharding", (configurationBuilder, provider) =>
{
configurationBuilder.WithRemoting(hostName, port)
.AddAppSerialization()
.WithClustering(new ClusterOptions
{ Roles = new[] { "Web" }, SeedNodes = seeds })
.WithShardRegionProxy<ProductMarker>("products", ProductActorProps.SingletonActorRole,
new ProductMessageRouter())
.WithSingletonProxy<ProductIndexMarker>("product-proxy",
new ClusterSingletonOptions { Role = ProductActorProps.SingletonActorRole });
// [other actor registrations]
});
Same setup as what we did before, but with a minimal WebApplication
setup instead.
If we take a look at our corresponding Razor Page PageModel
:
public class Product : PageModel
{
private readonly IActorRef _productActor;
public Product(IRequiredActor<ProductMarker> productActor)
{
_productActor = productActor.ActorRef;
}
[BindProperty(SupportsGet = true)]
public string ProductId { get; set; }
[BindProperty]
[Required]
[Range(1, 10000)]
public int Quantity { get; set; }
public ProductState State { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var result = await _productActor.Ask<FetchResult>(new FetchProduct(ProductId), TimeSpan.FromSeconds(3));
State = result.State;
if (State.IsEmpty) // no product with this id
return NotFound();
return Page();
}
}
The Product
page model takes an IRequiredActor<ProductMarker>
in its constructor, which will be resolved via ASP.NET Core’s IServiceProvider
at the time an HTTP request is sent to the corresponding route for this particular page. The ProductMarker
corresponds to the ShardRegionProxy
for the products
entity actors hosted in the other service. My ASP.NET resources can just interact with those actors no problem when we have Akka.Hosting hooked up to the IServiceCollection
.
Running Akka.NET as a Headless Service with Microsoft.Extensions.Hosting
Suppose you want to be able to run Akka.NET as a Windows Service, Linux Service, or just a headless process inside a platform like Kubernetes? Combining Akka.Hosting with Microsoft.Extensions.Hosting is the best way to go about that. I covered this at full length in “Building Headless Akka.NET Services.”
Our original Akka.DependencyInjection code sample illustrated a basic headless Akka.NET service quite well:
using Akka.Hosting;
using AkkaConsoleTemplate.App;
using Microsoft.Extensions.Hosting;
var hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices((context, services) =>
{
services.AddAkka("MyActorSystem", (builder, sp) =>
{
builder
.WithActors((system, registry, resolver) =>
{
var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor");
registry.Register<HelloActor>(helloActor);
})
.WithActors((system, registry, resolver) =>
{
var timerActorProps =
resolver.Props<TimerActor>(); // uses Msft.Ext.DI to inject reference to helloActor
var timerActor = system.ActorOf(timerActorProps, "timer-actor");
registry.Register<TimerActor>(timerActor);
});
});
});
var host = hostBuilder.Build();
await host.RunAsync();
This will run Akka.NET using the default IHostedService
launched as part of Akka.Hosting’s AddAkka
method, but you can also run Akka.NET inside BackgroundService
s and other custom IHostedService
implementations too:
public class MyBackgroundService : BackgroundService
{
private readonly IRequiredActor<TestActorKey> _testActor;
public MyBackgroundService(IRequiredActor<TestActorKey> requiredActor)
{
_testActor = requiredActor;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// asynchronously request the background actor
var myRef = await _testActor.GetAsync(stoppingToken);
var result = myRef.Ask<LongRunningOperationResult>(Start.Instance, cancellationToken:stoppingToken);
// [result handling code]
}
}
Conclusion
The short version of this post - Akka.Hostingtremendously simplifies Akka.NET, just use it!
One of the best ways to get started with it in a new project is to use our Akka.Templates
NuGet package:
dotnet new install "Akka.Templates::*"
This will make all of our templates available for the dotnet new
CLI but also as new project templates in Visual Studio, Rider, and VS Code.
- Read more about:
- Akka.NET
- Case Studies
- Videos
Observe and Monitor Your Akka.NET Applications with Phobos
Did you know that Phobos can automatically instrument your Akka.NET applications with OpenTelemetry?
Click here to learn more.