Unity Decorator Extension fails with multiple implementations

萝らか妹 提交于 2019-12-11 08:10:07

问题


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

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