0

I've recently "discovered" the IoC realm, and decided to refactor a project I'm working on to use such a container.
To be specific - I'm using autofac in C#, in a .NET Core console application.

The program reads some configuration at startup, doesn't really matter how, just that it's all wrapped nicely with interfaces, especially an IConfigurationReader which reads it and returns a matching class.
There are many possible configurations, each has its own class (e.g. NetConfig for, well, network configuration), but eventually they're all part of a main AppConfig - As properties.
Reading the configuration should be the first thing the program does, then it should pass some of the configurations to other aspects of the system for further processing. Let's focus on NetConfig for now.

Up till now, aspects of the system that require NetConfig were accepting it as a concrete class, but there are a few problems with it:

  1. It's bad design when living in a DI-Aware system. Would be better to accept a Provider instead.
  2. I need to share the same configuration instance between all those modules that require it, similarly to a Singleton (Wouldn't implement one of course). A concrete provider, implementing the one suggested above, would be a nice & clean solution to this.

All right, say I've changed all NetConfig-dependent modules to accept an INetProvider instead.
A Singleton-like implementation of this provider should somehow provide the NetConfig, that's its purpose after all, but it can't do that without getting it from somewhere else first, e.g. as a ctor parameter.
Problem is, that this NetConfig instance is only created at runtime, reading it from some external source, so when registering with an IoC container, the NetProvider can't be registered as there's no NetConfig yet.

What should I do to resolve it? I've read many of the docs of autofac but it seems like none of their examples solve anything like this.

2 Answers 2

3

Some things to think about:

  • Creating the IoC container need not be the first thing you do. In fact, reading configuration should generally happen first, partially because other aspects of the IoC configuration might depend on that too. For example, maybe your configuration lets you switch between completely different data sources, that therefore require different repository implementations to be registered.

So read the configuration first, and then base the service registration on that.

  • There's nothing wrong with simply accepting a concrete instance of a configuration class in an IoC scenario. You can totally register your various configuration classes as singletons (using RegisterInstance in autofac), without there being any interface. Then have the classes that care about the configuration take the configuration as an injected parameter.

The specific NetConfig example you brought up might make you think that's a bad idea, but that might be more because the classes using it were too coupled to whatever they needed the NetConfig for. In that case, a INetProvider might be a good idea if you can factor that part out; or maybe what you really want is to inject a HttpClient (or rather, for testability, an abstracted version of it) into some of those classes, and some kind of mail sender or RPC abstraction into others.

  • .Net Core already has a facility for using configuration with IoC. The Microsoft.Extensions.Options NuGet package (and its siblings) provides facilities to
    • Read configuration from a variety of sources: command line, environment variables, JSON and XML files, and you can extend with your own parsers too.
    • Map the configuration to concrete classes.
    • Inject those concrete classes (behind an IOptions wrapper) into your classes via IoC.
    • Perform post-processing on the configuration.
    • Monitor configuration files for changes and update the application as necessary.

This facility is not tied to ASP.Net Core; it's usable independently. It is easier to use in conjunction with Microsoft's own IoC container rather than autofac due to some convenience extension methods, but it's not dependent on it.

4
  • Thanks for the clarification about the processing order, didn't think it's OK to read configuration before registering a container - Mostly because the configuration readers can also be configured through IoC. I'll read some more about .NET Core's built-in way to deal with configuration, it sounds promising! Commented May 22, 2019 at 12:09
  • 1
    @TimorGruber, "Mostly because the configuration readers can also be configured through IoC". I think you are disappearing down a rabbit hole here. Those configuration readers can be configured through abstractions (eg interfaces), factories, dependency injection etc. This doesn't mean they have to be configured through an IoC container though. Using pure DI to set up the configuration readers, then reading the configuration to setup the container and then having the container setup everything else is an approach you can take here.
    – David Arno
    Commented May 23, 2019 at 8:25
  • @DavidArno Yes, indeed, I just thought that when you're using a container, everything has to go be registered in it, no matter what. Guess I was wrong lol Commented May 23, 2019 at 8:30
  • You don't have to drink the entire jug of Kool-Aid. Remember, your project already worked before you discovered IoC. Commented May 27, 2019 at 19:47
1

For the sake of illustration, let's say that the classes you need to register depend on a type called IConfigurationValues. It could be an interface or a concrete type.

You can pass the configuration values to your module as a constructor argument like this:

public class MyModule : Autofac.Module
{
    private readonly IConfigurationValues _configuration;

    public MyModule(IConfigurationValues configuration)
    {
        _configuration = configuration;
    }

    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(cb => _configuration).As<IConfigurationValues>();
        // register your other stuff
    }
}

It's now impossible to use the module without supplying the configuration values. This makes the expected behavior explicit and easy to follow. The application knows that the dependencies it is going to register need these values, and it has to supply them.

The classes that depend on the configuration values don't know where they came from, so they're not coupled to whatever provides the configuration.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.