fajny_nick
fajny_nick

Reputation: 11

Inject IOptionsMonitor with Autofac - using Options pattern

I have problems with injecting IOptionsMonitor with Autofac.

Everything works properly with IOptions, but I'm not able to make it work properly using IOptionsMonitor.

For IOptions registration looks as below:

var cfg = _configuration.GetSection("GlobalAppSettings").Get<GlobalAppSettings>();
builder.Register(c => Options.Create(cfg)).SingleInstance();

And now in constructor:

public class ConfigurationReader : IConfigurationReader
{
    public GlobalAppSettings GlobalAppSettings { get; }

    public ConfigurationReader(IOptions<GlobalAppSettings> _globalAppSettings)
    {
        GlobalAppSettings = _globalAppSettings.Value;
    }
 }

But how to make it works with IOptionsMonitor? Is there a way to create it like IOptions using Options.Create() ?

Upvotes: 1

Views: 1126

Answers (2)

Gabor
Gabor

Reputation: 3256

Here is a tested, working way.
I used the EgonsoftHU.Extensions.* nuget packages.

  • Create a new ASP.NET Core Web API project: TestOptionsMonitorApi
  • Replace the contents of these files.

TestOptionsMonitorApi.csproj (To be replaced.)

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
    <PackageReference Include="EgonsoftHU.Extensions.DependencyInjection.Autofac" Version="2.0.0" />
    <PackageReference Include="EgonsoftHU.Extensions.Logging.Serilog" Version="2.0.0" />
    <PackageReference Include="EgonsoftHU.Extensions.Logging.Serilog.Autofac" Version="6.0.1" />
    <PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

appsettings.json (To be replaced.)

{
  "AllowedHosts": "*",
  "WeatherOptions": {
    "City": "Budapest",
    "Country": "Hungary"
  },
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console" ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft.Extensions.Options": "Debug",
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fffffff zzz} [{Level:u3}] [{SourceContext}]::[{SourceMember}] {Message:lj}{NewLine}{Exception}"
        }
      }
    ]
  }
}

WeatherOptions.cs (To be added.)

namespace TestOptionsMonitorApi
{
    public class WeatherOptions
    {
        public string City { get; set; } = default!;

        public string Country { get; set; } = default!;
    }
}

DependencyModule.cs (To be added.)

using Autofac;
using Autofac.Extensions.DependencyInjection;

using EgonsoftHU.Extensions.Bcl;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace TestOptionsMonitorApi
{
    public class DependencyModule : Module
    {
        // This will be injected. See Program.cs for details.
        public IConfiguration Configuration { get; set; } = default!;

        protected override void Load(ContainerBuilder builder)
        {
            Configuration.ThrowIfNull();

            var services = new ServiceCollection();

            services.Configure<WeatherOptions>(
                Configuration.GetSection(nameof(WeatherOptions))
            );

            builder.Populate(services);
        }
    }
}

Program.cs (To be replaced.)

using Autofac;
using Autofac.Extensions.DependencyInjection;

using EgonsoftHU.Extensions.DependencyInjection;

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using Serilog;

const string OutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fffffff zzz} [{Level:u3}] [{SourceContext}]::[{SourceMember}] {Message:lj}{NewLine}{Exception}";

Log.Logger =
    new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.Console(outputTemplate: OutputTemplate)
        .CreateBootstrapLogger();

var builder = WebApplication.CreateBuilder(args);

builder
    .Host
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureContainer<ContainerBuilder>(
        (hostBuilderContext, containerBuilder) =>
        {
            // This call chain discovers and registers all Autofac.Module implementations.
            // The IConfiguration is injected into these modules through property injection.
            containerBuilder
                .UseDefaultAssemblyRegistry(nameof(TestOptionsMonitorApi), nameof(EgonsoftHU))
                .TreatModulesAsServices()
                .RegisterModuleDependencyInstance(hostBuilderContext.Configuration)
                .RegisterModule<DependencyModule>();
        }
    )
    .UseSerilog(
        (hostBuilderContext, services, loggerConfiguration) =>
        {
            loggerConfiguration
                .ReadFrom.Configuration(hostBuilderContext.Configuration)
                .ReadFrom.Services(services);
        }
    );

// Add services to the container.

builder.Services.AddControllers().AddControllersAsServices();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

WeatherForecastController.cs (To be replaced.)

using System;
using System.Collections.Generic;
using System.Linq;

using EgonsoftHU.Extensions.Logging;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

using Serilog;

namespace TestOptionsMonitorApi.Controllers
{
    [ApiController]
    [Route("WeatherForecast")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger logger;
        private readonly IOptionsMonitor<WeatherOptions> options;

        public WeatherForecastController(ILogger logger, IOptionsMonitor<WeatherOptions> options)
        {
            this.logger = logger;
            this.options = options;

            options.OnChange(
                weatherOptions =>
                Log
                    .Logger
                    .ForContext("SourceContext", "IOptionsMonitor<WeatherOptions>")
                    .Here(nameof(IOptionsMonitor<WeatherOptions>.OnChange))
                    .Debug("WeatherOptions changed: {@WeatherOptions}", weatherOptions)
            );
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            logger
                .Here()
                .Debug("WeatherOptions: {@WeatherOptions}", options.CurrentValue);

            return
                Enumerable
                    .Range(1, 5)
                    .Select(
                        index =>
                        new WeatherForecast
                        {
                            Date = DateTime.Now.AddDays(index),
                            TemperatureC = Random.Shared.Next(-20, 55),
                            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                        }
                    )
                    .ToArray();
        }
    }
}

Steps:

  • Start application.
  • GET /WeatherForecast
  • Edit appsettings.json
  • GET /WeatherForecast

Result:

2022-11-06 18:09:46.7770973 +01:00 [DBG] [TestOptionsMonitorApi.Controllers.WeatherForecastController]::[Get] WeatherOptions: {"City": "Budapest", "Country": "Hungary", "$type": "WeatherOptions"}
2022-11-06 18:10:18.5447047 +01:00 [DBG] [IOptionsMonitor<WeatherOptions>]::[OnChange] WeatherOptions changed: {"City": "Győr", "Country": "Hungary", "$type": "WeatherOptions"}
2022-11-06 18:10:23.9365563 +01:00 [DBG] [TestOptionsMonitorApi.Controllers.WeatherForecastController]::[Get] WeatherOptions: {"City": "Győr", "Country": "Hungary", "$type": "WeatherOptions"}

Upvotes: 0

Patrick Quijano
Patrick Quijano

Reputation: 351

You need to add the following Nuget packages in to your project:

  • Autofac.Extensions.DependencyInjection
  • Microsoft.Extensions.Options

Create a new ServiceCollection with the AddOptions() method. Then add the ServiceCollection to your Autofac builder via Populate() method.

var serviceCollection = new Microsoft.Extensions.DependencyInjection.ServiceCollection().AddOptions();
var builder = new Autofac.ContainerBuilder();
builder.Populate(serviceCollection);
var container = builder.Build();

After, you can inject IOptions, IOptionsSnapshot and IOptionsMonitor.

Upvotes: 1

Related Questions