Search code examples
c#dependency-injectionsingletonihostedservice

AddSingleton and AddHostedService in WebAPI application


In my application I have some services to manage some devices on a PC (mainly via serial ports). Services will be accessed through API controllers.

Each service must run as singleton (single instance) as they will use a serial port each.

Currently I'm using TInterface:TImplementationClass pattern , to take advantage of DI/IoC framework, and to be able to write mock methods to test various functionalities.

I have a DeviceManagerService that is the implementation class declared as follow:

DeviceManagerService : IDeviceManagerService

and IDeviceManagerService is the service interface declared as follow:

IDeviceManagerService : IHostedService

I'm using IHostedService interface to be able to do some startup configuration in StartAsync method, as if I don't register it as HostedService, no method is launched at startup, but only the constructor is called when first accessing the service.

then in my API controller I have

SerialPortController(ILogger<SerialPortController> logger, IDeviceManagerService devicesManager)

so I'm able to call various methods of the service from the controller itself

I've tried registering my service like this:

builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<DeviceManagerService>());

but I'm getting System.InvalidOperationException: 'No service for type 'APIServer.HostedServices.DeviceManagerService' has been registered.'

Also registering like this:

builder.Services.AddSingleton<DeviceManagerService>();
builder.Services.AddHostedService<IDeviceManagerService>(sp => sp.GetRequiredService<DeviceManagerService>());

throws an 'Unable to resolve service for type IDeviceManagerService' exception when calling IDeviceManagerService from API controller.

Lastly I tried also doing this:

builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>();
builder.Services.AddHostedService<DeviceManagerService>();

but two instances of DeviceManagerService are created, and StartAsync is called only once.

The only working method I found is this:

builder.Services.AddSingleton<DeviceManagerService>();
builder.Services.AddSingleton<IDeviceManagerService>(sp => sp.GetRequiredService<DeviceManagerService>());
builder.Services.AddHostedService(sp => sp.GetRequiredService<DeviceManagerService>());

What is the purpose of AddHostedService if it doesn't register the Interface? And why do I have to call AddSingleton separately once for the implementation class and once for the Interface? Should this builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>(); be sufficient to register the Interface and Implementation type and then using AddHostedService with delegate to create the instance?

What am I not getting here? I apologize for any misunderstanding as I'm fairly new to DI/IoC patterns. I'm migrating from legacy WCF to WebAPI/Grpc so I'm exploring various solutions here.


Solution

  • You were very close on your first attempt

    builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>();
    builder.Services.AddHostedService(sp => sp.GetRequiredService<DeviceManagerService>());
    

    Should be

    builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>();
    builder.Services.AddHostedService(sp => sp.GetRequiredService<IDeviceManagerService>());
    

    As you registered the DeviceManagerService type as IDeviceManagerService interface, you can only resolve the interface but not the specific type.