2

There are many examples out there that show how to deserialize your app settings within the Startup.cs class. I get that. But that's not what I'm asking. I'm working within a unit test constructor.

This question is close: Read appsettings json values in .NET Core Test Project But in reading it carefully, I'm not seeing a slam dunk answer.

appsettings.json:

{
  "AppSettings": {
    "myKey1": "myValue1",
    "myKey2": "myValue2",
  }
}

And:

public class AppSettings
{
    public string myKey1 { get; set; }
    public string myKey2 { get; set; }
}

I am able to get single values just fine:

var config = new ConfigurationBuilder()
              .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
              .Build();
var myValue1 = config.GetSection("AppSettings:myKey1 ").Value;

But what I'm not figuring out is how to get create an instance of AppSettings. I've tried:

var appSettings = config.GetValue<AppSettings>("AppSettings");

But that ends up null. And I've played around with trying to create my own instance of IServiceCollection so I could do something like you might in Startup.cs, but I'm not making any progress.

5 Answers 5

1

You have to use the Microsoft.Extensions.Configuration.Binder extension, available as a separate package on NuGet.

Specifically, use the Configuration.Bind() method:

AppSettings appSettings;

config.Bind(appSettings);

Or even simpler:

var appSettings = config.Get<AppSettings>();

You could also bind it with help of DI:

services.Configure<AppSettings>(_configuration.GetSection("AppSettings"));

Then in your service that needs these settings, just add AppSettings appSettings as a dependency to it and the dependency should auto-resolve.

Sign up to request clarification or add additional context in comments.

8 Comments

I think this is the same thing nollidge is suggesting? If so, it does indeed create an instance of AppSettings but all of the properties are null.
@CaseyCrookston Okay, I usually do this with DI. Could you try out the suggestion in my edit?
So as I said in the OP, this is not iniside of a standard project This is a unit test. So there is no startup.cs where I can inject using services.
You could definitely use dependency injection in a unit test. Have you tried out fixtures? They're really powerful and allow you to create your own DI container to be used by your unit tests.
fixtures? Not familiar.
|
1

I ended up just doing this.

public class AppSettings
{
    AppSettings(IConfigurationRoot config)
    {
            myKey1 = config.GetSection("AppSettings:MyKey1").Value;
            myKey2 = config.GetSection("AppSettings:MyKey2").Value;
    }

    public string myKey1 { get; set; }
    public string myKey2 { get; set; }
}

Not ideal but it gets the job done and I can move on.

Comments

1

Short answer

var section = config.GetSection(nameof(AppSettings));
var settings = section.Get<AppSettings>

Long answer

You can't just do config.Get<AppSettings> as implied by other answers. That tries to bind the whole config file to that class, which doesn't work, resulting in silent failure (null members).

Microsoft documentation references for IConfiguration.Get method:

You need to get the section first before deserializing to a settings object instance:

var section = config.GetSection(nameof(AppSettings));
var settings = section.Get<AppSettings>

You can use Bind instead of Get if desired:

var section = config.GetSection(nameof(AppSettings));
var settings = new AppSettings();
section.Bind(settings);

There are other overloads for if your section's name doesn't match your settings class's name.

Bonus: extension methods

public static class IConfigurationExtensions
{
    public static T GetSection<T>(this IConfiguration config) where T : class, new()
    {
        IConfigurationSection section = config.GetSection(typeof(T).Name);
        return section.Get<T>();
    }
}

// Usage
var settings = config.GetSection<AppSettings>();

Going beyond the original question, you can inject these settings sections like this (using the extension method above):

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddSingletonConfigSection<T>(
        this IServiceCollection services, IConfiguration config) where T : class, new()
    {
        T section = config.GetSection<T>();
        services.AddSingleton<T>(section);
        return services;
    }

    public static IServiceCollection AddScopedConfigSection<T>(
        this IServiceCollection services, IConfiguration config) where T : class, new()
    {
        services.AddScoped<T>(_ => config.GetSection<T>());
        return services;
    }
}

// Usage
services.AddSingletonConfigSection<AppSettings>(config);
services.AddScopedConfigSection<AppSettings>(config);

2 Comments

Great answer, this helped me immediately
There are other overloads for if your section's name doesn't match your settings class's name. What are these? I don't see anything in the ConfigurationBinder options for this specification.
0

I believe you would have to create a new instance of IConfiguration and then get its values like you already are doing.

public UnitTestConstructor(IConfiguration configuration){
      Configuration = configuration;
}

public IConfiguration Configuration { get; }

public TestOne(){
      var appSettingsValue = Configuration.GetValue<string>("AppSettings:MyValue");
}

2 Comments

Why would they need a new instance of IConfiguration when they already have one?
I'm not sure you understood my question. I know how to get AppSettings:MyValue as a string. What I am tying to get is this: Configuration.GetValue<AppSetting>("AppSettings");
0

Sounds like you want to use ConfigurationBinder.Get<T>() (instead of ConfigurationBinder.GetValue<T>()

3 Comments

This got me close! But all properties of AppSettings are null.
Are you sure your appsettings.json is being written to the output dir? IConfiguration will return null for whatever you pass it if the keys don't exist to begin with.
CopyToOutputDirectory is set to copyIfNewer. Is that what you are asking?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.