Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IConfigureOptions<T> is not creating scoped options

Typically Options are singleton. However i am building options from the database, and one of the Options property is password which keep changing every month. So i wanted to create Scoped instance of Options. I am using IConfigureOptions<T> like below to build Options from the database

public class MyOptions
{
   public string UserID {get;set;}
   public string Password {get;set;
}

public class ConfigureMyOptions : IConfigureOptions<MyOptions>
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    public ConfigureMyOptions(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public void Configure(MyOptions options)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var provider = scope.ServiceProvider;
            using (var dbContext = provider.GetRequiredService<MyDBContext>())
            {
                options.Configuration = dbContext.MyOptions
                                        .SingleOrDefault()
                                        .Select(x => new MyOptions()
                                        {
                                            UserID = x.UserID,
                                            Password = x.Password
                                        });
            }
        }
    }
}

Use it in controller

    public class HomeController : BaseController
    {
        private readonly MyOptions _options;
        public HomeController(IOptions<MyOptions> option)
        {
            _options = option.Value;
        }

        [HttpGet]
        [Route("home/getvalue")]
        public string GetValue()
        {
            // do something with _options here
            return "Success";
        }
    }

I want to create an instance of MyOptions for every new request so register it as Scoped in startup.cs

services.AddScoped<IConfigureOptions<MyOptions>, ConfigureMyOptions>();

However, when i put debugger inside ConfigureMyOptions's Configure method it only gets hit once for the first request. For next request onward the container returns the same instance (like singleton).

How do i set the scope here so MyOptions will get created for each request?

like image 772
LP13 Avatar asked Feb 21 '18 20:02

LP13


People also ask

How do I use scoped services in iconfigureoptions?

If you need to use Scoped services when implementing IConfigureOptions<>, you should inject an IServiceProvider into your class, and manually create a new scope to resolve the services. Don't inject the services directly into your IConfigureOptions<> instance as you will end up with a captive dependency.

When to use iconfigureoptions in strongly-typed setting configuration?

Whenever you need to use a service that's registered with the DI container as part of your strongly-typed setting configuration, you need to use IConfigureOptions<T> or IConfigureNamedOptions<T>. By implementing these interfaces in a class, you can configure an options object T using any required services from the DI container.

What is the difference between iconfigureoptions<> and ioptionssnapshot<>?

In particular, I highlighted how IOptions<> is registered as Singleton service, while IOptionsSnapshot<> is registered as a Scoped service. It's important to bear that difference in mind when using IConfigureOptions<> with Scoped services to configure your strongly-typed settings.

How do I setup ioptions<toptions> service?

To setup the IOptions<TOptions> service you call the AddOptions extension method during startup in your ConfigureServices method. You configure options using the Configure<TOptions> extension method. You can configure options using a delegate or by binding your options to configuration:


1 Answers

Use IOptionsSnapshot instead of IOptions in your controller and it will recreate options per request.


Why doesn't work with IOptions:

.AddOptions extension method of Configuration API registers the OptionsManager instance as a singlethon for IOptions<>

services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));

and OptionsManager class uses caching internally:

 public virtual TOptions Get(string name)
 {
     name = name ?? Options.DefaultName;

     // Store the options in our instance cache
     return _cache.GetOrAdd(name, () => _factory.Create(name));
 }

The following issue on github helped to find above: OptionsSnapshot should always be recreated per request

like image 124
Set Avatar answered Oct 28 '22 17:10

Set