2

We have an API project in.net core and one library project in the .net core where we define the background process (Background services using IHosted Service).

In the API project, we register the dependencies of our background services as:

services.AddHostedService<MyBackgroundProcess>();

and we have some other scope, transient, and singleton dependencies, as:

services.AddScoped<IHttpClientService , HttpClientService>();
services.AddScoped<IUserService, UserService>();
services.AddTransient<IMyTransientService, MyTransientService>();
services.AddScoped<IMyScopeService, MyScopeService>();

In our background service project, in the constructor, we inject some of the scoped and transient dependencies as (for example):

public MyBackgroundProcess(IHttpClientService iHttpClientService, IUserService iUserService, IMyTransientService iMyTransientServcie, IMyScopeService iMyScopeService)                                                      
        {
            _iHttpClientService = iHttpClientService;
            _iUserService = iUserService;
            _iMyTransientServcie = iMyTransientServcie;
            _iMyScopeService = iMyScopeService;
        }

I was able to resolve these scoped dependencies in .net core 2.2. I am getting all those values from my API project. The background process constructor is being called when I call my background service from the API project. So I was getting all the updated dependencies.

The problem came when I migrated the projects to .net core 3.1. Now my dependencies are coming as null. These are not synced with my API project from where I invoke these background services. We are using .net core inbuild DI. The constructor of IHostedService/Background services is being called while deploying the application to service fabric, it does not get called on calling background process from API.

My API Business Logic constructor is like:

public MyAPIManager(IMyTransientService myTransientService, IMyScopeService myScopedService, IHostedService myBackGroundProcess1,
                                 IHostedService myBackgroundProcess2, IServiceProvider serviceProvider)
        {
            _myTransientService = myTransientService;
            _myScopedService = myScopedService;
            _myBackGroundService1 = myBackGroundService1 as MyBackgroundServiceA;
            _myBackGroundService2 = myBackGroundService2 as MyBackgroundServiceB;
            if (_myBackGroundService2 == null)
            {
                var services = serviceProvider.GetServices<IHostedService>();
                _myBackGroundService2 = services.First(o => o.GetType() == typeof(MyBackgroundServiceB)) as MyBackgroundServiceB;
            }
        }

Here I am injecting, scoped, and transient dependencies and 2 background services. Whenever I get the instances of IHostedService from "serviceProvider.GetServices()", in .net core 2.2, it calls the constructor of all the IHosted services registered in my DI, but in .net core 3.1 it does not call the constructor of background services.


UPDATE--

I tried register my background services, as Transient instead of AddHostedService as below:

services.AddTransient<IHostedService, MyBackgroundService1>();
 services.AddTransient<IHostedService, MyBackgroundService2>();

Seems it is working now, it calls the constructor every time I tried to resolve the type:

var services = serviceProvider.GetServices<IHostedService>();
                _myBackgroundProcess1 = services.First(o => o.GetType() == typeof(MyBackgroundProcessA)) as MyBackgroundProcessA;

Is it advisable to register IHostedService like this? Will there be any impact?

Rrao
  • 21
  • 1
  • 4
  • where is your code like "services.AddSingleton(); ? – granadaCoder Jul 28 '20 at 19:34
  • added sample code where i register dependencies. – Rrao Jul 28 '20 at 19:48
  • You need to add the full context of the IoC code. (like your germane code of your entire Program.cs or Startup.cs file) – granadaCoder Jul 28 '20 at 19:50
  • SanityCheck : Can you put a breakpoint on all your AddScoped, AddTransient and make sure that code is being excecuted? – granadaCoder Jul 28 '20 at 19:59
  • The problem is, wherever i am trying to resolve the dependency of Background Service, It does not call its constructor. So i am not getting the updated scoped objects. In .net framework 2.2, it calls the constructor of Background services. – Rrao Jul 28 '20 at 20:07
  • Yes, It executes those DI, In my API business logic, I am doing lot of stuff using these scoped and transient dependencies. when I call the background process, which is also injected as a dependency, it does not call the constructor of those background services. In .net core 2.2, when we register a DI as AddHostedService, it returns the Transient object, in .net core 3.1 I think i am getting singleton object. – Rrao Jul 28 '20 at 20:11
  • In MyApiManager, can you try injecting : IEnumerable myHostedServices .. instead of 2 of them in serial ( hsNumber1, hsNumber2) ? – granadaCoder Jul 28 '20 at 21:18
  • The problem is here : var services = serviceProvider.GetServices(); It is not calling the constructor of myBackgroundProcess. In .net core 2.2 it does and i always get the fresh object. In .net core 3.3, i am getting the same object of myBackgroundProcess. Seems it is registering as singleton. – Rrao Jul 28 '20 at 21:28
  • services.AddHostedService() is just a helper method. Try adding it as Scoped instead of .AddHostedService. – granadaCoder Jul 28 '20 at 21:42
  • @granadaCoder, I tried register them as below: services.AddTransient(); services.AddTransient(); Seems it is working, is it advisable to register hosted service like this? Or there is any impact for registering in such a way, updating it in my question also. – Rrao Jul 29 '20 at 05:32
  • https://github.com/dotnet/extensions/issues/553 After sleeping on it, IMHO, it acting like a Singleton is probably the "most correct". Seems there is a discussion about just that thing (< – granadaCoder Jul 29 '20 at 12:17
  • Yes, the injected dependencies are coming as null in Background Service, if i user AddHostedService, If i use AddTransient, It maintains the injected dependencies. This was not happening in .net core 2.2. Whenever you read the object from IOC, if it is added as transient, it should return the fresh object. and It should call the constructor of that requested type. But in my case, It is not calling the constructor of the type i added as "AddHostedService" Thanks @granadaCoder, for all the help and research you provided. I appreciate it. – Rrao Jul 29 '20 at 12:51

1 Answers1

0

Schoooo Wheeeeee.

Looks like you ran into a small-holy-war.

Here is link and discussion:

https://github.com/dotnet/extensions/issues/553

"AddHostedService should not be a singleton. Please don't make this breaking change,"

"This thread is extremely strange. HostedServices should be singletons not transient."

This seems to be the key comment in your case:

"Because it's a helper for what we expect is the most common scenario. If that doesn't suit you then use the DI methods directly."

PREVIOUS RESPONSE:

As per this example:

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

(code above from the example)

You have to register your dependencies

services.AddHostedService<MyBackgroundProcess>();

services.AddScoped<IHttpClientService , MyConcreteHttpClientService>();
services.AddScoped<IUserService, MyConcreteUserService>();
services.AddScoped<IMyTransientService, MyConcreteMyTransientService>();
services.AddScoped<IMyScopeService, MyConcreteScopeService>();

This (the above) is .. of course... if you are not using some kind of "autoscan" solution. (Did you have kind of autoscan solution in your 2.2 code maybe??)

something like

NetCore.AutoRegisterDi

https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/

public static class NetCoreDiSetupExtensions
{
    public static void RegisterServiceLayerDi
        (this IServiceCollection services)
    {
        services.RegisterAssemblyPublicNonGenericClasses()
            .AsPublicImplementedInterfaces();
 
        //put any non-standard DI registration, e.g. generic types, here
    }
}

Another link:

Ultimately, I think this is the issue:

https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting.Abstractions/ServiceCollectionHostedServiceExtensions.cs#L18

granadaCoder
  • 26,328
  • 10
  • 113
  • 146
  • yes, all those dependencies are already registered. My API uses those dependencies. (Constructor injection) – Rrao Jul 28 '20 at 19:44
  • You need to show all that code (the IoC registrations) There could be a "timing" issue. – granadaCoder Jul 28 '20 at 19:46
  • here is my living example. :: https://github.com/granadacoder/dotnetcore-hostedservice-containerized-one/blob/master/src/ConsoleOne/Program.cs#L69 – granadaCoder Jul 28 '20 at 19:48
  • added DI registration code. The same code works fine in .net core 2.2 – Rrao Jul 28 '20 at 19:49
  • Updated the answer after finding the link to the discussion. – granadaCoder Jul 29 '20 at 12:30
  • Yes, so based on that it should return the Transient (Fresh) object every time i get from DI. But in my case, it was returning the same object like a singleton. I don't know why it is behaving like that. I know AddHostedService should return transient, which is different between AddSingleton and AddHostedService. – Rrao Jul 29 '20 at 12:48
  • So.........in my limited experience...........try registering everything as Transient (including NOT using the convenience method)..........and work "backwards" from there. I kinda remember having to do that once. – granadaCoder Jul 29 '20 at 12:57
  • i am doing that now. I think it won't impact anything. Thank you. – Rrao Jul 29 '20 at 13:14
  • Sometimes...you run across an SO answer that makes you wanna kick yourself. I've been pulling my hair out, trying to understand why my custom background `IHostedService` implementation was returning two different instances, when what I want is to always only have one instance. The rest of the services in my project are auto-registered by their "Service" suffix, `AddHostedService` was always adding a 2nd registration. So now I simply suffix my `IHostedService` implementations with an underscore (`MyHostedService_`), which ensures they don't get auto-registered. Whew!!! I was losing my mind. – NovaJoe Feb 19 '22 at 17:08
  • That is the double-edged sword of "scanning" IoC builders. And why I shy away from them. It is "easy peezy" in the beginning......but when there is an issue, it becomes a "where's waldo" goose chase (and complete waste of time). What I do in java spring-bean land (guh).....(where java spring developers LOVE attribute/annotation "auto scan" IoC is........on my startup I "console.writeline" all of the items in my IoC container. But in dotnet(core) land......I shy away from "by convention" auto-scanning into the IoC. But it is a holy-war. – granadaCoder Feb 21 '22 at 14:44
  • PS, to be honest, I don't like your "string magic" (add a _ as a suffix) "fix". It violates c# naming conventions for class-names. https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces So why I understand "why"you did it.......it is voodoo IMHO. A better long term fix would be to disambiguate all your (and in general) word of "Service". Service is a way too often overused and ambiguous word on its own. MyFirstHostedService , MyFirstSomePhraseHereService....etc, etc. Then code your auto scanning to the non ambiguous "Service" – granadaCoder Feb 21 '22 at 14:58