112

I am familiar with loading an appsettings.json section into a strongly typed object in .NET Core Startup.cs. For example:

public class CustomSection 
{
   public int A {get;set;}
   public int B {get;set;}
}

//In Startup.cs
services.Configure<CustomSection>(Configuration.GetSection("CustomSection"));

//Inject an IOptions instance
public HomeController(IOptions<CustomSection> options) 
{
    var settings = options.Value;
}

I have an appsettings.json section who's key/value pairs will vary in number and name over time. Therefore, it's not practical to hard code property names in a class since new key/value pairs would require a code change in the class. A small sample of some key/value pairs:

"MobileConfigInfo": {
    "appointment-confirmed": "We've booked your appointment. See you soon!",
    "appointments-book": "New Appointment",
    "appointments-null": "We could not locate any upcoming appointments for you.",
    "availability-null": "Sorry, there are no available times on this date. Please try another."
}

Is there a way to load this data into a MobileConfigInfo Dictionary<string, string> object and then use the IOptions pattern to inject MobileConfigInfo into a controller?

Jim Aho
  • 9,932
  • 15
  • 56
  • 87
ChrisP
  • 9,796
  • 21
  • 77
  • 121
  • 1
    Hmmm isnt this question really about ASP.NET Core and not .NET Core? Bit of a misleading title. – bytedev Feb 07 '18 at 12:21

14 Answers14

82

Go with this structure format:

"MobileConfigInfo": {
    "Values": {
       "appointment-confirmed": "We've booked your appointment. See you soon!",
       "appointments-book": "New Appointment",
       "appointments-null": "We could not locate any upcoming appointments for you.",
       "availability-null": "Sorry, there are no available times on this date. Please try another."
 }
}

Make your setting class look like this:

public class CustomSection 
{
   public Dictionary<string, string> Values {get;set;}
}

then do this

services.Configure<CustomSection>((settings) =>
{
     Configuration.GetSection("MobileConfigInfo").Bind(settings);
});
code5
  • 4,434
  • 2
  • 31
  • 24
  • 2
    This worked for me. Also works if the dictionary is deeper down the section configuration, with the bind call as is. – Miguel Hughes Apr 18 '18 at 16:59
  • 8
    Adding to this, so long as you use `services.AddOptions()` from `Microsoft.Extensions.Options`, you don't need to use `Bind`. This is enough: `services.Configure(Configuration.GetSection("MobileConfigInfo"));` – J.D. Mallen Jan 25 '21 at 18:42
  • I can confirm that `services.Configure(Configuration.GetSection("MobileConfigInfo"));` Works well in .NET 7 without the Bind. And probably works prior to .NET 7. – irperez Aug 17 '23 at 13:51
80

For others who want to convert it to a Dictionary,

sample section inside appsettings.json

"MailSettings": {
    "Server": "http://mail.mydomain.com"        
    "Port": "25",
    "From": "info@mydomain.com"
 }

Following code should be put inside the Startup file > ConfigureServices method:

public static Dictionary<string, object> MailSettings { get; private set; }

public void ConfigureServices(IServiceCollection services)
{
    //ConfigureServices code......

    MailSettings = Configuration.GetSection("MailSettings").GetChildren()
                  .ToDictionary(x => x.Key, x => x.Value);
}

Now you can access the dictionary from anywhere like:

string mailServer = Startup.MailSettings["Server"];

One downside is that all values will be retrieved as strings, if you try any other type the value will be null.

Amro
  • 1,369
  • 2
  • 10
  • 16
  • 8
    You don't need the `.Select(item => new KeyValuePair(item.Key, item.Value))` part. You can just call `GetChildren().ToDictionary(x => x.Key, x => x.Value)`. – Alternatex Nov 05 '18 at 09:14
  • 1
    this should be the accepted answer, this is by far the simplest and most elegant solution. – Alexander Oh Sep 10 '20 at 08:21
  • 1
    If you need a non-interfaced `Dictionary` then you can call `new Dictionary(Configuration.GetSection("mysettings").AsEnumerable())` – Dan Nov 24 '20 at 17:00
44

I believe you can use the following code:

var config =  Configuration.GetSection("MobileConfigInfo").Get<Dictionary<string, string>>(); 
macpak
  • 1,190
  • 1
  • 14
  • 28
39

You can use Configuration.Bind(settings); in startup.cs class

And your settings class will be like

public class AppSettings
{
    public Dictionary<string, string> MobileConfigInfo
    {
        get;
        set;
    }
}

Hope it helps!

Hung Cao
  • 3,130
  • 3
  • 20
  • 29
  • I am trying to understand the example you provided, but it is very confusing. What is `settings`? – Catalin Jul 18 '18 at 11:06
  • 1
    @Catalin `settings` is an instance of `AppSettings` class. The `Bind` method will map the values in your `config.json` to your `settings` instance. After that you can use `.AddSingleton` or something like it to inject it to other classes – Hung Cao Jul 18 '18 at 19:36
  • 9
    This does not work for classes with a `Dictionary` property. The Bind method fails at runtime with: `System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.'` – Alistair Green Jan 15 '20 at 15:31
33

By far the simplest method would be to define your configuration class to inherit from the Dictionary type you want to support.

public class MobileConfigInfo:Dictionary<string, string>{
}

Then your startup and dependency injection support would be exactly the same as for any other configuration type.

Jeremy Lakeman
  • 9,515
  • 25
  • 29
14

In .NET Core 3.1 you can do something like the following...

appsettings.json:

{
  "myConfig": {
    "foo": "bar",
    "myMappings": {
      "key1": "value1",
      "key2": "value2"
    }
  }
}

A configuration model

MyConfig.cs

public class MyConfig
{
    public string Foo { get; set; }
    public Dictionary<string, string> MyMappings { get; set; }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfig>(configuration.GetSection("myConfig"));

Class using the options:

public class OptionsUsingClass
{
    public OptionsUsingClass(IOptions<MyConfig> myConfigOptions)
    {
        // Be wary of nulls in real code.
        var myConfig = myConfigOptions.Value;

        // Examples with the above data.
        myConfig.Foo.Should().Be("bar");

        myConfig.MyMappings["key1"].Should().Be("value1");
        myConfig.MyMappings["key2"].Should().Be("value2");
    }

This was how I used appsettings.json dictionary mappings.

ozzy
  • 999
  • 12
  • 12
9

You can do it on the fly:

appsettings.json:
{
   "MobileConfigInfo": {
      "a": "x",
      "b": "y",
      "c": "z"
    }
}

Somewhere in code: (don't forget to add IConfiguration dependency to the class constructor)

var yourDictionary = _configuration.GetSection("MobileConfigInfo")
                .Get<IDictionary<string, string>>();
Peter Hedberg
  • 3,487
  • 2
  • 28
  • 36
Egor Sindeev
  • 149
  • 2
  • 2
8

For simple (perhaps microservice) applications you can just add it it as a singleton Dictionary<string, string> and then inject it wherever you need it:

var mobileConfig = Configuration.GetSection("MobileConfigInfo")
                    .GetChildren().ToDictionary(x => x.Key, x => x.Value);

services.AddSingleton(mobileConfig);

And the usage:

public class MyDependantClass
{
    private readonly Dictionary<string, string> _mobileConfig;

    public MyDependantClass(Dictionary<string, string> mobileConfig)
    {
        _mobileConfig = mobileConfig;
    }

    // Use your mobile config here
}
ste-fu
  • 6,879
  • 3
  • 27
  • 46
8

I use the way below:

appsettings.json:

  "services": {
      "user-service": "http://user-service:5000/",
      "app-service": "http://app-service:5000/"
  } 

startup.cs:

  services.Configure<Dictionary<string, string>>(Configuration.GetSection("services"));

Usage:

private readonly Dictionary<string, string> _services;
public YourConstructor(IOptions<Dictionary<string, string>> servicesAccessor)
{
    _services = servicesAccessor.Value;
}
ErikXu
  • 81
  • 1
  • 1
  • 1
    Not sure why this isn't the top answer as nearly every other answer missed the fact the OP wanted to consume them through IOptions and dependency injection. This has really helped me anyway, thank you. – Matt Feb 03 '21 at 17:16
5

The only thing that worked for me (ASP.NET Core 3.0) was to add the following to the ConfigureServices method of Startup.cs:

services.Configure<Dictionary<string, string>>(dict => Configuration
    .GetSection("MySectionName")
    .GetChildren()
    .ToList()
    .ForEach(c => dict[c.Key] = c.Value));
Jesper Mygind
  • 2,406
  • 1
  • 18
  • 20
4

As an example of more complex binding in ASP.Net Core 2.1; I found using the ConfigurationBuilder .Get<T>() method far easier to work with, as per the documention.

ASP.NET Core 1.1 and higher can use Get, which works with entire sections. Get can be more convenient than using Bind.

I bound the configuration in my Startup method.

private Config Config { get; }

public Startup(IConfiguration Configuration)
{
    Config = Configuration.Get<Config>();
}

This binds the appsettings file:

{
    "ConnectionStrings": {
        "Accounts": "Server=localhost;Database=Accounts;Trusted_Connection=True;",
        "test": "Server=localhost;Database=test;Trusted_Connection=True;",
        "Client": "Server=localhost;Database={DYNAMICALLY_BOUND_CONTEXT};Trusted_Connection=True;",
        "Support": "Server=localhost;Database=Support;Trusted_Connection=True;"
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "Plugins": {
        "SMS": {
            "RouteMobile": {
                "Scheme": "https",
                "Host": "remote.host",
                "Port": 84567,
                "Path": "/bulksms",
                "Username": "username",
                "Password": "password",
                "Source": "CompanyName",
                "DeliveryReporting": true,
                "MessageType": "Unicode"
            }
        },
        "SMTP": {
            "GenericSmtp": {
                "Scheme": "https",
                "Host": "mail.host",
                "Port": 25,
                "EnableSsl": true,
                "Username": "smtpuser@mail.host",
                "Password": "password",
                "DefaultSender": "noreply@companyname.co.uk"
            }
        }
    }
}

Into this configuration structure:

[DataContract]
public class Config
{
    [DataMember]
    public Dictionary<string, string> ConnectionStrings { get; set; }
    [DataMember]
    public PluginCollection Plugins { get; set; }
}

[DataContract]
public class PluginCollection
{
    [DataMember]
    public Dictionary<string, SmsConfiguration> Sms { get; set; }
    [DataMember]
    public Dictionary<string, EmailConfiguration> Smtp { get; set; }
}

[DataContract]
public class SmsConfiguration
{
    [DataMember]
    public string Scheme { get; set; }
    [DataMember]
    public string Host { get; set; }
    [DataMember]
    public int Port { get; set; }
    [DataMember]
    public string Path { get; set; }
    [DataMember]
    public string Username { get; set; }
    [DataMember]
    public string Password { get; set; }
    [DataMember]
    public string Source { get; set; }
    [DataMember]
    public bool DeliveryReporting { get; set; }
    [DataMember]
    public string Encoding { get; set; }
}

[DataContract]
public class EmailConfiguration
{
    [DataMember]
    public string Scheme { get; set; }
    [DataMember]
    public string Host { get; set; }
    [DataMember]
    public int Port { get; set; }
    [DataMember]
    public string Path { get; set; }
    [DataMember]
    public string Username { get; set; }
    [DataMember]
    public string Password { get; set; }
    [DataMember]
    public string DefaultSender { get; set; }
    [DataMember]
    public bool EnableSsl { get; set; }
}
derpasaurus
  • 397
  • 3
  • 13
  • This is in fact the best solution. Note, it's possible to specify a section, e.g. `GetSection("Plugins")` but then this section itself cannot be part of the binding. Although even without such specifying `Get` will bind only existed models and ignore all other parts of json. Perfect! Also, since Net Core 3.5 class attributes are optional. – Niksr May 16 '21 at 13:20
1

The better way for me without changing appsetting:

"CustomSection": {
    "appointment-confirmed": "We've booked your appointment. See you soon!",
    "appointments-book": "New Appointment",
    "appointments-null": "We could not locate any upcoming appointments for you.",
    "availability-null": "Sorry, there are no available times on this date. Please try another."
}


public class CustomSection : Dictionary<string, string> {}

//In Startup.cs
services.AddOptions<CustomSection>().Bind(Configuration.GetSection("CustomSection"));

//Inject an IOptions instance
public HomeController(IOptions<CustomSection> options) 
{
    var settings = options.Value;
}
AlexeyS
  • 11
  • 2
0

I have a generic solution for setting dictionary type properties, such as the dictionary of HTML attributes that are retrieved from options.

Default dictionary values ​​can be set in options. If the same key exists in the section, then the value in the dictionary is overwritten, otherwise a key-value pair is inserted.

The dictionary is of type IDictionary<string, object> and the read values ​​are not parsed, they are set as type string.

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class JJurOptionsExtensions
    {
        /// <summary>
        /// Binding configuration of the property of type IDictionary {string, object}
        /// </summary>
        /// <typeparam name="TOptions">
        /// The type of class that contains the property to be set
        /// </typeparam>
        /// <param name="services">
        /// IoC container
        /// </param>
        /// <param name="section">
        /// Section containing key-value pairs for the dictionary to be set
        /// </param>
        /// <returns>
        /// IServiceCollection
        /// </returns>
        /// <param name="property">
        /// Delegate of the property to be set
        /// </param>
        public static IServiceCollection ConfigureDictionary<TOptions>(
            this IServiceCollection services,
            IConfigurationSection section,
            Func<TOptions, IDictionary<string, object>> property)
                where TOptions : class
        {
            var values = section        // List of sub-sections
                .GetChildren()
                .ToList();

            services.Configure<TOptions>(options =>
            {
                var dict = property(options);
                values.ForEach(v =>
                {
                    // If there is not key, then insert it.
                    // If there is, override the value.

                    dict[v.Key] = v.Value;
                });
            });

            return services;
        }
    }
}

An example of use:

        services.Configure<JJurCoreLibs.HtmlSortMnu.SortMenuOptions>(
                options => configuration.GetSection("SortMenuOptions").Bind(options)
            )
            .ConfigureDictionary<JJurCoreLibs.HtmlSortMnu.SortMenuOptions>(
                configuration.GetSection("SortMenuOptions:DropDownBbtnHtmlAttributes"),
                o => o.DropDownBbtnHtmlAttributes);

The SortMenuOptions class contains a property named DropDownBtnHtmlAttribute of type Dictionary<string, object>.

using System.Collections.Generic;

namespace JJurCoreLibs.HtmlSortMnu
{
    /// <summary>
    /// Options of the Bootstrap dropdown creating service for sorting items
    /// </summary>
    public class SortMenuOptions
    {
...
        public DropDownBbtnHtmlAttributes DropDownBbtnHtmlAttributes { get; } = new DropDownBbtnHtmlAttributes {
            { "role", "button" },
            { "data-toggle", "dropdown" },
            { "aria-expanded", false },
            { "aria-haspopup", true }
        };
    }

    public class DropDownBbtnHtmlAttributes : Dictionary<string, object> { }
}

Jan Jurníček
  • 126
  • 1
  • 5
0
You can do this in .Net Core 6. Please find the following code sample
**appsettings.json**
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AppSettings": {
    "dbConnection": "Data Source=myServerName;Initial Catalog=dbName;persist security info=True;User Id=userId;Password=testPWD;MultipleActiveResultSets=True",
    "sendereMail": "test@testdomain.com",
    "MQDetails": {
      "hostName": "testHost",
      "username": "testUser",
      "passWord": "testPwd",
      "exchangeName": "testName"
    }
  }
}
**AppSettings.cs**
public class AppSettings
    {
        public string? dbConnection { get; set; }
        public string? sendereMail { get; set; }
        public Dictionary<string, string>? MQDetails { get; set; }
    }
**Program.cs**

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
builder.Services.AddConnections();
  
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    //The generated Swagger JSON file will have these properties.
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API POC",
        Version = "v1",
    });
});


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyPOC");
        c.RoutePrefix = string.Empty;
    });
    app.UseExceptionHandler("/Error");
}
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
    endpoints.MapControllers();
});

app.Run();

**ApiController**
namespace DotNet6.Controller
{
    [Route("api/[controller]")]
    [ApiController]
    public class GetConfigurationsController : ControllerBase
    {
        private readonly AppSettings appSettings;
        public GetConfigurationsController(IOptions<AppSettings> options)
        {
            appSettings = options.Value;
        }

        [HttpGet("appsettings")]
        public AppSettings Get()
        {

            return appSettings;
        }
    }
}

enter image description here enter image description here

Govind
  • 186
  • 5