How can I register a type with dynamic string parameters in it's constructor at Composition root?

て烟熏妆下的殇ゞ 提交于 2019-12-08 07:10:43

问题


I have these constructors:

    public Configurator(string workingDirectory, string siteExclusions) : this(directory, exclusions, new ServerManager(), new DirectoryCheck())
    {
    }

    public Configurator(string directory, string exclusions, IServerManager manager, IDirectoryCheck directoryCheck)
    {
        this.manager = manager;
        this.directoryCheck = directoryCheck;
        if (exclusions != null)
        {
            // do stuff
        }

        this.directory = directory;
    }

Where directory and exclusions are created by the creating code, within a winforms application. E.g.

        using (var configurator = new Configurator(this.CalculatedDirectory(), this.exclusions))
        {
            var output = configurator.ConfigureIIS();
            this.txtOutput.Text = output;
        }

I am trying to remove the first constructor and retrofit the Unity DI Container. I have started to make this composition root, to be called from Program:

public static class ApplicationRoot
{
    private static readonly Lazy<IUnityContainer> Container = new Lazy<IUnityContainer>(() =>
    {
        var container = new UnityContainer();
        RegisterTypes(container);
        return container;
    });

    /// <summary>
    /// Gets the configured Unity container.
    /// </summary>
    public static IUnityContainer FetchConfiguredContainer()
    {
        return Container.Value;
    }

    private static void RegisterTypes(IUnityContainer unityContainer)
    {
        unityContainer.RegisterType<IServerManager, ServerManager>();
        unityContainer.RegisterType<IDirectoryCheck, DirectoryCheck>();
    }
}

But how do I deal with the strings directory and exclusions?

I know I can use new InjectionConstructor("foo") in my Composition Root, but I don't know the values at that point.


回答1:


I asked a similiar question a while ago - Resolve with parameter from other constructor with unity

As Steven so elegantly put it:

In other words, your [variable name] parameter is runtime data. Injecting runtime data into components during the components' initialization is an anti-pattern.

The way I solved it was to create a container that hold the value. In my case I was able to bind it to the WCF-context. But it could be hold for a specific thread, request or session instead.

Here's an example:

public interface IOperationContext<T>
{
    IDictionary<string, T> Items { get; }
}

public class ThreadOperationContext<T> : IOperationContext<T>
{
    [ThreadStatic]
    private static Dictionary<string, T> _items;

    public IDictionary<string, T> Items
    {
        get
        {
            if (_items == null)
            {
                _items = new Dictionary<string, T>();
            }
            return _items;
        }
    } 
}

public class WcfOperationContext<T> : IExtension<OperationContext>
{
    private readonly IDictionary<string, T> _items;

    private WcfOperationContext()
    {
        _items = new Dictionary<string, T>();
    }

    public IDictionary<string, T> Items
    {
        get { return _items; }
    }

    public static WcfOperationContext<T> Current
    {
        get
        {
            WcfOperationContext<T> context = OperationContext.Current.Extensions.Find<WcfOperationContext<T>>();
            if (context == null)
            {
                context = new WcfOperationContext<T>();
                OperationContext.Current.Extensions.Add(context);
            }
            return context;
        }
    }

    public void Attach(OperationContext owner) { }
    public void Detach(OperationContext owner) { }
}

/// <summary>
/// Provides a collection that will be unique for every operation context.
/// </summary>
/// <typeparam name="T"></typeparam>
public class WcfOperationContextWrapper<T> : IOperationContext<T>
{
    public IDictionary<string, T> Items
    {
        get { return WcfOperationContext<T>.Current.Items; }
    }
}

Then you can inject an IOperationContext<string> when the variable workingDirectory is supposed to be set. Then you can inject it in the implementation where you need it and have the variable set for you.

Example:

public class MySetter
{
    public MySetter(IOperationContext<string> stringContainer)
    {
        stringContainer["workingDirectory"] = "foo";
    }
}

public class MyGetter
{
    public MyGetter(IOperationContext<string> stringContainer)
    {
        var workingDirectory = stringContainer["workingDirectory"];
    }
}

The code above will work with WCF and Thread-unique processes. But, as I'm sure you know, there are several risks when binding information for a specific thread. But I hope it can guide you in the right direction. The point is to bind it to a container and use the container instead of a string. You can use Session instead of ThreadStatic if it's user specific and so on.



来源:https://stackoverflow.com/questions/37565494/how-can-i-register-a-type-with-dynamic-string-parameters-in-its-constructor-at

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!