.net core Worker Service (BackgroundService) cannot load user secrets

Viewed 1559

Having a .net core worker service with .net core version 3.1.8 and asp.net core version 3.1.8 I am having an issue to load user secret from worker service however the same works in asp.net core project.

Common method called from both asp.net core and .net core worker service

public static string GetFioApiKey(this IConfiguration configuration)
            => configuration["PaymentServiceSecrets:FioApiKey"];

csproj of worker service

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UserSecretsId>8d19c97d-a2fb-4a51-a694-9635b9c0c42c</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.8" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.8" />
  </ItemGroup>
</Project>

startup.cs of worker service

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration(config => config.AddUserSecrets(Assembly.GetExecutingAssembly()))
                .ConfigureServices((hostContext, services) =>
                {
                    // simplified
                    

                    services.AddSingleton<IApplicationSettings>(applicationSettings =>
                    {
                        var appSettings = new ApplicationSettings
                        {
                            FioApiKey = config.GetFioApiKey()
                        };

                        
                        return appSettings;
                    });

                    services.AddHostedService<Worker>();
                });
    }

found this link to github issue, tried to apply the workaround but still doesn't work

6 Answers

One "gotcha" here that isn't mentioned is the requirement that, in WorkerServices, the environment variable "DOTNET_ENVIRONMENT" must be set to "Development", or User Secrets will not even try to load. This is in contrast to the "ASPNETCORE_ENVIRONMENT" environment variable used in ASP.Net projects.

Ie, in Properties/launchSettings.json

{
  // .. Ommitted
  "profiles": {
    "MyWorkerService": {
      "commandName": "Project",
      "environmentVariables": {
        "DOTNET_ENVIRONMENT": "Development"
      }
    }
  }
}

Usually, the new project templates have this in place for you, but in my case, when converting ASP.Net projects (that had no need of web server capabilities) to Worker Services, the launchSettings.json profile sets the "ASPNETCORE_ENVIRONMENT" to "Development" which is effectively useless in WorkerServices.

For .net core 3.1 and later you need to manually add in a reference to the nuget package

Microsoft.Extensions.Configuration.UserSecrets

and then user secrets will be automagically loaded into the IConfiguration instance you can get through dependency injection.

This works straight from the new project template and doesn't require any further code to configure it.

further details can be found on this github issue : https://github.com/dotnet/extensions/issues/2743

you should try this

public static YourConfigObject GetFioApiKey(this IConfiguration configuration)
            => configuration.GetSection("PaymentServiceSecrets").Get<YourConfigObject>();

by putting below line in Program.cs I was able to fetch value from user secret

hostContext.Configuration.GetSection("PaymentServiceSecrets:FioApiKey").Value

The reason why the implementation worked in webapi project because in webapi, configuration object is DI into the APIController class and hence is available everywhere across your code. This is done via the IOptions and its siblings, so you need to refer to config as IOptions

But when in a self hosted environment like worker service, (due to absence of IOptions, you need an explicit Bind) you either need to refer it via the hostContext or create an instance of the strongly typed configuration object, and use it as a Singleton service and then demand an instance anywhere in the app project by having a constructor DI.

Pseudo code only

Program.cs

MyConfig config = new MyConfig()
hostContext.Configuration.Bind(configParams)
services.AddSingleton(config)

Worker.cs

myworker(MyConfig config) {
     connStr = config.Connection
     ... 
}

This issue seems to be fixed in .NET 6, so you no longer need to reference Microsoft.Extensions.Configuration.UserSecrets directly.

However, if you have, for some reason, set <GenerateAssemblyInfo>false</GenerateAssemblyInfo> in your .csproj file, the <UserSecretsId> value won't be used. I.e., I had to do this for legacy reasons in my project.

To make your worker service project recognize the secrets.json file, you should add a global attribute [assembly: UserSecretsId] to your custom AssemblyInfo.cs file, or any other .cs file in the project. I.e.:

[assembly: UserSecretsId("dotnet-Worker-12345678-1234-ABCD-5678-1234567890ABCDEF")]
Related