Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/webpack/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在不显式指定InjectionConstructor中的每个参数的情况下,如何将Decorator模式与Unity结合使用_C#_Unity Container_Decorator - Fatal编程技术网

C# 在不显式指定InjectionConstructor中的每个参数的情况下,如何将Decorator模式与Unity结合使用

C# 在不显式指定InjectionConstructor中的每个参数的情况下,如何将Decorator模式与Unity结合使用,c#,unity-container,decorator,C#,Unity Container,Decorator,David Haydn的这篇有用的文章(编辑:scam链接被删除,它可能已经被删除)展示了如何使用InjectionConstructor类来帮助您使用Unity的decorator模式建立一个链。但是,如果decorator链中的项在其构造函数中有其他参数,InjectionConstructor必须显式声明它们中的每一个(否则Unity会抱怨它找不到正确的构造函数)。这意味着您不能简单地向decorator链中的项添加新的构造函数参数,而不更新Unity配置代码 这里有一些示例代码来解释我的

David Haydn的这篇有用的文章(编辑:scam链接被删除,它可能已经被删除)展示了如何使用
InjectionConstructor
类来帮助您使用Unity的decorator模式建立一个链。但是,如果decorator链中的项在其构造函数中有其他参数,
InjectionConstructor
必须显式声明它们中的每一个(否则Unity会抱怨它找不到正确的构造函数)。这意味着您不能简单地向decorator链中的项添加新的构造函数参数,而不更新Unity配置代码

这里有一些示例代码来解释我的意思。
ProductRepository
类首先由
CachingProductRepository
包装,然后由
loggingProductRepository
包装。CachingProductRepository和LoggingProductRepository除了在其构造函数中使用IPProductRepository外,还需要来自容器的其他接口

    public class Product 
    {
        public int Id;
        public string Name;
    }

    public interface IDatabaseConnection { }

    public interface ICacheProvider 
    { 
        object GetFromCache(string key);
        void AddToCache(string key, object value);
    }

    public interface ILogger
    {
        void Log(string message, params object[] args);
    }


    public interface IProductRepository
    {
        Product GetById(int id);    
    }

    class ProductRepository : IProductRepository
    {
        public ProductRepository(IDatabaseConnection db)
        {
        }

        public Product GetById(int id)
        {
            return new Product() { Id = id, Name = "Foo " + id.ToString() };
        }
    }

    class CachingProductRepository : IProductRepository
    {
        IProductRepository repository;
        ICacheProvider cacheProvider;
        public CachingProductRepository(IProductRepository repository, ICacheProvider cp)
        {
            this.repository = repository;
            this.cacheProvider = cp;
        }

        public Product GetById(int id)
        {       
            string key = "Product " + id.ToString();
            Product p = (Product)cacheProvider.GetFromCache(key);
            if (p == null)
            {
                p = repository.GetById(id);
                cacheProvider.AddToCache(key, p);
            }
            return p;
        }
    }

    class LoggingProductRepository : IProductRepository
    {
        private IProductRepository repository;
        private ILogger logger;

        public LoggingProductRepository(IProductRepository repository, ILogger logger)
        {
            this.repository = repository;
            this.logger = logger;
        }

        public Product GetById(int id)
        {
            logger.Log("Requesting product {0}", id);
            return repository.GetById(id);
        }
    }
Func<IUnityContainer,object> createChain = container =>
    new LoggingProductRepository(
        new CachingProductRepository(
            container.Resolve<ProductRepository>(), 
            container.Resolve<ICacheProvider>()), 
        container.Resolve<ILogger>());

c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
这里有一个(通过的)单元测试。请参阅注释,了解我想要消除的多余配置位:

    [Test]
    public void ResolveWithDecorators()
    {
        UnityContainer c = new UnityContainer();            
        c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
        c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
        c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);

        c.RegisterType<IProductRepository, ProductRepository>("ProductRepository");

        // don't want to have to update this line every time the CachingProductRepository constructor gets another parameter
        var dependOnProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("ProductRepository"), new ResolvedParameter<ICacheProvider>());
        c.RegisterType<IProductRepository, CachingProductRepository>("CachingProductRepository", dependOnProductRepository);

        // don't want to have to update this line every time the LoggingProductRepository constructor changes
        var dependOnCachingProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("CachingProductRepository"), new ResolvedParameter<ILogger>());
        c.RegisterType<IProductRepository, LoggingProductRepository>(dependOnCachingProductRepository);
        Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
    }
[测试]
公共无效ResolveWithDecorators()
{
UnityContainer c=新的UnityContainer();
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterType(“ProductRepository”);
//不希望每次CachingProductRepository构造函数获取另一个参数时都必须更新此行
var dependOnProductRepository=new InjectionConstructor(new ResolvedParameter(“ProductRepository”),new ResolvedParameter();
c、 RegisterType(“CachingProductRepository”,dependOnProductRepository);
//不希望每次LoggingProductRepository构造函数更改时都必须更新此行
var dependchingproductrepository=new InjectionConstructor(new ResolvedParameter(“CachingProductRepository”),new ResolvedParameter();
c、 RegisterType(DependochingProductRepository);
Assert.IsInstanceOf(c.Resolve());
}

当我在等待答案时,我想出了一个相当粗糙的解决方法。我已经在
IUnityContainer
上创建了一个扩展方法,允许我使用反射注册一个装饰器链来创建InjectionConstructor参数:

static class DecoratorUnityExtensions
{
    public static void RegisterDecoratorChain<T>(this IUnityContainer container, Type[] decoratorChain)
    {
        Type parent = null;
        string parentName = null;
        foreach (Type t in decoratorChain)
        {
            string namedInstance = Guid.NewGuid().ToString();
            if (parent == null)
            {
                // top level, just do an ordinary register type                    
                container.RegisterType(typeof(T), t, namedInstance);
            }
            else
            {
                // could be cleverer here. Just take first constructor
                var constructor = t.GetConstructors()[0];
                var resolvedParameters = new List<ResolvedParameter>();
                foreach (var constructorParam in constructor.GetParameters())
                {
                    if (constructorParam.ParameterType == typeof(T))
                    {
                        resolvedParameters.Add(new ResolvedParameter<T>(parentName));
                    }
                    else
                    {
                        resolvedParameters.Add(new ResolvedParameter(constructorParam.ParameterType));
                    }
                }
                if (t == decoratorChain.Last())
                {
                    // not a named instance
                    container.RegisterType(typeof(T), t, new InjectionConstructor(resolvedParameters.ToArray()));
                }
                else
                {
                    container.RegisterType(typeof(T), t, namedInstance, new InjectionConstructor(resolvedParameters.ToArray()));
                }
            }
            parent = t;
            parentName = namedInstance;
        }
    }
}
静态类decororUnityExtensions
{
公共静态无效注册表decoratorChain(此IUnityContainer容器,类型[]decoratorChain)
{
类型parent=null;
字符串parentName=null;
foreach(装饰链中的t型)
{
字符串namedInstance=Guid.NewGuid().ToString();
如果(父项==null)
{
//顶层,只需做一个普通的寄存器类型
容器注册表类型(类型(T),T,名称状态);
}
其他的
{
//在这里可能会更聪明。只需要第一个构造器
var constructor=t.GetConstructors()[0];
var resolvedParameters=新列表();
foreach(constructor.GetParameters()中的var constructorParam)
{
if(constructorParam.ParameterType==typeof(T))
{
添加(新的ResolvedParameter(parentName));
}
其他的
{
添加(新的ResolvedParameter(constructorParam.ParameterType));
}
}
if(t==decoratorChain.Last())
{
//不是命名实例
RegisterType(typeof(T),T,newinjectionconstructor(resolvedParameters.ToArray());
}
其他的
{
RegisterType(typeof(T),T,namedInstance,newinjectionconstructor(resolvedParameters.ToArray());
}
}
父代=t;
parentName=namedInstance;
}
}
}
这使我能够使用可读性更强的语法配置容器:

[Test]
public void ResolveWithDecorators2()
{
    UnityContainer c = new UnityContainer();
    c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
    c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
    c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);

    c.RegisterDecoratorChain<IProductRepository>(new Type[] { typeof(ProductRepository), typeof(CachingProductRepository), typeof(LoggingProductRepository) });

    Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());

}
[测试]
使用Decorators 2()解决公共无效问题
{
UnityContainer c=新的UnityContainer();
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterDecoratorChain(新类型[]{typeof(ProductRepository)、typeof(CachingProductRepository)、typeof(LoggingProductRepository)});
Assert.IsInstanceOf(c.Resolve());
}

我仍然有兴趣知道是否有一个更优雅的解决方案,使用Unity

另一种方法,感谢@DarkSquirrel42的建议,就是使用
注入工厂
。缺点是每次在链中添加新的构造函数参数时,代码仍然需要更新。其优点是代码更容易理解,并且只需在容器中注册一次

    public class Product 
    {
        public int Id;
        public string Name;
    }

    public interface IDatabaseConnection { }

    public interface ICacheProvider 
    { 
        object GetFromCache(string key);
        void AddToCache(string key, object value);
    }

    public interface ILogger
    {
        void Log(string message, params object[] args);
    }


    public interface IProductRepository
    {
        Product GetById(int id);    
    }

    class ProductRepository : IProductRepository
    {
        public ProductRepository(IDatabaseConnection db)
        {
        }

        public Product GetById(int id)
        {
            return new Product() { Id = id, Name = "Foo " + id.ToString() };
        }
    }

    class CachingProductRepository : IProductRepository
    {
        IProductRepository repository;
        ICacheProvider cacheProvider;
        public CachingProductRepository(IProductRepository repository, ICacheProvider cp)
        {
            this.repository = repository;
            this.cacheProvider = cp;
        }

        public Product GetById(int id)
        {       
            string key = "Product " + id.ToString();
            Product p = (Product)cacheProvider.GetFromCache(key);
            if (p == null)
            {
                p = repository.GetById(id);
                cacheProvider.AddToCache(key, p);
            }
            return p;
        }
    }

    class LoggingProductRepository : IProductRepository
    {
        private IProductRepository repository;
        private ILogger logger;

        public LoggingProductRepository(IProductRepository repository, ILogger logger)
        {
            this.repository = repository;
            this.logger = logger;
        }

        public Product GetById(int id)
        {
            logger.Log("Requesting product {0}", id);
            return repository.GetById(id);
        }
    }
Func<IUnityContainer,object> createChain = container =>
    new LoggingProductRepository(
        new CachingProductRepository(
            container.Resolve<ProductRepository>(), 
            container.Resolve<ICacheProvider>()), 
        container.Resolve<ILogger>());

c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
Func createChain=container=>
新LoggingProductRepository(
新CachingProductRepository(
container.Resolve(),
container.Resolve()),
container.Resolve());
c、 RegisterType(新注入工厂(createChain));
Assert.IsInstanceOf(c.Resolve());

请参见关于实现装饰器容器扩展的说明。如果构造函数签名发生更改,则无需修改配置。另一种解决方案是向代码库中添加类型参数,以帮助Unity解析修饰类型。幸运的是,Unity完全能够自行解析类型参数及其依赖关系,因此在de时我们不必关心构造函数参数
public static class UnityExtensions
{
    public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, params InjectionMember[] injectionMembers)
        where TDecorator : class, TInterface
    {
        return Decorate<TInterface, TDecorator>(container, null, injectionMembers);
    }

    public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers)
        where TDecorator : class, TInterface
    {
        string uniqueId = Guid.NewGuid().ToString();
        var existingRegistration = container.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
        if(existingRegistration == null)
        {
            throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
        }
        var existing = existingRegistration.MappedToType;

        //1. Create a wrapper. This is the actual resolution that will be used
        if (lifetimeManager != null)
        {
            container.RegisterType<TInterface, TDecorator>(uniqueId, lifetimeManager, injectionMembers);
        }
        else
        {
            container.RegisterType<TInterface, TDecorator>(uniqueId, injectionMembers);
        }

        //2. Unity comes here to resolve TInterface
        container.RegisterType<TInterface, TDecorator>(new InjectionFactory((c, t, sName) =>
        {
            //3. We get the decorated class instance TBase
            var baseObj = container.Resolve(existing);

            //4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
            return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
        }));

        return container;
    }
}
container.RegisterType<IProductRepository, ProductRepository>();

// Wrap ProductRepository with CachingProductRepository,
// injecting ProductRepository into CachingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, CachingProductRepository>();

// Wrap CachingProductRepository with LoggingProductRepository,
// injecting CachingProductRepository into LoggingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, LoggingProductRepository>();
container.RegisterType<ILogger, DecoratorLogger>(
    new InjectionConstructor(
        new ResolvedParameter<Log4NetLogger>()));
public static IContainerRegistry RegisterDecorator<TInterface, TDecorator>(this IContainerRegistry container, ITypeLifetimeManager lifetimeManager, Type[] additionalInterfaces, params InjectionMember[] injectionMembers)
    where TDecorator : class, TInterface
{    
    var unityContainer = container.GetContainer();

    var existingRegistration = unityContainer.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));

    if (existingRegistration == null)
    {
        throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
    }

    var existing = existingRegistration.MappedToType;
    var uniqueId = Guid.NewGuid().ToString();

    // 1. Create a wrapper. This is the actual resolution that will be used
    if (lifetimeManager != null)
    {
        unityContainer.RegisterType<TDecorator>(uniqueId, lifetimeManager, injectionMembers);
    }
    else
    {
        unityContainer.RegisterType<TDecorator>(uniqueId, injectionMembers);
    }

    unityContainer.RegisterType<TInterface, TDecorator>();

    if (additionalInterfaces != null)
    {
        foreach (var additionalInterface in additionalInterfaces)
        {
            unityContainer.RegisterType(additionalInterface, typeof(TDecorator));
        }
    }

    unityContainer.RegisterFactory<TDecorator>(DecoratorFactory);

    return container;

    object DecoratorFactory(IUnityContainer c)
    {
        // 3. We get the decorated class instance TBase
        var baseObj = c.Resolve(existing);

        // 4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
        return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
    }
}