How to handle a hierarchy of properties in ASP.NET 5 AppSettings? - c#

In ASP.NET 4 to organize settings, I am prefixing the setting key with a small word that indicates where this config is used (e.g. key="dms:url", "sms:fromNumber" .... etc).
In ASP.NET 5, the AppSettings configuration is mapped to a strongly typed class.
what is the property that i need to build for "dms:url"? How could map dashes & special chars to a C# property in ASP.NET 5?

You can organize your configuration file within a hierarchy in the config.json
{
"AppSettings": {
"SiteTitle": "PresentationDemo.Web",
"Dms": {
"Url": "http://google.com",
"MaxRetries": "5"
},
"Sms": {
"FromNumber": "5551234567",
"APIKey": "fhjkhededeudoiewueoi"
}
},
"Data": {
"DefaultConnection": {
"ConnectionString": "MyConnectionStringHere. Included to show you can use the same config file to process both strongly typed and directly referenced values"
}
}
}
We defined the AppSettings as a POCO class.
public class AppSettings
{
public AppSettings()
{
Dms = new Dms(); // need to instantiate (Configuration only sets properties not create the object)
Sms = new Sms(); // same
}
public string SiteTitle { get; set; }
public Dms Dms { get; set; }
public Sms Sms { get; set; }
}
public class Dms
{
public string Url { get; set; }
public int MaxRetries { get; set; }
}
public class Sms
{
public string FromNumber { get; set; }
public string ApiKey { get; set; }
}
We then load the configuration into an instance of IConfigurationSourceRoot and then set values of AppSettings using GetSubKey. The best practice would be to do this in ConfigureServices and add it to the DI Container.
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
var configuration = new Configuration()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
}
public void ConfigureServices(IServiceCollection services)
{
// Add Application settings to the services container.
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
//Notice we can also reference elements directly from Configuration using : notation
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
}
We can now provide access in a controller through the constructor. I set the setting values explicitly the constructor but you could use the entire IOptions
public class HomeController : Controller
{
private string _title;
private string _fromNumber;
private int _maxRetries;
public HomeController(IOptions<AppSettings> settings)
{
_title = settings.Options.SiteTitle;
_fromNumber = settings.Options.Sms.FromNumber;
_maxRetries = settings.Options.Dms.MaxRetries;
}
If you wanted to keep everything flat and use a pseudo hierarchy like you have been doing, you can, but ":" isn't a valid symbol for a variable name. You would need to use a valid symbol like "_" or "-" instead.

Related

Azure Function: Difference between AddOptions and Configure method of IServiceCollection

I am using Options Pattern within Azure Function. I created custom classes to represent the config data.
Below is my settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"ServiceBusConfigOptions:EventTypeTopic": "",
"ServiceBusConfigOptions:EventTypeTopicSubscription": "",
"ServiceBusConfigOptions:ConnectionString": "",
"ServiceBusConfigOptions:SendEmailTopic": "",
"ServiceBusConfigOptions:NotificationMonitoringTopic": "",
"EmailConfigOptions:FromAddress": "",
"EmailConfigOptions:Subject": "",
"EmailConfigOptions:MailingServiceAccount": "",
"EmailConfigOptions:EmailTemplateDefaultValue": "",
}
}
My EmailConfiOptions class:
public class EmailConfigOptions
{
public string FromAddress { get; set; }
public string Subject { get; set; }
public string MailingServiceAccount { get; set; }
public string EmailTemplateDefaultValue { get; set; }
}
ServiceBusConfigOptions class:
public class ServiceBusConfigOptions
{
public string ConnectionString { get; set; }
public string EventTypeTopic { get; set; }
public string EventTypeTopicSubscription { get; set; }
public string SendEmailTopic { get; set; }
public string NotificationMonitoringTopic { get; set; }
}
In the Startup.cs class I can register with both methods AddOptions and Configure(as shown in the below).
So basically which method should we use? AddOptions or Configure.
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging();
builder.Services.AddOptions<ServiceBusConfigOptions>()
.Bind(builder.GetContext().Configuration.GetSection(nameof(ServiceBusConfigOptions)));
builder.Services.Configure<EmailConfigOptions>(builder.GetContext().Configuration.GetSection(nameof(EmailConfigOptions)));
builder.Services.AddSingleton<IValidateOptions<EmailConfigOptions>, EmailConfiOptionsValidator>();
builder.Services.AddSingleton<IValidateOptions<ServiceBusConfigOptions>, ServiceBusConfigOptionsValidator>();
}
}
The ConfigurationBuilder Class and the extension methods used to configure various files in your code.
In a ConfigurationBuilder we use Configure property to inject the settings of our custom or predefined services.
which method should we use? AddOptions or Configure.
We can use both Configure or AddOptions to inject the IOptions. But the proper way which follows to avoid conflicts.
If we are trying to inject the Custom IOptions in our Application, we need to use the Services.AddOptions(). to retrieve the exact IOptions value we need to use the Services.Configure< IOptions>()
public override void Configure(IFunctionsHostBuilder builder)
{
# Inject Options
builder.services.AddOptions();
# it inject the Connection String value of ServiceBusConfigOptions
builder.services.Configure<ServiceBusConfigOptions>(Configuration.GetSection("ConnectionString"));
...
}
Reference
Usage of IOptions in Configure Link 1 & 2
Usage of IOptions in AddOptions Link 1 & 2

Net 6 Multiple Connection Strings

How to setup in Net 6 program.cs a multiple connection strings?
I want to work with Development, Staging, and Production environments, all of them pointing to different database servers.
NET 6. Program.cs:
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
Thanks in advance.
Here's what you could do.
First, create an appsettings.json like this:
appsettings.json
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
}
}
},
"WillAppConfig": {
"ActiveEnvironment": "Development",
"DevDatabase": "server:123.123.123.123, user: will, pass:1234",
"STGDatabase": "server:123.123.123.123, user: will, pass:1234",
"ProdDatabase": "server:123.123.123.123, user: will, pass:1234"
}
}
Then create a class somewhere in your project, that will serve to map the configuration to an object.
WillAppConfigurationMap.cs
public class WillAppConfigurationMap
{
public string ActiveEnvironment { get; set; }
public string DevDatabase { get; set; }
public string STGDatabase { get; set; }
public string ProdDatabase { get; set; }
}
Finally in your Program.cs, you could select the connection string to use depending on the value of ActiveEnvironment.
var builder = WebApplication.CreateBuilder(args);
WillAppConfig = builder.Configuration.GetSection("WillAppConfig").Get<WillAppConfigurationMap>();
var connectionString = "";
if (WillAppConfig.ActiveEnvironment == "Development")
{
connectionString = WillAppConfig.DevDatabase
}
else if (WillAppConfig.ActiveEnvironment == "Staging")
{
connectionString = WillAppConfig.STGDatabase
}
else if (WillAppConfig.ActiveEnvironment == "Production")
{
connectionString = WillAppConfig.ProdDatabase
}
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(connectionString));
});
partial class Program
{
public static WillAppConfigurationMap WillAppConfig { get; private set;}
}
You can remove the "Kestrel" section from the appsettings.json if you don't use it. You can use this approach to map any appsettings.json structure.
Then, you can access your configuration object from ANYWHERE in your app doing Program.WillAppConfig.
Create multiple connection strings with different names in appsetting.json:
builder.Services.AddDbContext<MyContext>(options => {
options.UseSqlServer(builder.Configuration.GetConnectionString(UseNameofConnectionString));
});
Also you can create extension method which gives you required connectionString when you call it.

How to read values from multiple json files in .Net Core?

I want to read values from multiple json files. Getting the values from default json file but unable to read separately added json file. below is my code.
//Reads from endpoint-defaults.json
var endpointDefaultsBuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("endpoint-defaults.json", true, true)
.AddEnvironmentVariables();
ConfigurationEndpointDefaults = endpointDefaultsBuilder.Build();
string signOnType = ConfigurationEndpointDefaults.GetSection("AppSettings:mastercard.mip.signOnType").Value;
var mscdGamingList = ConfigurationEndpointDefaults.GetSection("AppSettings:paymentPostCountryProhibitionCheckStage.mscdGamingList.list").Get<string[]>();
//Reads from config.json
var configbuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("jsconfig1.json", true, true)
.AddEnvironmentVariables();
Configuration = configbuilder.Build();
string environmentMode = Configuration.GetSection("AppSettings:mastercard.mip.hostname").Value;
Here is the output.
My Json Files
endpoint-defaults.json
{
"AppSettings": {
//Default MasterCard properties
"mastercard.mip.signOnType": "ONDEMAND",
"mastercard.mip.responseTimeout": 30,
}
}
jsconfig1.js
{
"AppSettings": {
"mastercard.mip.hostname": "localhost",
"mastercard.mip.authorisation_port": 19092,
"mastercard.mip.test_authorisation_port": 19094,
}
}
You can always use multiple json configuration files in building configuration of a .net core application. You can also include XML configuration file.
I have very little to no knowledge about the the configuration structure and the values you are using. So here I will explain with a simple configuration examples and a asp.net core application.
I have two configuration files.
appsettings.json
apisettings.json
Both the above configuration files have multiple configuration sections in them.
//appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"DbSettings": {
"ConnectionString": "Data Source=localhost; Initial Catalog=CareCMSDatabase;User ID=sa; Password=Password1; TimeOut=30;"
},
"SmtpSettings": {
"UserName": "someemail#domain.com",
"Host": "someserver.smtp.com",
"Port": "587",
"Password": "password"
}
}
//apisettings.json
{
"ProductApi": {
"BaseUrl": "https://www.products.com/",
"AuthEndpoint": "api/2.0/Auth/",
"ClientId": "somelcientid",
"ClientSecret": "someclientsecret"
},
"SearchApi": {
"BaseUrl": "https://www.search.com/",
"RandomEndpoint": "api/random",
"AuthToken": "sometoken"
}
}
Then I create C# classes to represent the configuration sections.
public class SmtpSettings
{
public string Host { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public int Port { get; set; }
}
public class DbSettings
{
public string ConnectionString { get; set; }
}
public class ProductApiSettings
{
public string BaseUrl { get; set; }
public string AuthEndpoint { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
}
public class SearchApiSettings
{
public string BaseUrl { get; set; }
public string RandomEndpoint { get; set; }
public string AuthToken { get; set; }
}
Now I add these JSON files to the configuration builder while building Creating HostBuilder in Program.cs file.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration((builder) => {
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile("apisettings.json")
.AddEnvironmentVariables();
});
webBuilder.UseStartup<Startup>();
});
The above code will add the json configuration files to the configuration builder and build the configuration.
I can now retrieve the configuration sections and translate them to C# classes and use the configuration values. In following code I am accessing the configuration sections and their values inside ConfigureServices method of Startup class.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Get DbSettings section from the configuration file.
var dbSettingSection = Configuration.GetSection("DbSettings");
// Get Configuration value and convert it to C# class object.
var dbSettings = dbSettingSection.Get<DbSettings>();
// Now I can access ConnectionString value from the configuration by accessing dbSettings.ConnectionString
//Same as above, get ProductApi Section from the configuration file.
var productApiSection = Configuration.GetSection("ProductApi");
// Get the configuartion value and convert it to C# class object.
var productApiSettings = productApiSection.Get<ProductApiSettings>();
var smtpSection = Configuration.GetSection("SmtpSettings");
var smtpSettings = smtpSection.Get<SmtpSettings>();
var searchApiSection = Configuration.GetSection("SearchApi");
var searchApiSettings = searchApiSection.Get<SearchApiSettings>();
var authToken = Configuration["SearchApi:AuthToken"];
services.AddControllersWithViews();
}
You can also inject these configuration sections as dependencies in other parts of the application such as controller or service class.
For that, you need to add the configuration sections to the service collections so that they get resolved as dependencies. Change ConfigureService method as following.
public void ConfigureServices(IServiceCollection services)
{
var dbSettingSection = Configuration.GetSection("DbSettings");
// Add the section to service collection.
services.Configure<DbSettings>(dbSettingSection);
var productApiSection = Configuration.GetSection("ProductApi");
services.Configure<ProductApiSettings>(productApiSection);
var smtpSection = Configuration.GetSection("SmtpSettings");
services.Configure<SmtpSettings>(smtpSection);
var searchApiSection = Configuration.GetSection("SearchApi");
services.Configure<SearchApiSettings>(searchApiSection);
services.AddControllersWithViews();
}
Now I can have dependency on, let say ProductApi config section, in my HomeController as following.
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
// Field for ProductApiSettings
private ProductApiSettings _productApiSettings;
public HomeController(IOptions<ProductApiSettings> productsApiSettings, Logger<HomeController> logger)
{
_logger = logger;
// Initilizing settings object from the dependency.
// This will have the values retrieved from the json config files.
_productApiSettings = productsApiSettings.Value;
}
public IActionResult Index()
{
// Using properties from the settings
var productApiAuthURL = _productApiSettings.BaseUrl + _productApiSettings.AuthEndpoint;
return View();
}
}
I hope this will help you solve your issue.

POCO object array inside AppSettings.json in ASP.NET Core

This seems like it should be really simple, I have been searching SO and a lot of other places for an answer to this, everything I have found and tried does not work.
I have an appsettings.json file that looks like this
"Email": {
"Port": "25",
"Host": "localhost",
"EnableSSL": "false",
"Credentials": {
"Username": "fakeuser",
"Password": "fakepassword"
},
"SystemFromAddress": "testsender#localhost.com",
"SystemFromDisplayName": "Test Sender",
"EmailTemplateRootDirectory": "Email\\EmailTemplates",
"EmailTemplates": [
{
"TemplateKey": "ResetPassword",
"TemplatePath": "ResetPassword.cshtml"
},
{
"TemplateKey": "NewAccount",
"TemplatePath": "NewAccount.cshtml"
},
{
"TemplateKey": "VerifyEmail",
"TemplatePath": "VerifyEmail.cshtml"
}
]
}
There are several models (EmailOptions being the parent) that I am trying to bind to, the EmailOptions class is expecting to have it's EmailTemplates list populated from the EmailTemplates list in the appsettings.json as seen above.
The parent class is being populated by the appsettings.json file as expected, the Child List of Email Templates in this class is always coming up empty.
Here are the classes I am binding to.
public class EmailOptions
{
public int Port { get; set; }
public string Host { get; set; }
public bool EnableSSL { get; set; }
public EmailCredentials Credentials { get; set; }
public string SystemFromAddress { get; set; }
public string SystemFromDisplayName { get; set; }
public string EmailTemplateRootDirectory { get; set; }
public IEnumerable<EmailTemplate> EmailTemplates { get; set; } = new List<EmailTemplate>();
}
public class EmailTemplate
{
public string TemplateKey { get; set; }
public string TemplatePath { get; set; }
}
public class EmailCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
I am using the following call I am making in my startup class in ASP.NET Core.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<EmailOptions>( _configuration.GetSection("Email" ));
...
For some reason the IEnumerable property in my EmailOptions is not being deserialized from the appsettings.json into my options - when I attempt to use it anywhere in my controllers - the list is always set to an empty array.
FWIW: I have this working in a console application where I have more control over setting up my options from the appsettings.json. Here is what I am doing in the console app, (I am leaving out the code where I set up the options with the DI container for brevity)
var emailSection = configuration.GetSection( "Email" );
var emailOptions = emailSection.Get<EmailOptions>();
emailOptions.EmailTemplates = configuration.GetSection( "Email:EmailTemplates" ).Get<List<EmailTemplate>>();
as expected - in the console application, I get my Email Templates because i have the ability to get the child list separately and add it to the options before handing it over to the DI container. I don't seem to have that flexibility in the ASP.NET Core IServiceCollection.Configure() extension method (so maybe use another method to do this? which one? After a couple hours of searching I am crying uncle and asking for help).
So how does one get this to work using the ASP.NET Core "IServiceCollection.Configure()" method? Is there a better way to do this?
Thank you Joe for pointing out what needed to happen!
I made the false assumption that the serializer would happily create it's list from the json and assign that list to my IEnumerable - rather - you need to make sure to use List if you intend to deserialize a list of json objects into your Options (and other concrete dotnet types where applicable).
so instead of this
IEnumerable<EmailTemplate> EmailTemplates { get; set; }
I should have had this...
List<EmailTemplate> EmailTemplates { get; set; }

Configuring properties from config.json using services.Configure

Following on from a StackOverflow question regarding Using IConfiguration globally in mvc6. A comment to the accepted answer suggests using
services.Configure<SomeOptions>(Configuration);
Now this works fine with the following code;
Class
public class SomeOptions
{
public string MyOption { get; set; }
}
config.json
{
"MyOption": "OptionValue"
}
Startup.cs
public Startup(IHostingEnvironment env)
{
Configuration = new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SomeOptions>(Configuration);
}
However the config.json file doesn't have any really structure, and I would like it to look more like;
{
"SomeOptions": {
"MyOption": "OptionValue"
}
}
However this does not bind the values within the class. Is there anyway to allow this?
If you want to change the config.json structure you also need to change your class structure.
{
"SomeOptions": {
"MyOption": "OptionValue"
}
}
maps to something like
public class SomeOptions
{
public List<MyOption> MyOptions { get; set; }
}
public class MyOption
{
public string OptionValue { get; set; }
}
You can access specific value in config.json like:
Configuration.Get("SomeOptions:MyOption");
Which returns
"OptionValue"
So, your code will be
services.Configure<SomeOptions>(options =>
options.MyOption = Configuration.Get("SomeOptions:MyOption"));
services.Configure<SomeOptions>(Configuration.GetSubKey(nameof(SomeOptions)));
Should do it.

Categories

Resources