ABP frame entry pit - configure User Secrets

Configure User Secrets

ABP pit record - Contents

cause

In the past, I used to save the connection string and other information in User Secrets, but when I moved the connection string to secrets.json, I found that the following errors would be reported during migration:

In short, connection string information cannot be obtained during migration.

Solution

  1. In the qincaid.entityframeworkcore project, locate the QincaiDbContextFactory.cs file, and modify the code in the comments below.

    public class QincaiDbContextFactory : IDesignTimeDbContextFactory<QincaiDbContext>
    {
        public QincaiDbContext CreateDbContext(string[] args)
        {
            var builder = new DbContextOptionsBuilder<QincaiDbContext>();
            //var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
            var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder(), addUserSecrets: true);
    
            QincaiDbContextConfigurer.Configure(builder, configuration.GetConnectionString(QincaiConsts.ConnectionStringName));
    
            return new QincaiDbContext(builder.Options);
        }
    }
    

experience

It seems easy to solve this problem, but it took me a lot of time to find out the reason.

First of all, I wonder if there is a problem with the secrets.json file, so I deleted the UserSecretsId in Qincai.Web.Host.csproj and regenerated it. Unfortunately, this is not only the migration problem, but also the application can't be started. Of course, the error message can't find the connection string.

So, I started to find the code to inject configuration in StartUp, and then found that Module Zero implemented an extension method, IHostingEnvironment.GetAppConfiguration, which is different from the method of directly injecting IConfiguration objects into Microsoft template code. Its source code is as follows:

public static IConfigurationRoot GetAppConfiguration(this IHostingEnvironment env)
{
    // The third parameter here indicates whether to add User Secrets
    // You can see that Module Zero is only added in the development environment by default
    return AppConfigurations.Get(env.ContentRootPath, env.EnvironmentName, env.IsDevelopment());
}

The AppConfigurations class is called here. Note that this class is defined in the Qincai.Core project, which is the key to the problem.

Then, let's look at this code in the AppConfigurations class:

private static IConfigurationRoot BuildConfiguration(string path, string environmentName = null, bool addUserSecrets = false)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(path)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

    if (!environmentName.IsNullOrWhiteSpace())
    {
        builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true);
    }

    builder = builder.AddEnvironmentVariables();

    if (addUserSecrets)
    {
        // User Secrets added here
        builder.AddUserSecrets(typeof(AppConfigurations).GetAssembly());
    }

    return builder.Build();
}

At first glance, it seems to be OK, but notice that I just said that AppConfigurations belongs to the Qincai.Core project, that is, according to the default configuration, typeof(AppConfigurations).GetAssembly() gets the assembly under Qincai.Core.dll.

In other words, the User Secrets added here are based on the UserSecretsId defined in Qincai.Core.csproj, not Qincai.Web.Host.csproj.

Open Qincai.Core.csproj, and we do notice that there is a UserSecretsId field, which is the same as before the unmodifier in Qincai.Web.Host.csproj.

This is where Module Zero comes in handy. In VS, only the Web project can find the management user secret in the right-click. Therefore, two identical UserSecretsId can be configured to modify the secrets.json file of Qincai.Core project through vs shortcut.

Now that the reason is found, just restore the UserSecretsId to be consistent, and tap F5 happily to start successfully.

Therefore, once you modify the UserSecretsId in Qincai.Web.Host.csproj, don't forget to modify Qincai.Core.csproj. Make sure that the two UserSecretsId are the same, otherwise you can't change the program any more.

Isn't it over? It's weird!! Our problem is that User Secrets cannot be read during migration. The above experience can only be said to return to the starting point.

At this point, we can successfully read User Secrets when the program is running, but in the process of database migration, an error will still be reported:

System.ArgumentNullException: Value cannot be null.
Parameter name: connectionString

Let's open the ef tool Detailed output - v Let's take a look at the output:

Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider...
Finding IWebHost accessor...
Using environment 'Development'.
Using application service provider from IWebHost accessor on 'Program'.
Finding DbContext classes in the project...
Found DbContext 'QincaiDbContext'.
Using DbContext factory 'QincaiDbContextFactory'.

It refers to the class QincaiDbContextFactory. The source code is as follows:

/* This class is needed to run "dotnet ef ..." commands from command line on development. Not used anywhere else */
public class QincaiDbContextFactory : IDesignTimeDbContextFactory<QincaiDbContext>
{
    public QincaiDbContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<QincaiDbContext>();
        // Pay attention here
        var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder();

        QincaiDbContextConfigurer.Configure(builder, configuration.GetConnectionString(QincaiConsts.ConnectionStringName));

        return new QincaiDbContext(builder.Options);
    }
}

When we see the comments, we should find the right place. Pay attention to the position I marked in the code. It also obtains the configuration through AppConfigurations.Get, but does not give the AddUserSecrets parameter (default is false). According to the previous code, it does not add User Secrets.

Then the solution is very simple, just give the AddUserSecrets parameter explicitly.

var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder(), addUserSecrets: true);

Keywords: JSON Database

Added by gmbot on Tue, 21 Apr 2020 15:12:37 +0300