Injecting a service singleton into actor (Akka.NET) in ASP.NET Core - 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?

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).

Related

Shared service provider NServiceBus and ASPNET Core

I'm creating a way to publish integration events via NServiceBus that are published from within an operation executed in a handler. The path I've chosen is bridge the IIntegrationEventProvider with IEventCollectionPublisher to get the published events from domain layer.
public sealed class Bridge : IIntegrationEventProvider /* Infrastructure */,
IEventCollectionPublisher /* Domain */
{
private readonly List<object> _events = new List<object>();
void IEventCollectionPublisher.Publish(object domainEvent) { _events.Add(domainEvent): }
IReadOnlyCollection IIntegrationEventProvider.GetEvents() => _events;
}
Since NServiceBus has its own service provider (IBuilder) I need to resolve the class doing the application operation from the IServiceProvider that is made available to pipeline in ServiceScopedBehavior. Doing this I can get the bridge instance that contains the events published from domain layer and publish them as integration events using NServiceBus.
I published a Gist with (hopefully) the code pieces needed to grasp what I'm trying to achieve.
The question is: can I instruct NServiceBus to just delegate calls to the application service provider instead of building it and copy all instructions in endpoint.UserContainer<ServiceBuilder>()? Below is an example
internal sealed class Handler : IHandleMessages<Command>
{
public async Task Handle(Command message, IMessageHandlerContext context)
{
// Resolved from ASPNET DI
var useCase = context.GetService<CommandUseCase>();
// _useCase is resolved NSB DI since injected from constructor
Debug.Assert(ReferenceEquals(useCase, _useCase), "");
await useCase.Execute().ConfigureAwait(false);
}
}
This way I could inject to correct scoped application class in the handler constructor instead of resolving it from the scope provided by IServiceProvider that is made available from context.Extensions.Get<IServiceScope>().ServiceProvider.
Thanks for help
Regards
I think ASP.NET Core integration sample could be useful. Starting from version 7.2 sharing of the DI infrastructure between ASP.NET and NServiceBus is much simpler. There is also a specialized NServiceBus.Extensions.Hosting adapter package that adds UseNServiceBus API.

Getting Hub Context for ASPNet.Core Signal-R (.NET Core 2.1) RC

I'm using ASP.NET Core 2.1 RC1.
I'm also using Signal-R for it (found here):
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.1
I'm creating a .NET Core console application that's hosting Kestrel and using Signal-R. I've pretty much set it up exactly as the getting started documentation states for setting up the Startup.
This all works great. I'm able to connect to the it, get my HTML with signal-R script in it, receive messages I crafted with Clients.All.SendAsync. Works great.
BUT
I want to be able to send a message to clients, from outside the Hub. Where some event happens in my application, and a message is sent to clients. In full .NET, I'd use the GlobalHost and get the context. In ALL my searches on Stack Overflow, they reference something that no longer works, or used within an REST API controller that's passed in the IHubContext.
I have an event listener in my program.cs, and when the event is triggered, I'd love to be able to send a message to my UserInterfaceHub.
So -- how do I get the hub context in Program.CS - so I can send messages to it (call the SwitchUI method) from within an event delegate I have in Program.CS?
StartUp.cs
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc();
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder => {builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials();}));
services.AddSignalR();
var provider = services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
app.UseMvc();
//app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
}
Program.CS
CreateWebHostBuilder(args)
.UseKestrel()
.UseUrls("http://0.0.0.0:" + appProperties.HostPort.ToString().Trim())
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Build()
.Start();
UserInterfaceHub.cs
namespace InterfaceModule.Hubs {
public class UserInterfaceHub : Hub {
public async Task SwitchUI(string message) {
await Clients.All.SendAsync("ReceiveEvent", message);
}
public override async Task OnConnectedAsync() {
//await SwitchUI("HOWDY NEW PERSON!");
await base.OnConnectedAsync();
}
}
}
edit adding clarity.
In Program.CS, I have this event delegate:
//now that we're ready, start listening.
await
deviceClient.SetInputMessageHandlerAsync(ModuleProperties.InputName, OnReceiveEvent, deviceClient);
Console.WriteLine("INIT: Event Message Input handler created: [{0}]", ModuleProperties.InputName);
which is this:
static async Task<MessageResponse> OnReceiveEvent(Message message, object userContext) {
//HOW DO I REACH THE HUB FROM HERE SO I CAN SEND A MESSAGE TO THE LISTENERS?
}
I encountered a similar situation and here's how to resolve it:
In your Service layer, create an interface called something like ISendHubMessage. Have a method called Send() that takes parameters that you're wanting to send via SignalR. Create a class within the same file called SendHubMessage that implements the interface. Have it just do a return.
In your top-level project (where your Startup.cs file is located) create another class called SendHubMessage that implements that same ISendHubMessage interface from your Service layer. Within this SendHubMessage, you can use DI to get at the hub as explained above. This method will do the actual logic of sending via SignalR.
In your Startup ConfigureServices() method, add the following line:
services.AddTransient<"Service".ISendHubMessage, "TopLevel".SendHubMessage>();
(where "Service" is the namespace to your Service-level project and "TopLevel" in the namespace to your top-level project).
What you're doing with this line is saying "Whenever an object requests the ISendHubMessage dependency from the Service layer, supply it with the SendHubMessage class defined in my top-level project".
Finally, in all the places in code outside of your top-level project that you're wanting to send messages through your hub, inject that ISendHubMessage dependency in the constructor. You can then refer to it in the class methods and when you call Send(), it will call the Send() method defined in your SendHubMessage class in your top-level project.
This line of code:
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
will register your hub with the DI container. Then to get access to it, you either use constructor injection to inject in the IHubContext<UserInterfaceHub> (this works for example in a Web Controller) or access it directly from the DI container by doing the following:
var hub = app.ApplicationServices.GetRequiredService<IHubContext<UserInterfaceHub>>();
(for example if executed in the startup.cs Configure method)
If you don't have access to the app.ApplicationServices which is basically an IServiceProvider at the location you need to access the hub, then you will need to either 1) get that class to work with dependency injection to inject in the IHubContext<UserInterfaceHub> or IServiceProvider 2) Setup a static Services global var via Configure so that you can have access to one of them globally, or find some other way to access the DI container (aka IServiceProvider) to get your hub via the above line of code.
Once you have your hub, then sending the message to the registered clients is a simple as calling the method on your hub.
await hub.Clients.All.SendAsync("ReceiveEvent", message);
You're question is a little unclear, but I'm assuming you mean you want to replace the following with something that can send a message through your hub:
app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
Since this is in your Configure method, you can simply add IServiceCollection services to your Configure methods params. Then, you can do:
var hub = services.GetRequiredService<IHubContext<MyHub>>();
However, I'm not sure that this will actually do anything useful ultimately. At startup, you'd logically have no clients with subscriptions yet. As a result, sending a message through your hub at this point, would essentially go nowhere. By the time a user actually hits your site and gets connected to your hub, this part of your application has already run, and won't be hit again.

How create a middleware with api endpoints in .NET Core

I have created the web application with the web api. The application contains some Controllers for example TodoController:
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly TodoContext _context;
public TodoController(TodoContext context)
{
_context = context;
}
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
}
}
If I create the GET request - /api/todo - I get the list of Todos from database.
I have a list of controllers and api endpoints like above.
I would like distribute this api to another application ideally like middleware - my idea is register in Startup.cs like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddTodoApi();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseTodoApi();
}
This will be awesome use case for my api but I don't know how this controllers api endpoints rewrite like middleware and return same JSON data same approache like using classic Controllers.
How can I write the middleware in .NET Core for creating API endpoints?
Instead of the separate middleware, you may configure the MVC middleware to discovery controllers from another assembly:
// using System.Reflection;
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddMvc()
.AddApplicationPart(typeof(TodoController).GetTypeInfo().Assembly);
Controllers are part of MVC middleware, they are not a separate part of request pipeline (but this is what middlewares are). When you register the custom middleware, it by default invokes on each request and you have HttpContext context as an input parameter to work with/edit
Request/Response data. But ASP.NET Core provides Map* extensions that are used as a convention for branching the pipeline.
Map branches the request pipeline based on matches of the given request path. If the request path starts with the given path, the branch is executed.
Example:
private static void HandleMapTodo(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("/api/todo was handled");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/api/todo", HandleMapTodo);
}
Note, that as middleware knows nothing about MVC middleware, you have only access to "raw" request and do not have features like model binding or MVC action filters.
Because it looks like the perfect microservices approach (similar than what my team is doing right now) I'd create a client assembly that can consume your API, the one that contains your TodoController, if you define a contract, and interface, for that API you can register it in your other assembly as it was a midleware and also you could mock that behaviour in your unit tests.
So, as I said, you could inject your client in ConfigureServices method, you can create:
public static IServiceCollection AddTodoRestClient(this IServiceCollection services)
{
services.AddSingleton<ITodoRestClient, TodoRestClient>();
return services;
}
Also consider that you will need to provide the enpoint so, it might looks like:
public static IServiceCollection AddConfiguredTodoClient(this IServiceCollection services, string todoEndpoint)
{
AddTodoClient(services);
ITodoRestClient todoRestClient = services.BuildServiceProvider().GetService<ITodoRestClient>();
// Imagine you have a configure method...
todoRestClient.Configure(services, todoEndpoint);
return services;
}
You can create those methods in a TodoRestClientInjector class and use them in Configure method on your startup.
I hope it helps
--- MORE DETAILS TO ANSWER COMMENTS ---
For me TodoClient is a Rest client library that implements calls to the ToDo API, (I've edited previous code to be TodoRestClient) methos like, i.e., CreateTodoItem(TodoDto todoItem) which implementation would call to the TodoController.Post([FromBody] item) or GetTodos() which wuold call TodoController.Get() and so on and so forth....
Regarding the enpoints... This approach implies to have (at least) two different applications (.NET Core apps), on the one hand the ASP NET Core app that has your TodoController and on the other hand a console application or another ASP NET Core API on which startup class you'll do the inyection adn the Rest client (the Todo Rest client) configuration ...
In a microservices approach using docker, in a dev environment, you'll use docker-compose-yml, but in a traditional approach you'll use concrete ports to define the endpoints...
So, imagine that you have in the second service a controller that need to use TodoController, to achieve so I'll use the above aproach and the "SecondController" would look like:
public class SecondController : Controller
{
private readonly SecondContext _context;
private readonly TodoRestClient _todoRestClient;
public TodoController(SecondContext context, ITodoRestClient todoRestClient)
{
_context = context;
_todoRestClient= todoRestClient;
}
// Whatever logic in this second controller... but the usage would be like:
_todoRestClient.GetTodos()
}
Just few final hints: it's key to minimize calls between services because it increases latency, and more and more if this happens on cascade. Also consider Docker usage, looks challenging but it is quite easy to start and, indeed, is thought to be used in scenarios that the one you presented and solutions like mine.
Again, I hope it helps.
Juan

Autofac with self hosted WCF service and object disposal

I'm injecting a business layer into a self-hosted (InstanceContextMode=per-call) WCF service by leveraging AutoFac's ServiceHost.AddDependencyInjectionBehavior() extension (as described in the Autofac documentation)
My business layer uses components that cannot be re-created each time a new request comes in (let's say it needs a persistent database connection).
As such, when building the container I'd like to register the BL service as a Single instance, i.e.:
builder.RegisterType<BusinessLayer>()
.SingleInstance();
The business-layer injection into the WCF service is working fine; my issue is that:
Dispose() is not called on ANY services created in the container: not just the Business layer itself, but the 'persistent' services too.
I'd expect this to happen for the BL service itself. From the Autofac docs again:
If you have singleton components (registered as SingleInstance()) they will live for the life of the container. Since container lifetimes are usually the application lifetime, it means the component
won’t be disposed until the end of the application.
, but why are none of my 'child' (Autofac-registered) services (i.e the 'IPersistentService' below) disposed when the lifetime scope is -- given that I'm not explicitly making them 'SingleInstance'?
Note:: this is still the case if I manually dispose the business layer service within the lifetime scope after I close the ServiceHost
E.g. (IDisposable implementations omitted for brevity):
[ServiceContract]
public interface IMyService
{
void DoStuff();
}
public class MyService : IMyService
{
IBusinessLayer _bl;
public MyService(IBusinessLayer bl)
{
_bl = bl;
}
public void DoStuff()
{
_bl.BLDoStuff();
}
}
public interface IBusinessLayer
{
void BLDoStuff();
}
public class BusinessLayer : IBusinessLayer
{
IPersistentService _service;
public BusinessLayer(IPersistentService service)
{
_service = service;
}
public void BLDoStuff()
{
// Do something that requires a 'cached' / persistent component
_service.DoSomethingWithPersistentConnection();
}
}
public interface IPersistentService : IDisposable
{
void DoSomethingWithPersistentConnection();
}
With Autofac registrations looking something like:
builder.RegisterType<BusinessLayer>()
.SingleInstance();
builder.RegisterType<MyPersistentService>()
.As<IPersistentService>()
.OnActivated(e => e.Instance.Start());
As Steven mentioned, what you experience here is a Captive Dependency problem. In other words, a singleton (BusinessLayer, registered with .SingleInstance()) keeps a lifetime-scoped or transient dependency (MyPersistentService, registered by default as transient).
Put it this way, dependencies of singleton services will always be singletons themselves, no matter how they were registered in the container. The diagram in Mark Seeman's article that Steven linked to gives a good view of this.
I think you can achieve what you expect, but your registrations are wrong.
My business layer uses components that cannot be re-created each time a new request comes in (let's say it needs a persistent database connection).
As such, when building the container I'd like to register the BL service as a Single instance
This is where the problem is. It's the dependency of the business service that has to be registered as a singleton, not the business service itself. This means you could have Autofac create a different instance of BusinessLayer for each WCF call, but the MyPersistentService instance injected into would always be the same. Does this make sense? Your registrations would then look like:
builder
.RegisterType<BusinessLayer>()
.As<IBusinessLayer>()
.InstancePerLifetimeScope(); // a new instance per WCF call
builder
.RegisterType<MyPersistentService>()
.As<IPersistentService>()
.OnActivated(e => e.Instance.Start())
.SingleInstance(); // one same instance for the lifetime of the application
The one instance of MyPersistenService would then be disposed of only after disposing the root container (that you created by calling builder.Build()) after you close the Service Host.

Add database context to service in configureServices

Is it possible to access the database context in a self created service? In the code beneath i want to set the database context with the constructor of the exampleClassService.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//Add MVC
services.AddMvc();
//Postgres connection
var connectionString = Configuration["ConnectionStrings:PostgresConnection"];
services.AddDbContext<ApplicationDbContext>(
opts => opts.UseNpgsql(connectionString)
);
//Own created service
services.AddSingleton<ExampleClassService>(
provider => new ExampleClassService(dbcontext?);
...
}
In "default" asp.net core:
If your ExampleClassService have constructor like:
public ExampleClassService(ApplicationDbContext dbContext)
then you do not need to add any special factories - this parameter will be injected by DI layer automatically (because you already registered ApplicationDbContext earlier), just write services.AddSingleton<ExampleClassService>()
If your service have some "special" constructor, then use provider param to obtain required dependencies:
services.AddSingleton<ExampleClassService>(
provider => new ExampleClassService((ApplicationDbContext)provider.GetService(typeof(ApplicationDbContext)));
But! In your sample you have registration of ApplicationDbContext as "scoped" and ExampleClassService as "singleton" - are you REALLY sure that you can/may/need use singleton class (single for whole app) ExampleClassService that use some other (DbContext) class that you require to be different for each scope/request?
May be you have design errors here, may be ExampleClassService must be "scoped" too or must not require DbContext in constructor.