.netasp.net-core.net-coreapp-secret

Manage User Secrets for Class Library


Situation

I have 3 dotnet core projects in question:

In the class library, I have a shared EmailService class that I register for dependency injection in both the Web API and the Worker Service.

Both the Web API and Worker Service projects have the same config section for my sendgrid API key, and they are bound to a POCO using the IOptions pattern in the same exact way. They are both registered with the AddScoped method. But the actual class implementation for EmailService is in the shared class library. Running locally does not pickup my user-secrets in either the class library (which probably makes sense) as well as the ETL user-secrets. Both user secrets share the same exact value of json.

Problem

When I set my user-secrets for the ETL project and run them locally, the API key I have for my send grid account is not getting picked up because, I think, the class is defined in the class library.

What I've tried

I tried adding the same user-secrets to the class library as well, but this doesn't seem to work and I'm assuming that is because there is no appsettings.json in my class library, as probably there shouldn't be.


Solution

  • First of all, there is no need to add user secrets to your class library because nothing will pick those up. The configuration system actually doesn’t care in what assembly your options POCOs live, especially since Microsoft.Extensions.Configuration (which is where the user secrets are loaded) is only somewhat related to Microsoft.Extensions.Options (which is what your POCO is for).

    Since you are consuming the configuration through the Options pattern, as long as the dependency injection system is configured to correctly provide all the configuration sources for configuring the options, the code in your library shouldn’t worry much.

    So if you are having troubles with the user secrets in your two (executing) assemblies, then it’s about how those are configured. Since you have a ASP.NET Core web app and a worker service, I am going to assume that you are using the host builder for both of these projects. In that case, you don’t need to worry much about setting up your configuration sources since the host builder comes with a sensible default that does include user secrets in development mode.

    For what it’s worth, if you ever come across the need to configure user secrets as a configuration provider, you should know that the IConfigurationBuilder.AddUserSecrets extension method will usually expect you to pass in an assembly that identifies the user secrets from the project configuration in the .csproj file. – If you are using the default host builder, then it will set this up for you and will use your project’s assembly which is usually what you want to do.

    As you have found out yourself, there is an open bug for 3.1 which will prevent user secrets from working out of the box for the Worker project template. The reason for this is that the underlying project SDK forgot to handle the <UserSecretsId> MSBuild property which is used to identify the user projects.

    Fortunately, there is an easy workaround for this problem: All you need to do is to add an explicit reference to the Microsoft.Extensions.Configuration.UserSecrets package. The package is already implicitly referenced by the Microsoft.Extensions.Hosting which is included by the default in the Worker project template but by adding it again explicitly you are enabling the few magic MSBuild bits to make the project pick up the user secrets properly.

    So your project template should look something like this:

    <Project Sdk="Microsoft.NET.Sdk.Worker">
    
      <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <UserSecretsId>fb8be4c5-1316-42a7-8c30-3a752ed21c09</UserSecretsId>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.8" />
        <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.8" />
      </ItemGroup>
    
    </Project>