As the title states, when I register multiple instances of IHostedService, it calls StartAsync twice on the first instance, but not the second, but it does call both constructors.
Program.cs
services.AddSingleton<IHostedService, ProductService>(provider => (ProductService)provider.GetService<IProductService>()!);
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<IHostedService, ProductService>(provider => (ProductService)provider.GetService<IProductService>()!);
services.AddSingleton<IProductService, ProductService>();
ProductService.cs
public class ProductService : IProductService, IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken) { }
public async Task StopAsync(CancellationToken cancellationToken) { }
}
How can I solve this? I need multiple instances of ProductService (name changed for simplicity).
To spin up 2 hosted services of the same type, it suffices to register the same service 2 times with a transient lifetime scope.
Each will have a constructor call and a StartAsync call, which you can verify via the value of the Guid field in the example ProductService below.
services.AddTransient<IHostedService, ProductService>();
services.AddTransient<IHostedService, ProductService>();
This post explains that a hosted service used to be a long lived transient with the behavior of a singleton.
public class ProductService : IHostedService
{
private readonly Guid _id = Guid.NewGuid();
public ProductService( /* Any dependencies */ )
{ }
public Task StartAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}
Well, it's hard to say without your implementation of StartAsync but StartAsync method is intended to be non-blocking.
Schedule any work you need and then finish StartAsync.
StartAsync method of the second instance is waiting for the first one to finish starting before it tries to start the next one.
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(() => SomeWork(cancellationToken));
return Task.CompletedTask;
}
You can use a factory to maintain n number of instances. i just added a sample
class ProductFactory : IProductFactory
{
private readonly Dictionary<string, ProductService> _productService ;
public void Register(string name, ProductService productService)
{
_productService[name] = productService;
}
public HttpClient Resolve(string name)
{
return _productService[name];
}
}
var factory = new ProductFactory();
factory.Register("p1", new ProductService());
factory.Register("p2", new ProductService());
services.AddSingleton<IProductFactory>(factory);
public ProductController(IProductFactory factory)
{
_productFactory = factory.Resolve("p1");
}
You can use factory not to start .
Related
In my ASP.Net Core 3.1 webapi, I'm registering the IHttpContextAccessor as a singleton and injecting it into all my controllers. I have an interface that also gets injected into all my controllers and my services (which in turn connect to the db). The implementation is:
public class PrincipalProvider : IPrincipalProvider
{
private readonly UserPrincipal principal;
public PrincipalProvider(IHttpContextAccessor accessor)
{
accessor.HttpContext.Items.TryGetValue("principal", out object principal);
this.principal = principal as UserPrincipal;
}
public UserPrincipal GetPrincipal()
{
return principal;
}
}
The ctor of a service looks like:
public MyService(
IPrincipalProvider provider,
ILogger<MyService> logger,
IUnitOfWork unitOfWork) : base(provider, logger, unitOfWork)
{ }
All the above works as expected as long as I'm within the request context.
I have a controller action that starts a background task using the new IHostedService implementation with a background queue, and it gets started like this:
backgroundQueue.QueueBackgroundWorkItem(async (scope, hubContext, ct) =>
{
await hubContext.Clients.Client(provider.GetPrincipal().ConnectionId).Notify();
var myService = scope.Resolve<IMyService>();
}
where scope is ILifetimeScope and hubConext is IHubContext<MyHub, IMyHub>. The provider variable is the IPrincipalProvider that was injected into the controller ctor.
The problem is that when I try to resolve IMyService within the task, it creates an instance of IPrincipalProvider and that in turn requires IHttpContextAccessor which doesn't exist anymore.
What is the solution in this case? Do I need to have a second ctor on the service with a different IPrincipalProvider which gets the context from somewhere else? And if that's the case, from where?
The nicest solution would be to have 2 implementations of IPrincipalProvider, the one that use the httpContextAccessor and another one that use something else. Unfortunately it is not always easy to have the other implementation.
When you create the child lifetimeScope you can add registration to this child lifetime scope. You can register a StaticPrincipalProvider here.
private async Task BackgroundProcessing(...) {
...
try {
using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope(builder => {
builder.RegisterInstance(new StaticPrincipalProvider(principal))
.As<IPrincipalProvider>();
})){
await workItem(queueScope, stoppingToken);
}
}
...
}
All you have to do now is to find a way to get the corresponding principal when you dequeue the task. To do this you can change the implementation of BackgroundTaskQueue to use a ConcurrentQueue<WorkItem> instead of ConcurrentQueue<Func<ILifetimeScope, CancellationToken, Task>> where WorkItem is
public class WorkItem {
public Func<ILifetimeScope, CancellationToken, Task> Work { get; private set; }
public IPrincipal Principal { get; private set; }
// or
public Action<ContainerBuilder> builderAccessor { get; private set; }
}
and because BackgroundTaskQueue is instanciated with a request scope you will have access to the current principal.
I created the default ASP.NET Core Web Application MVC project. Then, I added this.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddHostedService<MyService>(); //<-- What I added.
}
MyService is a dummy class that implements IHostedService
public class MyService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Debug.WriteLine("Hello world");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Debug.WriteLine("Goodbye world");
return Task.CompletedTask;
}
}
Now, in the HomeController, I tried the following two things to get the instance of MyService, but both caused not-found or null exceptions.
public IActionResult Index([FromServices] MyService ms)
{
public IActionResult Index([FromServices] IServiceProvider sp)
{
var ms = sp.GetService(typeof(MyService));
Is it not possible to get it? The modifications above are all I did to the default scaffolding, but if you need to browse the full source code, I uploaded it to this Git repository.
Why do you want to inject your IHostedService in your controller? Seems really weird to me.
Anyhow, AddHostedService registers the implementation as Transient as can be seen here...
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
=> services.AddTransient<IHostedService, THostedService>();
}
...so the following should "work"
services.AddTransient<MyService>();
services.AddTransient<IHostedService>(x => return x.GetRequiredService<MyService>());
The only problem with this is that you will get a new fresh instance of your service everytime you inject/resolve it, and I don't think that's what you want.
So, why do you want to do this, what problem are you trying to solve?
I am trying to inject an instance of IHostedService into another IHostedService but I always get the above error when Run() is called in my Program.cs.
Basically, I have two services:
public class CacheService : HostedService
{
public CacheService()
{
}
/...
}
public class ClientService : HostedService
{
private CacheService _cacheService;
public ClientService(CacheService cacheService)
{
_cacheService = cacheService;
}
/...
}
HostedService implements IHostedService:
public abstract class HostedService : IHostedService
{
// Example untested base class code kindly provided by David Fowler: https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancellation to the executing method
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
// Throw if cancellation triggered
cancellationToken.ThrowIfCancellationRequested();
}
// Derived classes should override this and execute a long running method until
// cancellation is requested
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
This is how I inject these services in my Startup.cs class:
private void AddRequiredServices(IServiceCollection services)
{
services.AddSingleton<IHostedService, CacheService>();
services.AddSingleton<IHostedService, ClientService>();
}
However, every time I run the application, I get an error that CacheService can't be resolved for service ClientService. Am I doing something wrong here or is this not supported?
EDIT: Here is a repository that you can clone which reproduces my issue.
As you are not injecting the IHostedService interface in your classes you should register the ClientService and CacheService directly to the service collection, instead of using the interface e.g.
private void AddRequiredServices(IServiceCollection services)
{
services.AddSingleton<CacheService>();
services.AddSingleton<ClientService>();
}
The Dependency Injector (DI) will be able to resolve the correct service and inject it into your constructor.
When you add a service with an interface, the DI will look for references to the interface in constructors and not the class, which is why it fails in your example to inject the correct instance.
#Simply Ged, your solution worked for me but it does not make sense to me. I am trying to achieve the same thing in my current project and were struggling since yesterday. Glad I found your post.
This post will help.
Injecting SimpleInjector components into IHostedService with ASP.NET Core 2.0
I have a simple service that contains a List<Foo>. In Startup.cs, I am using the services.addScoped<Foo, Foo>() method.
I am inject the service instance in two different places (controller and middleware), and for a single request, I would expect to get the same instance. However, this does not appear to be happening.
Even though I am adding a Foo to the List in the Controller Action, the Foo list in the Middleware is always empty. Why is this?
I have tried changing the service registration to a singleton, using AddSingleton() and it works as expected. However, this has to be scoped to the current request. Any help or ideas are greatly appreciated!
FooService.cs
public class FooService
{
public List<Foo> Foos = new List<Foo>();
}
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<FooService, FooService>();
}
[Below are the two places where I am injecting the service, resulting in two different instances]
MyController.cs
public class MyController : Controller
{
public MyController(FooService fooService)
{
this.fooService = fooService;
}
[HttpPost]
public void TestAddFoo()
{
//add foo to List
this.fooService.Foos.Add(new Foo());
}
}
FooMiddleware.cs
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
this.next = next;
this.serviceProvider = serviceProvider;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = this.serviceProvider.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
That's because when you inject IServiceProvider into your middleware - that's "global" provider, not request-scoped. There is no request when your middleware constructor is invoked (middleware is created once at startup), so it cannot be request-scoped container.
When request starts, new DI scope is created, and IServiceProvider related to this scope is used to resolve services, including injection of services into your controllers. So your controller resolves FooService from request scope (because injected to constructor), but your middleware resolves it from "parent" service provider (root scope), so it's different. One way to fix this is to use HttpContext.RequestServices:
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = context.RequestServices.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
But even better way is to inject it into Invoke method itself, then it will be request scoped:
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
First of all you shouldn't be using GetService, use the proper DI system that is in place by passing it into the Invoke method as a parameter.
Secondly, the reason you are getting a different object is because the constructor of the middleware is called outside of the scope of any request, during the app initialisation phase. So the container used there is the global provider. See here for a good discussion.
public class AppMessageMiddleware
{
private readonly RequestDelegate _next;
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
_next = next;
}
//Note the new parameter here: vvvvvvvvvvvvvvvvvvvvv
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count;
return Task.CompletedTask;
});
await _next(context);
}
}
So let's say I have a singleton class instance that I register in the DI like this:
services.AddSingleton<IFoo, Foo>();
And let's say the Foo class has a number of other dependencies (mostly repository classes that allow it to load data).
With my current understanding, the Foo instance is not created until it's first used (asked). Is there a way to initialize this class other than the constructor? Like right after ConfigureServices() completes? Or should the initialization code (loading data from db) be done in Foo's constructor?
(It would be nice if this class could load its data before the first use to speed up first time access)
Do it yourself during startup.
var foo = new Foo();
services.AddSingleton<IFoo>(foo);
Or "warm it up"
public void Configure(IApplicationBuilder app)
{
app.ApplicationServices.GetService<IFoo>();
}
or alternatively
public void Configure(IApplicationBuilder app, IFoo foo)
{
...
}
But this feels just dirty and is more a problem with your design, if you do something that you shouldn't in the constructor. Class instantiation has to be fast and if you do long-running operations within it, you break against a bunch of best practices and need to refactor your code base rather than looking for ways to hack around it
I got the same problem and I find Andrew Lock blog:
https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-3/
He explains how to do this with asp .net core 3, but he also refers to his pages on how to to this with previous version.
Lately I've been creating it as an IHostedService if it needs initialization, because to me it seems more logical to let the initialization be handled by the service itself rather than outside of it.
You can even use a BackgroundService instead of IHostedService as it's pretty similar and it only needs the implementation of ExecuteAsync
Here's the documentation for them
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services
An example of how to add the service so you can inject it directly:
services
.AddHostedService<MyService>()
.AddSingleton<MyService>(x => x
.GetServices<IHostedService>()
.OfType<MyService>()
.First());
Example of a simple service:
public class MyService : IHostedService
{
// This function will be called automatically when the host `starts`
public async Task StartAsync(CancellationToken cancellationToken)
{
// Do initialization logic
}
// This function will be called automatically when the host `stops`
public Task StopAsync(CancellationToken cancellationToken)
{
// Do cleanup if needed
return Task.CompletedTask;
}
}
Some extension methods I created later on because i needed to use the same pattern again
public static class HostedServiceExtensions
{
public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services) where T : class, IHostedService
=> services.AddHostedService<T>().AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());
public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services, Func<IServiceProvider, T> factory) where T : class, IHostedService
=> services.AddHostedService(factory).AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());
}
Used like
services.AddHostedServiceAsService<MyService>();
// Or like this if you need a factory
services.AddHostedServiceAsService<MyService>(x => new MyService());
Adding detail to Jérôme FLAMEN's answer, as it provided the key I required to calling an async Initialization task to a singleton:
Create a class that implements IHostedService:
public class PostStartup : IHostedService
{
private readonly YourSingleton yourSingleton;
public PostStartup(YourSingleton _yourSingleton)
{
yourSingleton = _yourSingleton;
}
// you may wish to make use of the cancellationToken
public async Task StartAsync(CancellationToken cancellationToken)
{
await yourSingleton.Initialize();
}
// implement as you see fit
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Then, in your ConfigureServices, add a HostedService reference:
services.AddHostedService<PostStartup>();
From link.
I made some manager and I need to subscribe to events of the other services.
I didn't like doing this in the
webBuilder.Configure (applicationBuilder => ...
I think it should be in the section
webBuilder.ConfigureServices ((context, services) => ...
So, here is my answer (test on net.core 3):
public static IHostBuilder CreateHostBuilder (string [] args) =>
Host.CreateDefaultBuilder (args)
.ConfigureWebHostDefaults (webBuilder =>
{
...
services.AddSingleton<ISomeSingletonService,SomeSingletonService>();
var buildServiceProvider = services.BuildServiceProvider();
var someSingletonService = buildServiceProvider.GetRequiredService <ISomeSingletonService>();
...
});