Last week, we got an introduction to ASP.NET’s Configuration System. We primarily looked at some of the configuration providers that the system supports out-of-the-box. We explored the flexibility of this system that allows you to specify configuration values in multiple areas. We also saw how there was a default order of precedence for these providers where the system will select the value to use based on this priority order if the same key/value is specified in multiple places. If you haven’t had a chance to read that yet, you can check it out, here. This week, let’s dig a little deeper and explore some of the other aspects of the configuration system.
Configuration Builder
In last week’s episode, we simply new’d up a brand new ASP.NET application using the default template. Here it is again for reference:
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
Applications bootstrapped like this with the WebApplication.CreateBuilder
call gets the preconfigured defaults of the configuration system. It wires up the configuration providers to gather config information from the command-line, environment variables, secrets.json, appsettings.Environment.json files, and appsettings.json files.
But, as is the case with all things dotnet, you can forego the default and wire up the configuration system, just the way you like it. First, to remove the default providers, you can do something like this:
var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureAppConfiguration(configBuilder => { // Remove all the default sources configBuilder.Sources.Clear(); }); var mySecret = builder.Configuration.GetValue<string>("MySecret"); // will be empty as we cleared all the default sources Console.WriteLine($"MySecret: {mySecret}"); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
You can also use this same hook to bring in the specific providers that you want to bring in and in the order that you like them to operate:
builder.Host.ConfigureAppConfiguration(configBuilder => { // Remove all the default sources configBuilder.Sources.Clear(); // Add the ones you want... configBuilder.AddJsonFile("MyConfig.json", optional: true, reloadOnChange: true); configBuilder.AddXmlFile("myconfigs.xml", optional: true); });
Yes, you can load configs from XML files or even INI files if you need to. If you have a unique situation, you can even write your own provider. That’s a bit beyond the scope of this article but you can get more info on that, here.
Retrieving Complex and Hierarchical Configuration Data
Last week, the examples we looked at employed quite simple key/value pairs (e.g., MySecret=HelloWorld
). But the configuration system will certainly support much more complexity than that. Consider the following appsettings.json file:
{ "SuperAwesomeService": { "Host": "www.example.com", "User": "Joe Smith" }, "MyKey": "My appsettings.json Value", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "FavoriteSites": ["abc.def", "ghi.jkl", "mno.pqr"], "MeaningOfLife": 42 }
Here, we see a more hierarchical structure. We also see an array of FavoriteSites
, and an integer value for the key MeaningOfLife
. How do we retrieve these values?
You can use colons (:
) to traverse the hierarchical structure. Separate each segment using a colon. For example, you can refer to the Microsoft.Hosting.Lifetime
attribute, nested LogLevel
which itself is nested beneath the Logging
section like so:
var builder = WebApplication.CreateBuilder(args); var lifetime = builder.Configuration.GetValue<string>("Logging:LogLevel:Microsoft.Hosting.Lifetime"); Console.WriteLine($"Lifetime: {lifetime}"); // outputs "Information"
If you have an integer value, such as the MeaningOfLife
, you can coerce that value into an integer variable, like so:
var builder = WebApplication.CreateBuilder(args); var answer = builder.Configuration.GetValue<int>("MeaningOfLife"); Console.WriteLine($"Meaning of Life: {(answer / 2) * 2}");
However, do keep in mind that you can get a runtime error if the value that you have specified in configuration couldn’t be automatically cast by C# to the type you have specified in code.
How about getting the array of FavoriteSites
? Try something like this:
var builder = WebApplication.CreateBuilder(args); var sites = builder.Configuration.GetSection("FavoriteSites").GetChildren(); foreach (var item in sites) { Console.WriteLine($"Site: {item.Get<string>()}"); } // Outputs // Site: abc.def // Site: ghi.jkl // Site: mno.pqr
Strongly Typed Configuration Values
If you don’t want to deal with these complex, nested configuration values at a low-level, you can quite easily have dotnet hydrate a C# object for you. To do that, first create a class (or set of classes) that models your configuration data. If we take the example appsettings.json from above, you can represent it in C#, like so:
using System.Text.Json.Serialization; public class Logging { public LogLevel LogLevel { get; set; } = new LogLevel(); } public class LogLevel { public string Default { get; set; }= string.Empty; public string Microsoft { get; set; }= string. Empty; [JsonPropertyName("Microsoft.Hosting.Lifetime")] public string MicrosoftHostingLifetime { get; set; } = string.Empty; } public class MyAppSettings { public SuperAwesomeService SuperAwesomeService { get; set; } = new SuperAwesomeService(); public string MyKey { get; set; } = string.Empty; public Logging Logging { get; set; } = new Logging(); public List<string> FavoriteSites { get; set; } = new List<string>(); public int MeaningOfLife { get; set; } } public class SuperAwesomeService { public string Host { get; set; } = string.Empty; public string User { get; set; } = string. Empty; }
With this in place, you can request dotnet to hydrate any of the appsettings section as C# objects. For example, let’s say you are dealing with that SuperAwesomeService
section of the appsettings file. You can get that configuration as C#, by doing this:
var builder = WebApplication.CreateBuilder(args); var superAwesomeService = builder.Configuration.GetSection("SuperAwesomeService") .Get<SuperAwesomeService>(); Console.WriteLine($"Host: {superAwesomeService.Host}"); Console.WriteLine($"User: {superAwesomeService.User}"); // Outputs: // Host: www.example.com // User: Joe Smith
Closing Thoughts
This week, we got to dig a little deeper into ASP.NET’s configuration system. Are you hosting your app in Azure AppService? You can specify your configuration values in the Configuration section of AppService and dotnet will automatically pull those values for runtime use. If you need something a bit more rich, you can use the Azure AppConfiguration service. I wrote this post a while back, discussing how you can pull in configuration values from there for use in your ASP.NET applications. By the way, if you have true secrets that you need to bring in at runtime, you can use Azure KeyVault to store them and proxy them through Azure AppConfiguration, as well. There are quite a lot of possibilities to fit varying needs.