Options pattern in C#

In the previous post we saw how to read configuration for our .NET applications using IConfiguration. In this post, we're going to learn about the Options Pattern, a pattern for reading such configuration.

 

 

A great benefit of the options pattern is that it forces us to define classes representing parts of the application.

 

1 - What is the Options pattern in C#?

The benefit of introducing a pattern is it allows us to encapsulate and separate our configuration logic from the rest of the components.

 

For example, we saw that to read the configuration we use the .bind() method, but what if we have to validate that configuration? Checking if the server is an IP address (or a domain) and not just random text, or making sure the field isn't null, etc.

For this post, we'll use the same code as in the rest of the series, but we'll extend it to implement some functionality for sending an email.

 

For this, we create a class called EmailConfiguration, which will have a one to one relationship with the information we'll define in our appsettings.json.

public class EmailConfiguration
{
    public string SmtpServer { get; set; }
    public string From { get; set; }
}

 

And with it, a small service which will simulate sending an email:

public interface IEmailSender
{
    Task<Result<bool>> SendEmail(string to, string subject, string body);
}

public class EmailSender : IEmailSender
{
    private readonly EmailConfiguration _emailConfiguration;

    public EmailSender(EmailConfiguration emailConfiguration)
    {
        _emailConfiguration = emailConfiguration;
    }

    public async Task<Result<bool>> SendEmail(string to, string subject, string body)
    {
        Console.WriteLine("this simulates the an email being Sent");
        Console.WriteLine($"Email configuration Server: {_emailConfiguration.SmtpServer}, From: {_emailConfiguration.From}");
        Console.WriteLine($"Email data To: {to}, subject: {subject}, body: {body}");
        return true;
    }
    
}

 

As you can see, we will inject the EmailConfiguration type into our service. By the way, the EmailSender service itself can also be injected through its interface IEmailSender.

 

As the code stands now, we could use the bind method, like we saw in the previous post, to map our configuration to our object:

public void ConfigureServices(IServiceCollection services)
{
    /*más código*/
    
    services.AddSingleton(x =>
        {
            EmailConfiguration emailConfiguration = new EmailConfiguration();
            Configuration.Bind("emailService", emailConfiguration);
            return emailConfiguration;
        } )
        .AddScoped<IEmailSender, EmailSender>();
    services.AddScoped<IEmailSender, EmailSender>();
}

 

But up to now, there's nothing new compared to the previous post. So what's different?

 

 

2 - How to configure the options pattern in C#?

To configure the Options Pattern, we need to change the type we inject into our service from EmailConfiguration to IOptions<EmailConfiguration>.

 

To use IOptions<T>, you need the Microsoft.Extensions.Options package, and as in the previous example, when our service is invoked in the code, the dependency container will inject our instance of the configuration.

 

Since we're using generics, we need to access the value of T, which we'll do via .Value in the constructor:

public class EmailSender : IEmailSender
{
    private readonly EmailConfiguration _emailConfiguration;

    public EmailSender(IOptions<EmailConfiguration> options)
    {
        _emailConfiguration = options.Value;
    }
    /*más código*/
}

 

If you try to run the code now, it won't work. This is because we haven't injected IOptions<T>. To add our configuration to the dependency container, we need to use the .Configure<T>() method of the ServiceCollection.

 

The .Configure<T> method requires as a parameter an IConfigurationSection, as we saw in the previous post. To access it, we only need to access the configuration and invoke .GetSection("EmailService"):

services.Configure<EmailConfiguration>(Configuration.GetSection("EmailService"));
services.AddScoped<IEmailSender, EmailSender>();

 

Before continuing, keep in mind that the Options Pattern provides three interfaces: IOptions<T> (which we just saw), IOptionsSnapshot<T>, and finally IOptionsMonitor<T>.

 

 

3 - Why do I need IOptions in C#?

As we've just seen, using IOptions<EmailConfiguration> and previously injecting EmailConfiguration seem to work the same way, and they do, while we're developing, you won't notice any difference between them.

But keep a few things in mind:

  1. When you use the Options Pattern, your configurations are created in the dependency container as singletons (when using IOptions). This means you don't need to create them yourself, and they can be used in any service.
  2. Using it keeps your configuration strongly typed, which helps prevent errors.
  3. When reviewing code, or simply reading it after some time, it's much easier to understand if your configuration type is called IOptions<T>. That way, you know where it comes from.

Additionally, any of the three interfaces provide a method called .PostConfigure(), which lets you run code once the configuration is loaded.

Here, you can check or validate the configuration information.

services.Configure<EmailConfiguration>(Configuration.GetSection("EmailService"));
services.PostConfigure<EmailConfiguration>(emailConfiguration =>
{
    if ( string.IsNullOrWhiteSpace(emailConfiguration.SmtpServer))
    {
        throw new ApplicationException("el campo SmtpServer debe contner información");
    }
});

But the proper way to validate configuration will be discussed in the next post.

 

 

4 - IOptionsSnapshot in C#

The second of our interfaces is IOptionsSnapshot<T>, which initializes an instance at the start of each request (scoped) and keeps it immutable for the duration of that request.

 

The advantage of IOptionsSnapshot is that it lets you change the value of the configuration in the file, and it will be injected into your code without having to redeploy the application.

ioptions snapshot

The way to access the configuration is similar to before, you only need to change the type injected into your service.

  • Note: you do not need to change anything in the dependency container configuration, .configure() takes care of everything.
public class EmailSender : IEmailSender
{
    private readonly EmailConfiguration _emailConfiguration;

    public EmailSender(IOptionsSnapshot<EmailConfiguration> options)
    {
        _emailConfiguration = options.Value;
    }
    /*más código*/
}

 

 

5 - IOptionsMonitor in C#

The third and last interface is IOptionsMonitor, and it's a bit different from the previous two versions.

 

IOptionsMonitor<T> is injected into the service as a Singleton and works in a special way: instead of accessing .Value to reach T like before, now you'll use .CurrentValue, which returns the value at that moment.

This means you can inject IOptionsMonitor<T> and it will compute the correct value every time you use it.

 

This means that if you change the value during a request or in the middle of a process, you'll get the updated value:

And the code will be a bit different too, since in your constructor you'll store the IOptionsMonitor<T> instead of just T, fully benefiting from using IOptionsMonitor.

public class EmailSender : IEmailSender
{
    private readonly IOptionsMonitor<EmailConfiguration> _options;
    private EmailConfiguration EmailConfiguration => _options.CurrentValue;

    public EmailSender(IOptionsMonitor<EmailConfiguration> options)
    {
        _options = options;
    }

    public async Task<Result<bool>> SendEmail(string to, string subject, string body)
    {
        Console.WriteLine("this simulates the an email being Sent");
        Console.WriteLine($"Email configuration Server: {EmailConfiguration.SmtpServer}, From: {EmailConfiguration.From}");
        Console.WriteLine($"Email data To: {to}, subject: {subject}, body: {body}");
        return true;
    }
}

 

 

6 - When to use the Options pattern?

When programming in C#, you should always use the Options Pattern, as it is considered a best practice.

Choosing among the different interfaces of the IOptions pattern will depend on your specific use case.

If you're not going to change the configuration, you can use IOptions.

If you are going to change the configuration, you should use IOptionsSnapshot.

And if you need to change the configuration constantly or you know that the value might change in the middle of a process, it's better to use IOptionsMonitor.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café