Search code examples
azure.net-coredependency-injectionazure-webjobsasp.net-core-hosted-services

Hosted Service scope when running as Azure Web Job


Working on a BackgroundService that is published to Azure as a Web Job, I noticed something strange (works fine on Azure, exception when running locally), so I tried to recreate the situation.

Program.cs:

using TestBackgroundJob;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddScoped<IDummyService, DummyService>();
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();

Worker

public class Worker : BackgroundService
{
        private readonly ILogger<Worker> _logger;
        private readonly IDummyService _dummyService;

        public Worker(ILogger<Worker> logger, IDummyService dummyService)
        {
            _logger = logger;
            _dummyService = dummyService;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
}

Of course that when I run this local I get an exception, because I try to inject a scoped service inside Worker class, that being a HostedService, is singleton:

Cannot consume scoped service 'TestBackgroundJob.IDummyService' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)'

However if I publish this as a Web Job on Azure, the code works fine. I could not found any articles that explain this. Looks like when running on Azure, the scope of Worker class is changed from singleton to scoped. Is that what really happen?


Solution

  • The Worker always has Singleton lifetime and won't change. But let's see what happens when you want to create a default host (CreateDefaultBuilder method):

    Default builder settings

    The CreateDefaultBuilder method:

    • Sets the content root to the path returned by GetCurrentDirectory.
    • Loads host configuration from:
      • Environment variables prefixed with DOTNET_.
      • Command-line arguments.
    • Loads app configuration from:
      • appsettings.json.
      • appsettings.{Environment}.json.
      • User secrets when the app runs in the Development environment.
      • Environment variables.
      • Command-line arguments.
    • Adds the following logging providers:
      • Console
      • Debug
      • EventSource
      • EventLog (only when running on Windows)
    • Enables scope validation and dependency validation when the environment is Development.

    As you can see the scope validation only happens in the Development (local) mode but when you publish it on Azure it will run in the Production mode.

    So why it's important to take care about not resolving Scope service to Singleton?

    It's all about using resources since when you use the Scope service in the Singleton then it will behave like a singleton and stay alive on the application lifetime!

    It is recommended to use IServiceScopeFactory for solving your issue.

    For reading more about Generic Host in .net, here is the place:

    https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-7.0