1

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.

Jamie Hartnoll
  • 7,231
  • 13
  • 58
  • 97

2 Answers2

2

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.

John H
  • 14,422
  • 4
  • 41
  • 74
  • Thank you. That does seem to work, what I was doing wrong before was trying to use `IOptionsSnapshot` instead of `IOptions`. However, there was a reason for that (I should have made clear in my question), which was that I would like it to pick up config changes, but I am guessing from all I have tried and that yours is the only working code method I have, that I can't really do that?! – Jamie Hartnoll Sep 27 '20 at 09:21
  • @JamieHartnoll The problem is that `IOptions` has a lifetime of singleton, which is why it doesn't pick up changes made to your configuration. But `IOptionsSnapshot` is scoped, meaning its duration is for the request, but that's managed by the container. As the `ActivateRedirects` instance added as a rule will exist for the entirety of the application, it would keep its reference to `IOptionsSnapshot` alive for that same duration, and so never pick up configuration changes. – John H Sep 27 '20 at 09:40
  • Yes that's how I have now begun to understand it! I think as I am understanding, it is because the `ActivateRedirects` instance has a lifetime of the entirety of the application, it wouldn't make sense for it to rely on something with the lifetime of a request, it could create a situation where the ever-present `ActivateRedirects` has no `CoreSettings` object to draw its dependant data? – Jamie Hartnoll Sep 27 '20 at 09:46
  • @JamieHartnoll Almost right. With scoped instances, they are created per request by the container. But with `ActivateRedirects`, its lifetime isn't managed by the container, so it would keep a reference to the initial instance of `IOptionsSnapshot` that you pass to it, thus never providing updated configuration. – John H Sep 27 '20 at 09:48
  • 1
    @JamieHartnoll But I have just discovered `IOptionsMonitor` which may solve your problem. Give me a couple of minutes and I'll update my answer. – John H Sep 27 '20 at 09:49
  • @JamieHartnoll Give that a try. – John H Sep 27 '20 at 09:53
  • Thanks John. The updated code "works" but I have just realised, that determining whether the settings are actually updating or not within my app is a little more tricky than I realised, so I will have to check it out a little later on. I will report back. I did see `IOptionsMonitor` info when Googling this before, but couldn't quite work out what it does, or how to implement it. I have a feeling it needs some kind of event hookup, but we shall see.. – Jamie Hartnoll Sep 27 '20 at 10:02
  • @JamieHartnoll No problem. Let me know what happens, either here or on a new question. I'd like to know the answer. – John H Sep 27 '20 at 10:03
2

Its possible to do it right inside of the ApplyRule function itself. Example

namespace MyNamespace {

    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.AspNetCore.Rewrite;

    public class MyCustomRule : IRule {
        public void ApplyRule(RewriteContext context) {
            var injectedService = context.HttpContext.RequestServices.GetRequiredService<IMyInjectedService>();
        }
    }
}
Mike
  • 1,525
  • 1
  • 14
  • 11