C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
80: Configuration and Options Pattern
I see this happen all the time when developers first start building production-ready APIs. They know that settings—like API keys, connection strings, or timeout values—belong in appsettings.json, but they treat the configuration system like a giant dictionary. It works for five minutes, and then it falls apart the moment the app scales or the config file gets a typo.
The "String-ly Typed" Configuration Trap
// A typical "beginner" implementation of a notification service
public class EmailService
{
private readonly IConfiguration _config;
public EmailService(IConfiguration config)
{
_config = config;
}
public void SendEmail(string recipient, string message)
{
// This is where the danger lives
var smtpServer = _config["SmtpSettings:Server"];
var port = int.Parse(_config["SmtpSettings:Port"]);
Console.WriteLine($"Connecting to {smtpServer} on port {port}...");
// Logic to send email...
}
}
At first glance, this looks fine. But look closer. I've used "magic strings" to access my settings. If I accidentally type "SmtpSetting:Server" (missing the 's'), smtpServer becomes null, and the app might crash later in a way that's hard to debug. Even worse, int.Parse will throw a FormatException if someone puts "Five Hundred" instead of "500" in the JSON file. Your app crashes at runtime, not at startup, which is the worst time for a failure.
Plus, if you want to unit test EmailService, you now have to mock the entire IConfiguration tree, which is a tedious pain in the neck.
Moving to Strongly Typed Options
The professional way to handle this is the Options Pattern. Instead of asking the configuration system for a specific string, we define a class that represents our settings. This turns our configuration into a first-class object.
First, we create a simple POCO (Plain Old CLR Object) that matches the structure of our JSON:
public class SmtpOptions
{
public const string SectionName = "SmtpSettings";
public string Server { get; set; } = string.Empty;
public int Port { get; set; }
}
Now, we tell the Dependency Injection (DI) container how to bind the JSON section to this class in Program.cs:
// In Program.cs
builder.Services.Configure<SmtpOptions>(
builder.Configuration.GetSection(SmtpOptions.SectionName));
Finally, we refactor the service to use IOptions<T>. Notice how we no longer care about the JSON structure inside the service; we just care about the SmtpOptions object.
public class EmailService
{
private readonly SmtpOptions _options;
public EmailService(IOptions<SmtpOptions> options)
{
// .Value is where the bound settings live
_options = options.Value;
}
public void SendEmail(string recipient, string message)
{
// Now it's type-safe. No casting, no magic strings here.
Console.WriteLine($"Connecting to {_options.Server} on port {_options.Port}...");
}
}
Handling Runtime Configuration Changes
One thing I should mention: IOptions<T> is a singleton. Once the app starts, those values are locked in. If you change your appsettings.json while the app is running, IOptions won't see the changes until you restart the process.
If you're building something that needs to update settings on the fly (like a feature flag or a logging level), you have two other tools in your belt:
IOptionsSnapshot<T>: Useful for scoped services. It re-reads the config on every request, ensuring you have the latest values for that specific execution.IOptionsMonitor<T>: A singleton service that provides aCurrentValueproperty and anOnChangeevent. This is the go-to for long-running background services.
In most cases, IOptions is enough, but knowing the difference prevents those "Why isn't my config updating?" debugging sessions that waste an entire afternoon.
📋 Practical Task
Implementing a Validated API Configuration for a Payment Gateway
You are integrating a third-party payment gateway. The gateway requires an API Key and a Timeout value. You need to ensure the application fails immediately on startup if these values are missing or invalid, rather than failing during a transaction.
Your Task:
- Create a class called
PaymentGatewayOptionswith two properties:ApiKey(string) andTimeoutSeconds(int). - In your
Program.cs, use the.Configure<T>method to bind this class to a section inappsettings.jsonnamed"PaymentGateway". - Create a service called
PaymentProcessorthat injectsIOptions<PaymentGatewayOptions>. - Inside the
PaymentProcessor, implement a methodProcessPayment(decimal amount)that prints a message saying: "Processing payment of [amount] using API Key [ApiKey] with a timeout of [TimeoutSeconds]s." - Bonus Challenge: Research
PostConfigureorValidateDataAnnotationsto ensure that theApiKeycannot be null or empty upon application startup.
There are no comments for now.