4

I found two related posts that are close to my question but still not answer what I'm looking for.

Which design patterns can be applied to the configuration settings problem?

Should my program's "services" be responsible to get their own configuration?

I have an application using AutoFac as DI container and ran into the following problem. I have classes I register with AutoFac at startup, but some constructor parameters are only known during runtime. I made up a sample to show my intension. The comment shows the location in code where I want to retrieve an instance of a class and inject some configuration data at this point in time.

The idea behind it is to separate the implementation of a generic settings provider from the configuration of certain classes. The configuration should be retrieved from the settings provider (ISettingsProvider), be converted to a class related settings class (FooSettings) and then be injected at creation time into that class (Foo).

Any suggestions on how to do this?

My sample:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Autofac;

namespace FooNamespace
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<SettingsProvider>().As<ISettingsProvider>().SingleInstance();
            builder.RegisterType<AnotherService>().As<IAnotherService>().SingleInstance();
            builder.RegisterType<Foo>();
            builder.RegisterType<ClientClass>();

            var container = builder.Build();

            var myClient = container.Resolve<ClientClass>();

            myClient.DoSomething();

        }
    }

    public class Foo
    {
        private FooSettings _settings;
        private IAnotherService _anotherService;

        public Foo(IAnotherService anotherService, FooSettings settings)
        {
            _anotherService = anotherService;
            _settings = settings;
        }
    }

    public class FooSettings
    {
        public string SettingA { get; private set; }
        public string SettingB { get; private set; }
        public FooSettings(string settingA, string settingB)
        {
            SettingA = settingA;
            SettingB = settingB;
        }
    }

    public class ClientClass
    {
        private readonly ISettingsProvider _settingsProvider;

        public ClientClass(ISettingsProvider settingsProvider)
        {
            _settingsProvider = settingsProvider;
        }

        public void DoSomething()
        {
            var providerSettings = _settingsProvider.GetSettings("fooSettings");

            var fooSettings = new FooSettings(providerSettings[0], providerSettings[1]);

            // what is the best way to get an instance of foo with the fooSettings set at runtime
            // when Foo and IAnotherService have been registered with AutoFac
        }

    }

    public interface ISettingsProvider
    {
        string[] GetSettings(string settingsGroup);
    }

    class SettingsProvider : ISettingsProvider
    {

        public string[] GetSettings(string settingsGroup)
        {
            return new string[2] {"valueA", "valueB"};
        }
    }

    public interface IAnotherService
    {

    }

    class AnotherService : IAnotherService
    {
    }
}
Community
  • 1
  • 1

2 Answers2

3

I use Abstract Factory pattern always when I need a runtime value to be injected as dependency.

Define a factory interface

public interface IFooFactory
{
    Foo CreateFoo();
}

Implement the interface with the help of Delegate Factories support provided by autofac. If you take a dependency on any delegate, autofac will create the implementation for you with all the dependencies except the dependencies which the delegate takes in.

public class FooFactory : IFooFactory
{
    private readonly Func<FooSettings, Foo> creationFunc;
    private readonly ISettingsProvider settingsProvider;
    public FooFactory(Func<FooSettings, Foo> creationFunc, ISettingsProvider settingsProvider)
    {
        if (creationFunc == null) throw new ArgumentNullException("creationFunc");
        if (settingsProvider == null) throw new ArgumentNullException("settingsProvider");

        this.creationFunc = creationFunc;
        this.settingsProvider = settingsProvider;
    }

    public Foo CreateFoo()
    { 
        var providerSettings = settingsProvider.GetSettings("fooSettings");
        var fooSettings = new FooSettings(providerSettings[0], providerSettings[1]);
        return creationFunc(fooSettings);
    }
}

Take a dependency on IFooFactory instead of ISettingsProvider

public class ClientClass
{
    private readonly IFooFactory fooFactory;
    public ClientClass(IFooFactory fooFactory)
    {
        this.fooFactory = fooFactory;
    }

    public void DoSomething()
    {
        var foo = fooFactory.CreateFoo();
       //Do whatever with foo
       //Your foo is now injected with another service and settings too:)
    }
}

And of course register the factory

builder.RegisterType<FooFactory>().As<IFooFactory>(); 
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
0

You can have a FooSettingsProvider that will depends on SettingsProvider and register a lambda as a FooSettings.

For example :

public interface ISettingsProvider<TSettings> where TSettings : ISettings
{
    TSettings Settings { get; }
}
public class FooSettingsProvider : ISettingsProvider<FooSettings>
{
    public FooSettingsProvider(SettingsProvider settingsProvider)
    {
        this._settingsProvider = settingsProvider;
        this._settings = new Lazy<FooSettings>(this.InitializeSettings);
    }


    private readonly SettingsProvider _settingsProvider;
    private readonly Lazy<FooSettings> _settings;

    public FooSettings Settings
    {
        get
        {
            return this._settings.Value;
        }
    }

    private FooSettings InitializeSettings()
    {
        var providerSettings = this._settingsProvider.GetSettings("fooSettings");
        var fooSettings = new FooSettings(providerSettings[0], providerSettings[1]);

        return fooSettings;
    }
}

To register your FooSettings you can use this registration :

builder.RegisterType<FooSettingsProvider>()
        .As<ISettingsProvider<FooSettings>>();
builder.Register(c => c.Resolve<ISettingsProvider<FooSettings>>().Settings)
        .As<FooSettings>();
Cyril Durand
  • 15,834
  • 5
  • 54
  • 62