Jason
Jason

Reputation: 217

Azure Function V3 Dependency Injection Lifetime Question

I am using Azure Functions v3 and DI. I have a service that I setup with a transient lifetime in my StartUp.cs (using: builder.Services.AddTransient<ICoreApiClient>(s => coreApiService);) and then I inject into my Azure Function class. The Azure Function is a Storage Queue trigger function.

I expect that for each queue message I will get a separate instance of my "coreApiService" but I seem to be sharing one instance when multiple messages are put in the queue at the same time.

Is my expectation to get a separate instance for each invocation or "Run" incorrect? The issue I am running into is that the "_coreApiClient" seems to be shared between multiple invocations of the Run method so as I set properties within it (like "Customer ID" or "API Key", etc.) that I are valid for one message and should remain for the processing of that message are changing as it starts processing the next message.

Am I doing it wrong or do I not understand the lifetime properly?

Here is my startup code:

public class Startup: FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddLogging();
            
            var loggingService = new HttpLoggingService(Environment.GetEnvironmentVariable("LoggingURL"), 0, "ABC.Integrations");
            builder.Services.AddTransient<ILoggingService>(s => loggingService);

            int CoreApiTimeout = 60;  //Environment.GetEnvironmentVariable("CoreApiTimeout")
            var coreApiService = new CoreApiClient(Environment.GetEnvironmentVariable("CoreApiKey"),string.Empty, CoreApiTimeout,Environment.GetEnvironmentVariable("CoreApiBaseUrl"));
            builder.Services.AddTransient<ICoreApiClient>(s => coreApiService);
           
        }
    }

And my Azure Function class:

public class TaskRouter
    {
        private readonly ICoreApiClient _coreApiClient;
        private readonly ILoggingService _upgLogger;
        private readonly ILogger<TaskRouter> _log;        

        public TaskRouter(ICoreApiClient coreApiClient, ILoggingService upgLogger, ILogger<TaskRouter> log)
        {
            _coreApiClient = coreApiClient;
            _upgLogger = upgLogger;
            _log = log;
            
        }

        [FunctionName("RouteTask")]
        [ExponentialBackoffRetry(5, "00:00:04", "00:03:00")]
        public async Task Run([QueueTrigger("task-scheduler", Connection = "")]
            string queueItem)
        {

            
                //Call Appropriate Function to start task
                var taskMsg = JsonConvert.DeserializeObject<TaskMessage>(queueItem);
                                
                _coreApiClient.SetId(taskMsg.TaskNumber);
                
                //DO SOME WORK HERE
                
                Console.Log(_coreApiClient.GetId);  //Will be different b/c this was set by the next message
        }
   }

Upvotes: 1

Views: 934

Answers (1)

Nkosi
Nkosi

Reputation: 247551

Although the client is registered as transient with the container, it is actually a singleton since the instance created in Startup is the only instance that will ever be returned for each call to inject ICoreApiClient.

Move the instance creation into the factory delegate, making this an actual transient registration, so that a new instance will be initialized each time ICoreApiClient has to be resolved from the service provider.

//...

builder.Services.AddTransient<ICoreApiClient>(s => 
    new CoreApiClient(Environment.GetEnvironmentVariable("CoreApiKey"), string.Empty, CoreApiTimeout, Environment.GetEnvironmentVariable("CoreApiBaseUrl"))
);

//...

Upvotes: 1

Related Questions