Having written or contributed to a number ASP.NET Web projects, Secret Manager is a familiar tool for storing sensitive information. Using Secret Manager with a Console Application however wasn’t as straight forward as I’ve come to expect with web projects. This post looks at what needed to occur for secrets to be accessible in a Console App, and how both the workaround and reasoning was discovered.

Getting started with Secret Manager

Secret Manager for ASP.NET Core makes working with sensitive data like passwords and keys simpler, while helping to keep this information outside of source control. Getting started is a matter of running two commands;

dotnet user-secrets init

Which will add a guid to your .csproj that will be used next to reference a particular project’s secrets and;

dotnet user-secrets set "Section:Property" "my-secret"

Which registers a secret that when referenced at runtime will override a setting in your appsettings.json or IConfiguration that looks like this;

{
    "Section": {
        "Property": "my-secret"
    }
}

Accessing secrets at runtime

If you follow the docs you’ll see that user secrets are automatically added if your ASP.NET Core project calls CreateDefaultBuilder. If you’re building a web project, this often means you can access secrets without any further changes. However, I made the same call in a console project, but found at runtime the secrets were not there.

I stepped into .NET Core source at runtime to check whether AddUserSecrets was getting called - it wasn’t. The source revealed that the IHostEnvironment needed to be set to Development - a configuration that OmniSharp/vscode will add automatically for web projects.

After setting the environment to Development myself for the console app, secrets were still not available. Stepping again into source revealed AddUserSecrets was now being called, but stepping in deeper revealed a conditional expecting UserSecretsIdAttribute to be present on the assembly - another issue that web projects don’t seem to have.

Plonking the code below into Program.cs appeased the condition and secrets were now accessible in the console app.

[assembly: UserSecretsIdAttribute("35c1247a-0256-4d98-b811-eb58b6162fd7")]

Why do I need UserSecretsIdAttribute now?

I’ve never had to use UserSecretsIdAttribute explicitly before, so why do I have to now? Fellow coder and lager drinker Mickaël Derriey thought the same;

Mick then went ahead and determined exactly why secrets weren’t accessible. I’ll summarise it below, but the GitHub issue Mick raised is worth a read if you want to understand the problem (and finding it’s solution) at a deeper level.

Microsoft.NET.Sdk.FrameworkReferenceResolution.targets or Microsoft.Extensions.Configuration.UserSecrets.targets are how the attribute gets ninja’d on to your assembly. To have these apply during build, you need either an explicit reference to Microsoft.Extensions.Configuration.UserSecrets or a framework reference to Microsoft.AspNetCore.App, the Core shared framework. Microsoft.NET.SDK.Web includes an implicit reference to the shared framework, but you have to reference it yourself when using Microsoft.Net.SDK.

See UserSecretsId attribute not generated when M.E.C.UserSecrets reference transitively. And, more info on what the shared framework is here. Finally, here again is Mick’s Github issue on the matter - thanks Mick!