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?

Kirk Larkin
  • 84,915
  • 16
  • 214
  • 203
Phil
  • 1,852
  • 2
  • 28
  • 55

1 Answers1

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.

Kirk Larkin
  • 84,915
  • 16
  • 214
  • 203
  • @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](https://stackoverflow.com/a/51481139/10883465) seems to contradict your claims. – Joelius Oct 27 '19 at 20:15
  • 1
    @Joelius 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... – Kirk Larkin Oct 27 '19 at 20:18
  • 1
    @Joelius [Here you go](https://github.com/aspnet/Extensions/blob/release/3.0/src/Hosting/Hosting/src/Internal/Host.cs#L48). The `Host` just requests `IEnumerable` 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. – Kirk Larkin Oct 27 '19 at 20:22
  • 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). – Joelius Oct 27 '19 at 20:26