0

I am injecting two services into my dot net core web api, the main service relies on data in the helper service. The helper service populates this data in the constructor, however when the main service goes to use this data it is not ready because the constructor of the helper service has not finished by the time it is needed.

I thought DI and the compiler would resolve and chain these services properly so the helper service would not be used until it was fully instantiated.

How I tell the main service to wait until the helper service is fully resolved and instantiated?

Generic sample code of what I am doing. I call the DoSomething() in MainSerice the HelperService calls out to an external API to get some data, that data is needed in the MainService.

StartUp.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IHelperService, HelperService);
    services.Scoped<IMainService, MainService);
}

MainService.cs

public class MainService : IMainService
{
    private readonly IHelperServuce _helper;

    public MainService(IHelperService HelperService)
    {
        _helper = HelperService;
    }

    public void DoSomething()
    {   
        string helperParameter = _helper.Param1; //This fails because the constructor of HelperService has not finished 
    }
}

HelperService.cs

public class HelperService : IHelperService
{

    public HelperService()
    {
        GetParamData();
    }

    private async void GetParamData()
    {
        var response = await CallExternalAPIForParameters(); //This may take a second.
        Params1 = response.value;
    }


    private string _param1;

    public string Param1
    {
        get
        {
            return _param1;
        }

        private set
        {
            _param1 = value;
        }
    }
}
ProfessionalAmateur
  • 4,447
  • 9
  • 46
  • 63
  • 1
    You are not awaiting the async method `GetParamData()` data in the constructor. That is, ofcourse, not possible. Your constructor should only initialize simple data. You could fix this by, instead of using a property to return, you could also return a `Task` from a method called _(for example)_ `Task GetParam1()`. Which could cache the string value. – Jeroen van Langen May 18 '20 at 22:11
  • @JeroenvanLangen I'm new to DI and netcore, This example only has 1 param, but the real project has two. So would the Task GetParam() be the method definition? And then the constructor would call the task? – ProfessionalAmateur May 18 '20 at 22:18
  • The problem is that a constructor cannot be awaited. it's an initialization "method" that is called when the object is instantiated. A constructor cannot be broke up into a statemachine. – Jeroen van Langen May 18 '20 at 22:19
  • Also see [Injection Constructors should be simple](https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/). – Steven May 19 '20 at 07:27

2 Answers2

4

You are not awaiting the async method GetParamData() data in the constructor. That is, ofcourse, not possible. Your constructor should only initialize simple data. You could fix this by, instead of using a property to return, you could also return a Task from a method called (for example) Task<string> GetParam1(). Which could cache the string value.

for example:

public class HelperService : IHelperService
{
    private string _param1;

    // note: this is not threadsafe.
    public async Task<string> GetParam1()
    {
        if(_param1 != null)
            return _param1;

        var response = await CallExternalAPIForParameters(); //This may take a second.
        _params1 = response.value;
        return _param1;
    }
}

You could even return a ValueTask<string> because most of the calls can be executed synchronously.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • I see how you mean now, there would be no constructor. Would it not be threadsafe even if its a singleton? We are retrieving credentials from a webservice that the program will use, so we just need it to run once when the program starts, but every time after they will not change. Maybe there is a better way to populate this data from Configure(IApplicationBuilder app, IWebHostEnvironment env) in Startup.cs? – ProfessionalAmateur May 18 '20 at 22:20
  • With the thread safe I mean that I am not entirely sure that this method from an instance is not called from other threads. – Jeroen van Langen May 18 '20 at 22:31
0

Pass a lambda to the helper service that initializes the variable in your main service, as in...

Helper service.getfirstparam(  (response) -> 
    {  firstparam = response.data;});

    While (firstparam == null)
          sleep

  // now do your processing
Rodney P. Barbati
  • 1,883
  • 24
  • 18