Options Pattern Named Options in .Net Core with examples

As we learned in the earlier article, we need to do the option pattern and validations on the application side. Here is the link to both articles.

Introduction

If you wish to bind many choices to a type by a string name, but your configurations have the same features and you don't want to create distinct types for each configuration, you can use named options.

Assume you have the following JSON configuration.

{
  "VehicleDetails": {
    "Car": {
      "Name": "Honda City",
      "Company":  "Honda"
    },
    "Two-Wheeler": {
      "Name": "Jupiter",
      "Company":  "TVS"
    }
  }
}

In the above example, I have used the vehicle details, and both types have the same property, so we need to create the below class.

public class VehicleDetails
    {
        public const string Car = " VehicleDetails:Car";
        public const string TwoWheeler = " VehicleDetails:Two-Wheeler";


        public string Name { get;set; }

        public string Company { get; set; }
    } 

We need to register them in the Program.cs file.

builder.Services
    .AddOptions<VehicleDetails>(VehicleDetails.Car)
    .Bind(builder.Configuration.GetSection(VehicleDetails.Car));

builder.Services
    .AddOptions<VehicleDetails>(VehicleDetails.TwoWheeler)
    .Bind(builder.Configuration.GetSection(VehicleDetails.TwoWheeler));

Let’s try to fetch vehicle configurations in the API.

We can now use the options access interface to call the method Get(option-name) to obtain the appropriate option.

[Route("api/[controller]")]
[ApiController]
public class VehicleController : ControllerBase
{
    private readonly VehicleDetails _carVehicleDetails;
    private readonly VehicleDetails _twoWheelerVehicleDetails;

    public VehicleController(IOptionsSnapshot<VehicleDetails> vehicleDetails            )
    {
        _carVehicleDetails = vehicleDetails.Get(VehicleDetails.Car);
        _twoWheelerVehicleDetails = vehicleDetails.Get(VehicleDetails.TwoWheeler);
    }

    [HttpGet] 
    public IActionResult Get()
    {
        var carName = _carVehicleDetails.Name;
        var twoWheelerName = _twoWheelerVehicleDetails.Name;

        return Ok(new {carName, twoWheelerName });
    }
}

Once this type of code is done, we need to run the application and validate whether the vehicle details come as expected or not.

Parameter

Vehicle details

Named options vs the default options instance

Within the same program, you can utilize both named options and default options without any issues. If Configure() is used without a name, the default choices are used, such as.

Here is the updated JSON configuration.

"VehicleDetails": {
  "General": {
    "Name": "Baleno",
    "Company": "Suzuki"
  }
  "Car": {
    "Name": "Honda City",
    "Company": "Honda"
  },
  "Two-Wheeler": {
    "Name": "Jupiter",
    "Company": "TVS"
  }
}
// Configure the named options
builder.Services
    .AddOptions<VehicleDetails>(VehicleDetails.Car)
    .Bind(builder.Configuration.GetSection(VehicleDetails.Car));

builder.Services
    .AddOptions<VehicleDetails>(VehicleDetails.TwoWheeler)
    .Bind(builder.Configuration.GetSection(VehicleDetails.TwoWheeler));


// Configure the default "unnamed" options
builder.Services.Configure<VehicleDetails>(builder.Configuration.GetSection(VehicleDetails.General));

We can retrieve the default configuration options by the Value property of the IOtions<T> or IOptionsSnapshot<T>.

        private readonly VehicleDetails _carVehicleDetails;
        private readonly VehicleDetails _twoWheelerVehicleDetails;
        private readonly VehicleDetails _generalVehicleDetails;

        public VehicleController(IOptionsSnapshot<VehicleDetails> vehicleDetails            )
        {
            // fetch the individual settings
            _carVehicleDetails = vehicleDetails.Get(VehicleDetails.Car);
            _twoWheelerVehicleDetails = vehicleDetails.Get(VehicleDetails.TwoWheeler);

            // fetch the unnamed settings
            _generalVehicleDetails = vehicleDetails.Value;
        }

Value property

Injecting services into named options with IConfigureNamedOptions<T>

I explained how to use IConfigureOptions<T> to set the default settings. IConfigureNamedOptions<T> is an equivalent interface that you may also implement to configure named options.

/// <summary>
    /// Represents something that configures the <typeparamref name="TOptions"/> type.
    /// </summary>
    /// <typeparam name="TOptions">The options type being configured.</typeparam>
    public interface IConfigureNamedOptions<in TOptions> : IConfigureOptions<TOptions> where TOptions : class
    {
        /// <summary>
        /// Invoked to configure a <typeparamref name="TOptions"/> instance.
        /// </summary>
        /// <param name="name">The name of the options instance being configured.</param>
        /// <param name="options">The options instance to configure.</param>
        void Configure(string? name, TOptions options);
    }

Two ways to use this interface

There are two ways to use this interface

  • Configure(name, options): This is done directly through the interface
  • IConfigureOptions<T> (which it inherits) implements Configure(options).

It's critical to realize that when you implement the interface, Configure(name, options) will be invoked for each instance of the options objects T that your application instantiates. This covers every option that is named, including the default settings. It is your responsibility to determine which instance is being set up at runtime at the moment.

Implementing IConfigureNamedOptions<T> for a specific named options instance

public class CarDetailsOptionsService
{
    public string PremiumCarName()
    {
        return "GLE 230";
    }
}

Register the above class in the dependencies in the Program.cs file.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<CarDetailsOptionsService>();
}

Here is the implementation for IConfigureNamedOptions<T>.

    public class VehicleDetailsServiceOptions : IConfigureNamedOptions<VehicleDetails>
    {
        private readonly CarDetailsOptionsService _carDetailsOptionsService;

        public VehicleDetailsServiceOptions(CarDetailsOptionsService carDetailsOptionsService)
        {
            _carDetailsOptionsService = carDetailsOptionsService;
        }

        public void Configure(string name, VehicleDetails options)
        {
            if (name.Equals(VehicleDetails.Car, StringComparison.OrdinalIgnoreCase))
            {
                options.Name = _carDetailsOptionsService.PremiumCarName();
            }
        }

        public void Configure(VehicleDetails options)
        {
            Configure(Options.DefaultName, options);
        }
    }

In the above example, if the configuration is given for the car vehicle at that time, I override the car name value.

Once the implementation is done, we need to register in the Program.cs file to use the configuration in the application.

builder.Services.AddSingleton<IConfigureOptions<VehicleDetails>, VehicleDetailsServiceOptions>();

Let’s run the application and check whether the configuration is working or not, as expected.

Run the application

Get API vehical

We learned the new technique and evolved together.

Happy coding.