问题
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