// Exercise 2: layered configuration like Microsoft.Extensions.Configuration (medium) // // ASP.NET Core merges configuration sources in order; later sources win. // Keys are hierarchical and case-insensitive, joined with ":" — e.g. // "ConnectionStrings:Default". Environment variables use "__" instead of ":" // (ConnectionStrings__Default) because ":" isn't valid in env var names on // every OS. // // Implement the LayeredConfig class: // - AddSource(IDictionary values): adds a source on top // - AddEnvironment(IDictionary env, string prefix): // only variables starting with `prefix` (e.g. "SHOP_") are included; // the prefix is stripped and "__" is translated to ":" // - this[string key]: returns the value from the LAST source that defines // the key (case-insensitive), or null // - GetSection(string prefix): returns a Dictionary of child key -> value for // every key starting with "prefix:", with the prefix removed, using the // same precedence rules // // Run with: dotnet run (inside a console project). using System; using System.Collections.Generic; class LayeredConfig { // TODO: fields #pragma warning disable IDE0060 public void AddSource(IDictionary values) { // TODO } public void AddEnvironment(IDictionary env, string prefix) { // TODO } public string? this[string key] => null; // TODO public Dictionary GetSection(string prefix) { // TODO return new Dictionary(); } #pragma warning restore IDE0060 } class Program { static void Main() { int pass = 0, total = 0; var appsettings = new Dictionary { ["Logging:Level"] = "Information", ["ConnectionStrings:Default"] = "Server=localhost", ["ConnectionStrings:Reports"] = "Server=reports", }; var appsettingsProd = new Dictionary { ["Logging:Level"] = "Warning", }; var env = new Dictionary { ["SHOP_ConnectionStrings__Default"] = "Server=prod-db", ["PATH"] = "/usr/bin", }; var config = new LayeredConfig(); config.AddSource(appsettings); config.AddSource(appsettingsProd); config.AddEnvironment(env, "SHOP_"); total++; if (config["Logging:Level"] == "Warning") { Console.WriteLine("PASS: later file overrides earlier"); pass++; } else Console.WriteLine("FAIL: Logging:Level = " + config["Logging:Level"]); total++; if (config["connectionstrings:default"] == "Server=prod-db") { Console.WriteLine("PASS: env var overrides, case-insensitive"); pass++; } else Console.WriteLine("FAIL: ConnectionStrings:Default = " + config["connectionstrings:default"]); total++; if (config["PATH"] == null) { Console.WriteLine("PASS: unprefixed env vars ignored"); pass++; } else Console.WriteLine("FAIL: PATH leaked into config"); var section = config.GetSection("ConnectionStrings"); total++; if (section.Count == 2 && section["Default"] == "Server=prod-db" && section["Reports"] == "Server=reports") { Console.WriteLine("PASS: GetSection"); pass++; } else Console.WriteLine("FAIL: GetSection " + string.Join(", ", section)); Console.WriteLine($"--- {pass}/{total} checks passed ---"); } }