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.

Casey Crookston
  • 13,016
  • 24
  • 107
  • 193

5 Answers5

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.

silkfire
  • 24,585
  • 15
  • 82
  • 105
  • 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. – Casey Crookston Feb 27 '20 at 20:34
  • @CaseyCrookston Okay, I usually do this with DI. Could you try out the suggestion in my edit? – silkfire Feb 27 '20 at 20:36
  • 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`. – Casey Crookston Feb 27 '20 at 20:37
  • 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. – silkfire Feb 27 '20 at 20:38
  • fixtures? Not familiar. – Casey Crookston Feb 27 '20 at 20:39
  • Could you post some code of your unit test setup? What testing framework are you using? – silkfire Feb 27 '20 at 20:40
  • The framework is just out of the box Visual Studio. Add > New Project > xUnit Test Project (.NET Core). As for setup, again, I'm just out of the box starting with an empty test class, going one step at a time. – Casey Crookston Feb 27 '20 at 20:42
  • @CaseyCrookston Read more about them here: https://xunit.net/docs/shared-context#class-fixture – silkfire Feb 27 '20 at 20:56
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.

Casey Crookston
  • 13,016
  • 24
  • 107
  • 193
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);
ryanwebjackson
  • 1,017
  • 6
  • 22
  • 36
MarredCheese
  • 17,541
  • 8
  • 92
  • 91
  • Great answer, this helped me immediately – dennis_vok Dec 20 '22 at 14:13
  • `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. – ryanwebjackson Jan 07 '23 at 13:39
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");
}
GeersJ
  • 105
  • 2
  • 8
  • 1
    Why would they need a new instance of IConfiguration when they already have one? – nollidge Feb 27 '20 at 20:29
  • 1
    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("AppSettings");` – Casey Crookston Feb 27 '20 at 20:29
0

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

nollidge
  • 2,192
  • 12
  • 13