How to reference a hosted service in .Net Core 3?
Asked Answered
S

1

5

Back in .net core 2, I had created a hosted service with a custom property like:

 public class MyService : BackgroundService 
{
public bool IsRunning {get;set;}
...

That I could setup in startup.cs like:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHostedService,HostedServices.MyService>();
...

And then I could reference it elsewhere in a razor page like:

public class IndexModel : PageModel
{
    private readonly IHostedService _mySrv;
    public IndexModel(IHostedService mySrv) => _mySrv = mySrv;

    [BindProperty]
    public bool IsRunning { get; set; }

    public void OnGet() => IsRunning = ((HostedServices.MyService)_mySrv).IsRunning;
}

Now that I've upgraded to .net core 3, my startup has changed to:

services.AddHostedService<HostedServices.MyService>();

But my DI reference in IndexModel doesn't get me my MyService anymore, it gives me an object of type GenericWebHostService instead, that I can't figure out how to get my custom MyService from. Changing 'IHostedService' to 'MyService' in IndexModel doesn't work either, i get a 'Unable to resolve service' error.

How do I get an instance of MyService back from dependency injection?

Scotsman answered 27/10, 2019 at 17:7 Comment(0)
K
14

In 2.2, the setup you had worked mostly by chance. Whenever you register multiple implementations against a service, the last-registered is the one that "wins". For example, take the following code:

services.AddSingleton<IHostedService, HostedService1>();
services.AddSingleton<IHostedService, HostedService2>();

// ...

public IndexModel(IHostedServie hostedService) { }

The implementation of IHostedService that gets injected into IndexModel is HostedService2; the last registered. If IndexModel were to be updated to take an IEnumerable<IHostedService>, it would get both implementations, in order of registration:

public IndexModel(IEnumerable<IHostedService> hostedServices) { }

When I said "by chance", I meant that in your example, only HostedServices.MyService gets registered, so it's also the last-registered and therefore it "wins".

In 3.0, when using the Generic Host, an implementation of IHostedService, GenericWebHostService, handles processing web requests. This gives you a problem, because the GenericWebHostService is registered after HostedServices.MyService. I hope it's clear by now that this is the reason why the IHostedService you request in IndexModel is not what you expected.

In terms of a solution, I suggest performing two registrations:

services.AddSingleton<HostedServices.MyService>();
services.AddHostedService(sp => sp.GetRequiredService<HostedServices.MyService>());

Then, update your IndexModel to require your specific implementation:

public IndexModel(HostedServices.MyService myService) { }

This allows you to target your specific implementation of IHostedService. It's registered twice, against two different service types, but only one instance gets created.

Krusche answered 27/10, 2019 at 19:51 Comment(4)
@KirkLarkin wait really? Where can I find proof for this because I don't understand what would call StartAsync and StopAsync on the service if you just add it as a singleton. Also this answer seems to contradict your claims.Saraband
@Saraband So long as it's registered behind the interface IHostedService, it works fine. That's actually all AddHostedService does. I'll dig out a link to the source code to prove it...Krusche
@Saraband Here you go. The Host just requests IEnumerable<IHostedService> and calls StartAsync on them all. The answer you linked just says AddSingleton without specifying that interface doesn't work, and that's correct. I'm not saying any different. It's the second registration I've shown that ties them together.Krusche
Ohh that's something worth knowing. Is this mentioned in the docs? I feel like it's easy to assume that it might not do the same thing (although it's smart that it does).Saraband

© 2022 - 2024 — McMap. All rights reserved.