问题
I've been struggling with this problem for a couple days, and I still am not sure how to solve it.
I've created a container extension for the Unity Container to enable me to easily register decorator classes in the container. This is the implementation I currently have, which is almost identical to the one in this article:
public class DecoratorExtension : UnityContainerExtension
{
private int m_order;
private Dictionary<Type, IList<DecoratorRegistration>> m_typeStacks;
protected override void Initialize()
{
m_typeStacks = new Dictionary<Type, IList<DecoratorRegistration>>();
Context.Registering += AddRegistration;
Context.Strategies.Add(new DecoratorBuildStrategy(m_typeStacks), UnityBuildStage.PreCreation);
}
private void AddRegistration(object _sender, RegisterEventArgs _e)
{
if (_e.TypeFrom == null || !_e.TypeFrom.IsInterface)
return;
GetStack(_e.TypeFrom)
.Add(new DecoratorRegistration {Order = m_order++, Type = _e.TypeTo});
}
private IList<DecoratorRegistration> GetStack(Type _type)
{
if (!m_typeStacks.ContainsKey(_type))
m_typeStacks.Add(_type, new List<DecoratorRegistration>());
return m_typeStacks[_type];
}
}
What this does is use a list for each type, to store all type registrations for the same target type, so that I can reassemble it when Resolve is called, using this build strategy:
internal class DecoratorBuildStrategy : BuilderStrategy
{
private readonly Dictionary<Type, IList<DecoratorRegistration>> m_typeStacks;
internal DecoratorBuildStrategy(Dictionary<Type, IList<DecoratorRegistration>> _typeStacks)
{
m_typeStacks = _typeStacks;
}
public override void PreBuildUp(IBuilderContext _context)
{
var key = _context.OriginalBuildKey;
if (_context.GetOverriddenResolver(key.Type) != null)
return;
// Only interfaces can use decorators.
if (!key.Type.IsInterface)
return;
// Gets the list of types required to build the 'decorated' instance.
// The list is reversed so that the least dependent types are built first.
var decoratorTypes = GetDecoratorTypes(key.Type).Reverse().ToList();
if (!decoratorTypes.Any())
return;
object value = null;
foreach (var type in decoratorTypes)
{
Type typeToBuild = type;
if (typeToBuild.IsGenericTypeDefinition)
{
Type[] genericArgumentTypes = key.Type.GetGenericArguments();
typeToBuild = typeToBuild.MakeGenericType(genericArgumentTypes);
}
value = _context.NewBuildUp(new NamedTypeBuildKey(typeToBuild, key.Name));
// An Override is created so that in the next BuildUp the already
// built object gets used instead of doing the BuildUp again and
// entering an infinite loop
_context.AddResolverOverrides(new DependencyOverride(key.Type, value));
}
_context.Existing = value;
_context.BuildComplete = true;
}
private IEnumerable<Type> GetDecoratorTypes(Type _type)
{
var typeList = m_typeStacks.GetValueOrDefault(_type) ?? new List<DecoratorRegistration>(0);
if (!_type.IsGenericType)
return typeList.Select(_reg => _reg.Type);
// If the type is a generic type, we need to get all open generic registrations
// alongside the closed ones
var openGenericList = m_typeStacks
.GetValueOrDefault(_type.GetGenericTypeDefinition()) ??
new List<DecoratorRegistration>(0);
// The final result is an ordered concatenation of the closed and open registrations
// that should be used for the type
return typeList
.Concat(openGenericList)
.OrderBy(_registration => _registration.Order)
.Select(_reg => _reg.Type);
}
}
This is where the DecoratorRegistration model is used. It is just a pair of type/int that represents the order of the registration. I created this to be able to mix open and closed generic registrations correctly:
internal struct DecoratorRegistration
{
public int Order { get; set; }
public Type Type { get; set; }
}
This works wonders for the most part. The problem started when I had a class that implemented two interfaces, one which was decorated, and one that wasn't.
This is the current test case I'm trying to make work:
private interface IAny<T> {}
private interface IAnotherInterface {}
private class Base<T> : IAnotherInterface, IAny<T> {}
private class Decorator1<T> : IAny<T>
{
internal readonly IAny<T> Decorated;
public Decorator1(IAny<T> _decorated)
{
Decorated = _decorated;
}
}
[TestMethod]
public void DecoratorExtensionDoesNotInterfereWithNormalRegistrations()
{
// Arrange
var container = new UnityContainer()
.AddNewExtension<DecoratorExtension>()
.RegisterType<Base<string>>(new ContainerControlledLifetimeManager())
.RegisterType<IAny<string>, Decorator1<string>>()
.RegisterType<IAny<string>, Base<string>>()
.RegisterType<IAnotherInterface, Base<string>>();
// Act
var decorated = container.Resolve<IAny<string>>();
var normal = container.Resolve<IAnotherInterface>();
var anotherDecorated = container.Resolve<IAny<string>>();
var anotherNormal = container.Resolve<IAnotherInterface>();
// Assert
Assert.IsInstanceOfType(normal, typeof (IAnotherInterface));
Assert.IsInstanceOfType(decorated, typeof (Decorator1<string>));
Assert.AreSame(normal, anotherNormal);
Assert.AreSame(decorated, anotherDecorated);
}
This test should make my intent clear. I wanted singleton classes, but the first call to Resolve, for either IAnotherInterface
or IAny<string>
results in every subsequent call to return the same thing. Thus, I get an exception:
System.InvalidCastException: Unable to cast object of type 'Decorator1`1[System.String]' to type 'IAnotherInterface'.
on this line:
var normal = container.Resolve<IAnotherInterface>();
I'm not sure what to do here. I had to temporarily disable singletons in our project so that this could work as intended. What I wanted is that the Base<string>
instance was a sintleton, but when I requested a IAny<string>
it would create a NEW instance with the SAME base being decorated.
This is still using .Net 4.0, so I'm stuck with Unity 2.1 here (shouldn't matter in this case though).
回答1:
It's been a while since I've solved this, so I figured that it would be good to replicate the answer I got from Randy Levy from the EntLib team here.
It basically boils down to the build key I was using to register the decorator instance. With my code, the instance was actually registered with the base class type, while I needed to register it with the actual decorator type.
This post has the suggested workaround for the issue, which worked very nicely on our end.
回答2:
I'm not exactly sure if this is what you're looking for, but I think this does the trick in the specific case in your test:
container.RegisterType<IAny<string>, Base<string>>(
new ContainerControlledLifetimeManager(), "Inner");
container.RegisterType<IAny<string>, Decorator1<string>>(
new InjectionConstructor(
new ResolvedParameter(typeof(IAny<string>), "Inner")));
container.Register<IAnotherInterface>(new InjectionFactory(
c => c.Resolve<IAny<string>>("Inner")));
You don't need that extension for that.
来源:https://stackoverflow.com/questions/17478689/unity-decorator-extension-fails-with-multiple-implementations