2

In my appsettings.test.json I have the following structure.

{
  "ViewOptionsConfiguration": {
    "Default": {
      "GroupingOptions": [
        { "Key": "SortKey1", "Label": "SortVal1" },
        { "Key": "SortKey2", "Label": "SortVal2" }
      ]
    }
  }
}

Then, I can read it using the following syntax (obtaining correct values).

string path = "ViewOptionsConfiguration:Default:GroupingOptions";

string key0 = config.GetValue<string>(path + ":0:Key")
string lab0 = config.GetValue<string>(path + ":0:Label")
string key1 = config.GetValue<string>(path + ":1:Label")
string lab1 = config.GetValue<string>(path + ":1:Label")

Now, I introduced a class ViewOption like so, with intention of mapping the config to an array of such elements.

public class ViewOption 
{
  public string Key { get; set; }
  public string Label { get; set; }
}

I was expecting that the following would work. Regrettably, it seems that the mapped value is null, so I guess the mapping fails recognizing the values.

ViewOption option0 = config.GetValue<ViewOption>(path + ":0")
List<ViewOption> options = config.GetValue<List<ViewOption>>(path)

What am I missing in the mapping?

My workaround is doing something like the below. However, it's ugly as duck and, also, I need to figure out the number of elements for the array separately, which is even uglier and duckier.

ViewOption option0 = new ViewOption(
  config.GetValue<string>(path + ":0:Key"),
  config.GetValue<string>(path + ":0:Label"));
Kirk Larkin
  • 84,915
  • 16
  • 214
  • 203
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • Looks like you misunderstand the idea of mapping options to class in the app. You need the option class that fully represent the model of your appsettings part. It's not 100% necessary, but 100% helps to avoid problems in your code. And I thonk you want DI to be used, no? – Ivan Khorin Jun 04 '21 at 19:00
  • @IvanKhorin Are you saying that I need to introduce a class that consists of **all the elements** in my settings file? I tried that too and seemingly got `null` as well. I would expect **some** way to map in individual parts of the config like, e.g. connection string is pre-implemented in .NET. – Konrad Viltersten Jun 04 '21 at 19:03
  • Give me some time, I'll show how I think it would be right. – Ivan Khorin Jun 04 '21 at 19:13
  • Do you expect your configuration settings file have other sections under ViewOptionsConfiguration besides "Default" ? – alexm Jun 04 '21 at 19:15
  • @alexm Yes, I do. There will be other options on the same level as *ViewOptionsConfig* (like *MetaOptionsConfig* and *MiscConfig*) and inside it we'll have *Default* but also *Name1View*, etc. The views may be assumed to be fixed to 4 different (there might be a 5:th in the future but we're talking months from now). In each view, I'll always have *GroupingOptions* and *SortingOptions* but the count of such options will vary between the views. – Konrad Viltersten Jun 05 '21 at 14:40

2 Answers2

1

If I understand it correctly the intention here is to use a custom type to represent a fragment of data from configuration.

Options Pattern was introduced to decouple custom settings from configuration API (such as IConfiguration). An option normally binds to a section with a name representing that option instance. In your case it could be a class with GroupingOptions property:

public class MyViewOptions 
{
    public List<MyGroupingOption> GroupingOptions {get; set;}
}

public class MyGroupingOption
{
    public string Key {get; set;}
    public string Value {get; set;}
}

[...]

// Bind configuration sections to corresponding named options


public void ConfigureServices(IServiceCollection services)
{
    // Bind default (unnamed) option
    services.Configure<MyViewOptions>(Configuration.GetSection("ViewOptionsConfiguration:Default");   

    // Bind named options
    services.Configure<MyViewOptions>(Configuration.GetSection("ABC", "ViewOptionsConfiguration:ABC");   

    services.Configure<MyViewOptions>(Configuration.GetSection("DEF", "ViewOptionsConfiguration:DEF");   

}

Usage:

 // for default options only
 MyService(IOptions<MyViewOptions> options)
 {
    var opts = options.Value;
 }


 // for named (and default) options

 MyService(IOptionsSnapshot<MyViewOptions> options)
 {
      var defaultOptions = options.Value;
      var abcOptions = options.Get("ABC");
      var defOptions = options.Get("DEF");
 }
alexm
  • 6,854
  • 20
  • 24
1

I think you are pretty much close. I have done something like below into .Net 5 targeted console app. I have posted my full answer for someone else on this link.

List<string> emailAddresses = _config.GetSection("EmailAddresses").Get<List<string>>();
        foreach (string emailAddress in emailAddresses)
        {

            Console.WriteLine(emailAddress);
        }

My appsettings.json is:

{
  "AppSettings": {

    "FTPLocation": "\\\\FTPHostname\\\\c$\\\\Folder\\\\ftpSite\\\\Prod\\",
    "FTPUri": "ftp://ftphostname/myFolder/",
    "CSVFileName": "TestData.csv",
    },
  "ConnectionStrings": {
    "AppDbConnString": "Server=sqlserverhost.domain.com;Database=DBName; Trusted_Connection=True; MultipleActiveResultSets=true"
    ,
"IdentityConnection": "Server=hostname.domain.com;Database=IdentityDBName; Trusted_Connection=True; MultipleActiveResultSets=true"
      },  
    "EmailAddresses": [
          "email1@test.com",
          "email2@test.com",
          "email3@test.com"
        ],  
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      }
 }
Chinmay T
  • 745
  • 1
  • 9
  • 17
  • 1
    Yeah, I was almost there, turns out. I only needed to add an empty default constructor (which is there by default but was eliminated but me adding `ViewOption(string key,string label){...}` to the class for convenience. Well... convenience my butt! – Konrad Viltersten Jun 05 '21 at 14:57