Search code examples
asp.net-coredependency-injectionapp-startup

URLRewrite middleware that depends on Config dependency injection/scoped service. .Net Core


Struggling a little with Dependency Injection/Scoped Services with a rewrite rule class.

I have a redirects class which implements IRule

class ActivateRedirects : IRule
{        
    public void ApplyRule(RewriteContext context)
    {
        // Do stuff here which relies on CoreSettings (info below)
    }
}

I also have a CoreSettings class which contains various settings, some of which are required for ApplyRule to work, it is initialised in startup.cs as follows.

var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "site.json"), optional: false, reloadOnChange: true);
IConfigurationRoot root = configurationBuilder.Build();             
CoreSettings s = new CoreSettings();  
services.Configure<CoreSettings>(root.GetSection("Website"));

So you can see above, CoreSettings is created as a Service, which in most cases I can consume with DI:

public class SomeOtherClass{
    private CoreSettings Settings;
    public SomeOtherClass(Microsoft.Extensions.Options.IOptionsSnapshot<CoreSettings> S)
    {
        Settings = S.Value;
    } 
    // Do stuff with Settings ....
}

I have read up on several pages on why I can't simply add DI to the ActivateRedirects Class, or explicitly pass the value in using app.ApplicationServices.GetRequiredService but everything I have read is telling what I can't do, I can't find anything to tell me what I can!!

Before I am told to rewrite the code to not require CoreSettings for Rewriting, I can't do that because one of the rewrite rules depends on a condition which is set by a remote server via a REST API and what is needed in the CoreSettings class is the API credentials used to create the REST Client.


Solution

  • It is possible to do what you want. I'll assume your class is defined as follows:

    public class ActivateRedirects : IRule
    {
        private readonly CoreSettings _coreSettings;
    
        public ActivateRedirects(CoreSettings coreSettings)
        {
            _coreSettings = coreSettings;
        }
    
        public void ApplyRule(RewriteContext context)
        {
            
        }
    }
    

    Read the configuration file, as before:

    services.Configure<CoreSettings>(root.GetSection("Website"));
    

    Next, setup your RewriteOptions:

    var coreSettings = app.ApplicationServices.GetRequiredService<IOptions<CoreSettings>>();
    var activateRedirects = new ActivateRedirects(coreSettings.Value);
    
    var rewriteOptions = new RewriteOptions();
    rewriteOptions.Rules.Add(activateRedirects);
    
    app.UseRewriter(rewriteOptions);
    

    If you put a breakpoint inside ActivateRedirects, and send a request, you'll see the CoreSettings field has been populated.

    A screenshot showing an injected setting in the ActivateRedirects class.

    Update

    I think this scenario is what IOptionsMonitor<T> might be designed for. It's registered as a singleton, but is notified of options changes. Change ActivateRedirects to:

    public class ActivateRedirects : IRule
    {
        private readonly IOptionsMonitor<CoreSettings> _coreSettings;
    
        public ActivateRedirects(IOptionsMonitor<CoreSettings> coreSettings)
        {
            _coreSettings = coreSettings;
        }
    
        public void ApplyRule(RewriteContext context)
        {
            
        }
    }
    

    and change how the instance is constructed to:

    var coreSettings = app.ApplicationServices.GetRequiredService<IOptionsMonitor<CoreSettings>>();
    var activateRedirects = new ActivateRedirects(coreSettings);
    

    For me, editing the configuration does now show updated values in CoreSettings. One caveat is I don't know how that notification process works. If this ends up reading the configuration directly on each request, then it will scale really poorly, so I'd advise giving it a good test first.