Skip to content

Commit

Permalink
close #4454 - added full ASP.NET Core + DI sample (#4727)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaronontheweb committed Jan 20, 2021
1 parent b11c095 commit 466d7fc
Show file tree
Hide file tree
Showing 12 changed files with 332 additions and 1 deletion.
20 changes: 19 additions & 1 deletion src/Akka.sln
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Cluster.Metrics.Com
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.DependencyInjection", "contrib\dependencyinjection\Akka.DependencyInjection\Akka.DependencyInjection.csproj", "{4B022023-837F-4FDC-930E-BC1B5105E87C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.DependencyInjection.Tests", "contrib\dependencyinjection\Akka.DependencyInjection.Tests\Akka.DependencyInjection.Tests.csproj", "{38F5A7D9-081E-4896-8CB3-C92234CBA592}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.DependencyInjection.Tests", "contrib\dependencyinjection\Akka.DependencyInjection.Tests\Akka.DependencyInjection.Tests.csproj", "{38F5A7D9-081E-4896-8CB3-C92234CBA592}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{162F5991-EA57-4221-9B70-F9B6FEC18036}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Akka.AspNetCore", "examples\AspNetCore\Samples.Akka.AspNetCore\Samples.Akka.AspNetCore.csproj", "{D62F4AD6-318F-4ECC-B875-83FA9933A81B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -1093,6 +1097,18 @@ Global
{38F5A7D9-081E-4896-8CB3-C92234CBA592}.Release|x64.Build.0 = Release|Any CPU
{38F5A7D9-081E-4896-8CB3-C92234CBA592}.Release|x86.ActiveCfg = Release|Any CPU
{38F5A7D9-081E-4896-8CB3-C92234CBA592}.Release|x86.Build.0 = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|x64.ActiveCfg = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|x64.Build.0 = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|x86.ActiveCfg = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Debug|x86.Build.0 = Debug|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|Any CPU.Build.0 = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|x64.ActiveCfg = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|x64.Build.0 = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|x86.ActiveCfg = Release|Any CPU
{D62F4AD6-318F-4ECC-B875-83FA9933A81B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1197,6 +1213,8 @@ Global
{30894FD2-A04A-4543-9E19-BAA4E246534D} = {95A617E2-20A4-4EF8-9686-44D9D19749F8}
{4B022023-837F-4FDC-930E-BC1B5105E87C} = {48845655-5E60-40AB-A798-F118CC1CF510}
{38F5A7D9-081E-4896-8CB3-C92234CBA592} = {48845655-5E60-40AB-A798-F118CC1CF510}
{162F5991-EA57-4221-9B70-F9B6FEC18036} = {D3AF8295-AEB5-4324-AA82-FCC0014AC310}
{D62F4AD6-318F-4ECC-B875-83FA9933A81B} = {162F5991-EA57-4221-9B70-F9B6FEC18036}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Akka.DependencyInjection;
using Akka.Routing;
using Microsoft.Extensions.Hosting;
using Samples.Akka.AspNetCore.Messages;
using Samples.Akka.AspNetCore.Services;
using ServiceProvider = Akka.DependencyInjection.ServiceProvider;

namespace Samples.Akka.AspNetCore.Actors
{
/// <summary>
/// Implements <see cref="IPublicHashingService"/>, which is the public interface used by ASP.NET Core.
/// </summary>
public class AkkaService : IPublicHashingService, IHostedService
{
private ActorSystem _actorSystem;
public IActorRef RouterActor { get; private set; }
private readonly IServiceProvider _sp;

public AkkaService(IServiceProvider sp)
{
_sp = sp;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
var hocon = ConfigurationFactory.ParseString(await File.ReadAllTextAsync("app.conf", cancellationToken));
var bootstrap = BootstrapSetup.Create().WithConfig(hocon);
var di = ServiceProviderSetup.Create(_sp);
var actorSystemSetup = bootstrap.And(di);
_actorSystem = ActorSystem.Create("AspNetDemo", actorSystemSetup);

// props created via IServiceProvider dependency injection
var hasherProps = ServiceProvider.For(_actorSystem).Props<HasherActor>();
RouterActor = _actorSystem.ActorOf(hasherProps.WithRouter(FromConfig.Instance), "hasher");

await Task.CompletedTask;
}

public async Task StopAsync(CancellationToken cancellationToken)
{
// theoretically, shouldn't even need this - will be invoked automatically via CLR exit hook
// but it's good practice to actually terminate IHostedServices when ASP.NET asks you to
await CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance);
}

public async Task<HashReply> Hash(string input, CancellationToken token)
{
return await RouterActor.Ask<HashReply>(input, token);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Akka.Actor;
using Akka.Event;
using Microsoft.Extensions.DependencyInjection;
using Samples.Akka.AspNetCore.Messages;
using Samples.Akka.AspNetCore.Services;

namespace Samples.Akka.AspNetCore.Actors
{
public class HasherActor : ReceiveActor
{
private readonly ILoggingAdapter _log = Context.GetLogger();
private readonly IServiceScope _scope;
private readonly IHashService _hashService;

public HasherActor(IServiceProvider sp)
{
_scope = sp.CreateScope();
_hashService = _scope.ServiceProvider.GetRequiredService<IHashService>();

Receive<string>(str =>
{
var hash = _hashService.Hash(str);
Sender.Tell(new HashReply(hash, Self));
});
}

protected override void PostStop()
{
_scope.Dispose();

// _hashService should be disposed once the IServiceScope is disposed too
_log.Info("Terminating. Is ScopedService disposed? {0}", _hashService.IsDisposed);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Akka.Actor;

namespace Samples.Akka.AspNetCore.Messages
{
/// <summary>
/// Used to include both the hash and the actor who did the hashing, just for fun.
/// </summary>
public class HashReply
{
public HashReply(int hash, IActorRef hasher)
{
Hash = hash;
Hasher = hasher;
}

public int Hash { get; }

public IActorRef Hasher { get; }
}
}
26 changes: 26 additions & 0 deletions src/examples/AspNetCore/Samples.Akka.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Samples.Akka.AspNetCore
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>$(NetCoreTestVersion)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="app.conf" />
</ItemGroup>
<ItemGroup>
<Content Include="app.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\contrib\dependencyinjection\Akka.DependencyInjection\Akka.DependencyInjection.csproj" />
<ProjectReference Include="..\..\..\core\Akka\Akka.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Akka.Util;

namespace Samples.Akka.AspNetCore.Services
{
/// <summary>
/// A simple service type we're going to use to test DI
/// </summary>
public interface IHashService : IDisposable
{
bool IsDisposed { get; }

int Hash(string input);
}

/// <summary>
/// Service implementation that will throw when disposed
/// </summary>
public sealed class HashServiceImpl : IHashService
{
private bool _isDisposed;

public void Dispose()
{
_isDisposed = true;
}

public bool IsDisposed => _isDisposed;

public int Hash(string input)
{
if(_isDisposed)
throw new ObjectDisposedException("HashServiceImpl disposed");

return MurmurHash.StringHash(input);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// //-----------------------------------------------------------------------
// // <copyright file="IPublicHashingService.cs" company="Akka.NET Project">
// // Copyright (C) 2009-2021 Lightbend Inc. <http://www.lightbend.com>
// // Copyright (C) 2013-2021 .NET Foundation <https://github.com/akkadotnet/akka.net>
// // </copyright>
// //-----------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;
using Akka.Actor;
using Microsoft.Extensions.Hosting;
using Samples.Akka.AspNetCore.Messages;

namespace Samples.Akka.AspNetCore.Services
{
/// <summary>
/// Service meant to be exposed directly to ASP.NET Core HTTP routes
/// </summary>
public interface IPublicHashingService
{
Task<HashReply> Hash(string input, CancellationToken token);
}
}
58 changes: 58 additions & 0 deletions src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Samples.Akka.AspNetCore.Actors;
using Samples.Akka.AspNetCore.Services;

namespace Samples.Akka.AspNetCore
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// set up a simple service we're going to hash
services.AddScoped<IHashService, HashServiceImpl>();

// creates instance of IPublicHashingService that can be accessed by ASP.NET
services.AddSingleton<IPublicHashingService, AkkaService>();

// starts the IHostedService, which creates the ActorSystem and actors
services.AddHostedService<AkkaService>(sp => (AkkaService)sp.GetRequiredService<IPublicHashingService>());

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
var hashing = context.RequestServices.GetRequiredService<IPublicHashingService>();
var hash = await hashing.Hash(context.TraceIdentifier, cts.Token);
await context.Response.WriteAsync(
$"Actor [{hash.Hasher}] hashed TraceIdentifier [{context.TraceIdentifier}] into [{hash.Hash}]");
});
});
}
}
}
9 changes: 9 additions & 0 deletions src/examples/AspNetCore/Samples.Akka.AspNetCore/app.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
akka {
actor {
deployment{
/hasher {
router = random-pool
nr-of-instances = 10
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
10 changes: 10 additions & 0 deletions src/examples/AspNetCore/Samples.Akka.AspNetCore/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

0 comments on commit 466d7fc

Please sign in to comment.