Software, Data & Cloud Expert
Clean code and reliable deployments using IOptions
11/06/2025
The worst thing that can happen is to deploy bad code and not know it or have to wait until it's live to fix it. The Options pattern will take you one step closer to being able to blind deploy your applications and help you to encapsulate your code.
If you're a cloud engineer or developer trying to up your game, you'll want to see this.
You can have a look at the entire working sample at Sample on Github.
Bad Example
Let's start with a common pattern that I see and want to pull my hair out, injecting IConfiguration
and using strings to extract your value:
public class BadExampleController(IConfiguration configuration) : Controller
{
[HttpGet]
public IActionResult Get()
{
return Ok(configuration["Sample:Name"]);
}
}
It looks okay until your application gets larger and you want to refactor something. Good luck checking every line of configuration in a 1000 line appsettings to make sure that you aren't removing something important or trying to debug why your application keeps failing when its just a spelling mistake or you're referring to an appsetting that no longer exists.
Most importantly, on deployment, this will successfully build and deploy and you will only know there is a problem when somebody tries to hit this endpoint and gets a HTTP 500: Internal Server Error
So what can we do about it?
- If we're only using the Sample section of
IConfiguration
then we probably want an object to represent those appsettings and only those. - We want something that can be checked at compile time.
- This needs to be validated on startup.
Instead of rolling our own solution, Microsoft have handily created a simple but powerful extension called Options
that does everything we need and more.
Setup a Class
Start by creating a class that represents the appsettings that you are interested in. If you are adding Google Maps to your application then this class might be called GoogleMapsSettings
and contain your API key, zoom level and initial latitude and longitude. In this example, I've kept it simple and just used a string: Name and an integer: Age.
public class SampleSettings
{
public const string SettingsName = "Sample";
public required string Name { get; init; }
public int Age { get; init; }
}
Looks pretty simple so far but worth noting: there's a static (const
) property called SettingsName
containing the name of the section in appsettings that contains Name and Age. The snippet below might help to understand the heirarchy:
{
"Sample": {
"Name": "MyName",
"Age": 30
}
}
It's also worth using init instead of set since we never want the settings to be mutable.
Register Options
Here's where the magic happens. We will register this as an Option
on startup and perform validation at the same time:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddSampleOptions(this IServiceCollection services, IConfiguration configuration)
{
services
.AddOptions<SampleSettings>()
.Bind(configuration.GetRequiredSection(SampleSettings.SettingsName))
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddTransient <ISampleService, SampleService>();
return services;
}
}
So line by line:
Create an extension to the IServiceCollection
which allows you to perform your dependency injection neatly in its own file to keep your Program.cs
clean.
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddSampleOptions(this IServiceCollection services, IConfiguration configuration)
{
Register SampleSettings
as options and bind the options to a section of our appsettings using the SampleSettings.SettingsName
that we created earlier (which is why it is const, so it can be statically)
services.AddOptions<SampleSettings>()
.Bind(configuration.GetRequiredSection(SampleSettings.SettingsName))
Perform validation using data annotations. I won't go into the detail of this now but it allows you to add attributes to your properties like Regex or minimum and maximum sizes and have your application fail if those requirements are not met. Finally, perform the validation on startup, so your app intentionally fails early (during deployment and not after).
.ValidateDataAnnotations()
.ValidateOnStart();
The last two lines aren't relevant just yet but will make sense soon.
Create a Service
Next up, we create a service to use the Options
. In our case it's probably a bit overkill but if you want to do something fancy with your settings before you use them all over the place this is probably the best place to do it.
public class SampleService : ISampleService
{
private readonly SampleSettings _sampleSettings;
public SampleService(IOptions<SampleSettings> sampleOptions)
{
_sampleSettings = sampleOptions.Value;
}
There are other ways to do this but I prefer using a constructor for legibility. So you inject the IOptions
and when you want to use it you get the .Value
which will return the SampleSettings
itself, populated with the appsettings you bound it to. I like to store the SampleSettings
itself rather than the IOptions
for use within the class hence the private readonly SampleSettings _sampleSettings;
The other things I do here are make sure that there is an interface ISampleSettings
and create some example methods to show SampleSettings
being used.
By the way, this is where the last two lines of our ServiceCollectionsExtension
come in handy, to register our SampleService
too before returning the IServiceCollection
so calls can be chained in Program.cs
.
services.AddTransient();
return services;
Quick note on folder structure
I prefer the following folder structure because it keeps all of your related files together in the same namespace because they are used together. It also allows you to have multiple ServiceCollectionExtensions
scattered around your application because they have different namespaces and in my opinion it's just the perfect name for the file.
├── Services
│ └── Sample
│ ├── SampleService.cs
│ ├── SampleSettings.cs
│ └── ServiceCollectionExtensions.cs
Using Options in Razor Pages
Make sure you have your @using
, your @inject
and you use .Value
and its pretty selef-explanatory after that.
@using Microsoft.Extensions.Options
@inject IOptions<SampleSettings> SampleOptions
@{
ViewData["Title"] = "Home page";
var sampleSettings = SampleOptions.Value;
}
@sampleSettings.Age
I personally wouldn't use the SampleSettings
directly, I would use the SampleService
but for the sake of example I've injected the IOptions
instead of creating a cshtml.cs file where I would inject the SampleService
to build my PageModel
.
If that all went over your head, check out this page Microsoft Razor Docs to learn more about PageModel.
Using Options in Controllers
Similar usage, but here we use the ISampleService
so no need to .Value
.
public class SampleController(ISampleService sampleService) : Controller
{
[HttpGet]
public IActionResult Get()
{
return Ok(sampleService.GetName());
}
}
Trying it out
You can check out the full example here Sample on Github
If you try to run it, visit http://localhost:5003/
for the Razor Page example and http://localhost:5003/sample
for the Controller example.
Final thoughts
Use Options
, they make your code better in too many ways and its so simple to use that even if you don't understand the need yet, you will once your application gets big enough.
Also, check out Microsoft's own docs on the topic which go into more detail on other areas of Options Microsft Options Docs