Get user secrets for projects



This content originally appeared on DEV Community and was authored by Karen Payne

Introduction

Learn how to get all user secrets for a developer’s projects.

There are two different reasons for getting user secrets: the first is to transfer secrets to a new computer, and the second is to identify common secrets that need to be updated.

Note
This article does not cover Azure storage of secrets

Source code

Console project Core class project Serilog class project

AI usage

Rather than take time to write all the code presented, ChatGPT and Copilot were used to speed up the project.

  • ChatGPT For a JsonConverter
  • Copilot and NES (Next Edit Suggestions) throughout the project

Note
When using these and/or AI tools always ensure the code produced is completely understood.

Get secrets folder

Use the following method to get the secrets folder for the current developer.

public static string SecretsFolder => Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
    "Microsoft",
    "UserSecrets"
);

Get all developers’ secrets.

Without code, get the secret folder from the code above and open the folder with Microsoft VS Code and search and replace settings as needed.

With code, first off, why use code when the first method above gets secrets? Because using code a developer can learn.

Desired result for each project’s secrets sample where the original source does not indicate the project path or name.

{
  "ProjectFileName": "C:\\OED\\DotnetLand\\VS2022\\WebCodeSamples\\SecretManager1\\SecretManager1.csproj",
  "UserSecretsId": "1fe850fa-6fb3-4320-8003-b70d16d1a649",
  "Contents": {
    "ConnectionStrings:DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=EF.NotesDatabase;Integrated Security=True",
    "MailSettings:FromAddress": "FromAddress",
    "MailSettings:Host": "SomeHost",
    "MailSettings:Port": "25",
    "MailSettings:TimeOut": "3000",
    "MailSettings:PickupFolder": "MailDrop"
  }
}

Projects used

SerilogLibrary: Contains code to log to log files, a class project..

SecretsLibrary: Code specific to working with secrets, a class project.

FindUserSecretsApp: Front-end console project

Learning topics

For novice developers:

Splitting up code into class projects for reusability is a good idea. Start by writing code in the front-end project, keeping in mind that reusable code will be moved to a class project. Once a class project is created, move code from front-end project to the class project and update the namespaces. Next, in Solution Explorer drag the class project to the front-end project and this will add the class project as a reference to the front-end project.

Using a property in appsetting.json allows a developer to share the executable with other developers.

The developer changes VisualStudioFolder to the path where their solutions are and runs the executable.

{
  "ApplicationSettings": {
    "VisualStudioFolder": "C:\\DotnetLand\\VS2022"
  }
}

All levels of developers

JsonConverter

By using a JsonConverter for a specific model, a developer’s code resides in one class, keeping front-end code clean.

Below, SecretItem represents a secret item that is created by our code.

public class SecretItem
{
    public string ProjectFileName { get; init; }
    public string UserSecretsId { get; init; }
    public JsonDocument Contents { get; set; }
}


public class SecretItemConverter : JsonConverter<SecretItem>
{
    public override SecretItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => throw new NotImplementedException(); 

    public override void Write(Utf8JsonWriter writer, SecretItem value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        writer.WriteString(nameof(SecretItem.ProjectFileName), value.ProjectFileName);
        writer.WriteString(nameof(SecretItem.UserSecretsId), value.UserSecretsId);
        writer.WritePropertyName(nameof(SecretItem.Contents));
        value.Contents.WriteTo(writer);

        writer.WriteEndObject();
    }
}

File Operations

Rather than fully explain the code below, in the included source code, the class is fully documented.

public partial class FileOperations
{
    public static ApplicationSettings GetApplicationSettings()
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();

        return config.GetSection(nameof(ApplicationSettings)).Get<ApplicationSettings>();
    }

    public static void ScanDirectory(string directory, List<SecretItem> secretItems)
    {
        try
        {
            foreach (var file in Directory.GetFiles(directory, "*.csproj", SearchOption.AllDirectories))
            {
                var userSecretsId = ExtractUserSecretsId(file);
                if (string.IsNullOrEmpty(userSecretsId)) continue;

                if (Directory.Exists(Path.Combine(Utilities.SecretsFolder, userSecretsId)))
                {

                    var (json, exists) = ReadSecretFile(userSecretsId);

                    if (exists)
                    {
                        var rawJson = string.Join(Environment.NewLine, json).Trim('\uFEFF').Trim();

                        try
                        {
                            var jsonDocument = JsonDocument.Parse(rawJson);

                            secretItems.Add(new SecretItem
                            {
                                ProjectFileName = file,
                                UserSecretsId = userSecretsId,
                                Contents = jsonDocument
                            });

                        }
                        catch (JsonException ex)
                        {

                            Log.Warning(ex, $"Could not parse secrets.json for {userSecretsId}");
                            var fallbackDoc = JsonDocument.Parse("{\"error\": \"Invalid JSON content\"}");

                            secretItems.Add(new SecretItem
                            {
                                ProjectFileName = file,
                                UserSecretsId = userSecretsId,
                                Contents = fallbackDoc
                            });
                        }
                    }
                    else
                    {
                        var emptyDoc = JsonDocument.Parse("{\"info\": \"No secrets found\"}");
                        secretItems.Add(new SecretItem
                        {
                            ProjectFileName = file,
                            UserSecretsId = userSecretsId,
                            Contents = emptyDoc
                        });
                    }


                }

            }
        }
        catch (UnauthorizedAccessException unauthorized)
        {
            Log.Error(unauthorized, $"In {nameof(ScanDirectory)}");
            AnsiConsole.MarkupLine($"[deeppink3]Access denied:[/] {directory}");
        }
        catch (Exception ex)
        {
            Log.Error(ex, $"In {nameof(ScanDirectory)}");
            AnsiConsole.MarkupLine($"[deeppink3]Error processing[/] {directory}: {ex.Message}");
        }
    }

    private static string ExtractUserSecretsId(string filePath)
    {
        try
        {

            var content = File.ReadAllText(filePath);
            var match = GenerateUserSecretsIdRegex().Match(content);

            return match.Success ? match.Groups[1].Value : null;

        }
        catch (Exception ex)
        {
            Log.Error(ex, $"In {nameof(ExtractUserSecretsId)}");
            return null;
        }
    }

    public static (string[] json, bool exists) ReadSecretFile(string userSecretsId)
    {
        var directory = Utilities.ProjectFolder(userSecretsId);

        if (Directory.Exists(directory))
        {

            var file = Path.Combine(directory, "secrets.json");
            if (!File.Exists(file)) return ([], false);

            var lines = File.ReadAllLines(file);
            var isEmpty = Utilities.IsEmptyJsonObject(File.ReadAllText(file));

            return (lines, !isEmpty);
        }

        return ([], false);
    }

    public static JsonSerializerOptions Indented => new() { WriteIndented = true };

    [GeneratedRegex(@"<UserSecretsId>(.*?)</UserSecretsId>", RegexOptions.IgnoreCase, "en-US")]
    private static partial Regex GenerateUserSecretsIdRegex();
}

The learning process

  1. Change the property in appsetting.json VisualStudioFolder to a common folder for your Visual Studio solutions which have a least one project with user secrets.
  2. Run FindUserSecretsApp
  3. Examine UserProjects.json and UserSecretsProjects.json under FindUserSecretsApp executable folder.
  4. This step is important, set a breakpoint on the first line of code in the main method and step through the code or for seasoned developers examine all provided code.

If there are any runtime issues see the log files under LogFile folder under FindUserSecretsApp executable folder.

Guarantees (there are none)

For some developers, the utility will fail as this code has been tested on a limited set of secrets, and for other developers, it will work.

If there are failures, this is an opportunity to figure out the problem using the best debugger in Visual Studio.

Development environment

Microsoft VS2022 17.14.6 (June 2025)

Summary

This article aims to show that side projects can be useful as developers’ tools rather than conventional side projects on the web, along with learning at least one new thing, which is guaranteed.


This content originally appeared on DEV Community and was authored by Karen Payne