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?
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.
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.
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.
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:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With