0

已经为此苦苦挣扎了一段时间,所以我开始认为我已经创建了一个反模式。不过,这里有;

//Register self
container.Register(Component.For<IWindsorContainer>().Instance(container));
//Register all
container.Register(Component.For<IService1>().ImplementedBy<Service1>());
container.Register(Component.For<IService2>().ImplementedBy<Service2>());
//etc

IService1
{
  //blabla
}
IService2 {IService1 Service1{get;}}

所以 IService1 和 IService2 可以在没有任何特殊的情况下创建。从 IService3 开始,涉及一个 IProject。

IProject{}
//Resolve a service that, amongst other things, relies on an IProject
IProjectGet
{
    T Get<T>(IProject proj) 
        where T : class;
}
//Impl
ProjectGet : IProjectGet
{
    IWindsorContainer _cont;
    public ProjectGet(IWindsorContainer cont){_cont=cont}

    public T Get<T>(IProject proj)
    {
        //Resolve using the main (and only) container and pass the IProject
        return _cont.Resolve<T>(new {p = proj});
    }
}

这不起作用,仅使用“p = proj”解析主服务以及主服务具有的任何其他依赖项,这些依赖项也依赖于项目,导致异常说找不到项目服务。

IService3 
{
    IService2 Service2{get;}
    IProjectGet ProjectGet{get;}
    IProjectLevelStuff SetActiveProject(IProject proj);
}
Service3 : IService3 
{
    IService2 Service2{get;private set;}
    IProjectGet ProjectGet{get;private set;}

    public Service3(IService2 s2, IProjectGet p)
    {
        ProjectGet = p;
        Service2 = s2;
    }

    public IProjectLevelStuff SetActiveProject(IProject proj)
    {
        return ProjectGet.Get<IProjectLevelStuff>(proj);
    }
}
ProjectLevelStuff : IProjectLevelStuff
{
    IProject Project{get;private set;}
    IService4 Service4 {get;private set;}

    public ProjectLevelStuff(IProject p, IService4)//etc.
}
IService4
{
    IService2 Service2{get;}
    IService5 Service5{get;}
    IService6 Service6{get;}
    IProject Project{get;}
}
IService5{IProject Project{get;}}
IService6{IProject Project{get;}}

这失败了,因为只有 ProjectLevelStuff 将 IProject 传递给它,并且由于 IService4 及其依赖项也需要它,因此引发了异常。即使这确实有效,我也不喜欢它,因为每个依赖于 IProject 的服务都被迫调用我想避免的参数“p”。

我只想继续使用我已经拥有的服务,但是这次添加了作为可解析依赖项传递给我们的通用 Get 方法的 IProject 实例。我发现无法复制容器并创建一个新容器,然后将主容器添加为子容器不会改变任何东西(依赖关系仍然缺失)。这是怎么做到的?

Castle Windsor 确实内置了一个 TypeFactory,但它本质上与我已经在做的事情相同,并且没有解决任何问题。我发现的唯一“解决方案”是创建一个新容器并重新注册类型,但这次通过主容器解决它们(当然 IProject 除外).. 这是工作中的维护噩梦。

更新:我在下面的答案中添加了一些单元测试,希望能解决一些问题

4

2 回答 2

1

请检查您是否可以使用以下方法使用范围:

[SetUp]
public void Setup()
{
    int counter = 0;

    _container = new WindsorContainer();
    _container.AddFacility<TypedFactoryFacility>();
    _container.Register(
        Component.For<IService1>().ImplementedBy<Service1>().LifestyleScoped(),
        Component.For<IService2>().ImplementedBy<Service2>().LifestyleScoped(),
        Component.For<IService3>().ImplementedBy<Service3>().LifestyleScoped(),
        Component.For<Class1>().LifestyleTransient(),
        Component.For<Class2>().LifestyleTransient(),
        Component.For<IProject>().ImplementedBy<Project>().LifestyleScoped().DynamicParameters((k, d) => d["name"] = "MyProjectName"+counter++)
        );
}

[Test]
public void TestClass1()
{
    using (_container.BeginScope())
    {
        Class1 object1 = _container.Resolve<Class1>();;
        var object2 = _container.Resolve<Class1>();
        Assert.AreNotSame(object1, object2);

        Assert.AreSame(object1.Service1, object2.Service1);
    }
}

[Test]
public void TestClass2()
{
    Class2 object1;
    using (_container.BeginScope())
    {
        object1 = _container.Resolve<Class2>();
        var object2 = _container.Resolve<Class2>();
        Assert.AreNotSame(object1, object2);

        Assert.AreSame(object1.Project, object2.Project);
        Assert.AreSame(object1.Service2.Project, object2.Service2.Project);
    }

    Class2 object3;
    using (_container.BeginScope())
    {
        object3 = _container.Resolve<Class2>();
    }

    Assert.AreNotSame(object1.Project, object3.Project);
}
于 2013-04-29T07:58:29.507 回答
0

For some strange (but probably valid) reason Windsor child-containers can access their Parent containers but not the other way around. This means that in order to use services registered in the main container from the new container, we have to set the Parent of the main container rather than that of the new container.

This is painfully inconvenient because a container can only have one Parent.

internal class ProjServices : IProjServices
{
    private readonly IKwProject _proj;
    private readonly IWindsorContainer _mainCont;

    public ProjServices(IKwProject proj, IWindsorContainer mainCont)
    {
        _mainCont = mainCont;
        _proj = proj;
    }

    public T Resolve<T>()
    {
        T rett;

        //Create new container
        var projCont = new WindsorContainer();
        //Register new service
        projCont.Register(Component.For<IKwProject>().Instance(_proj));

        //Set hierarchy
        lock (_mainCont)
        {
            projCont.AddChildContainer(UiContainer); //ui needs project, set parent to projCont
            UiContainer.AddChildContainer(_mainCont); //main needs ui, set parent to uiCont

            //Resolve using main, which now has access to UI and Project services
            try
            {
                rett = _mainCont.Resolve<T>();
            }
            finally
            {
                projCont.RemoveChildContainer(UiContainer);
                UiContainer.RemoveChildContainer(_mainCont);
            }
        }

        return rett;
    }

    private static readonly object UIContainerLock = new object();
    private static volatile IWindsorContainer _uiContainer;
    private static IWindsorContainer UiContainer
    {
        get
        {
            if(_uiContainer==null)
                lock(UIContainerLock)
                    if (_uiContainer == null)
                    {
                        //Register the UI services
                    }
            return _uiContainer;
        }
    }
}

And now if I wanted to use these new containers in even newer containers in the future I think I'd get stuck again due to the one-parent-only thing.... how do I properly do this, please?

UPDATE:

Unit Tests for VS 2010 and 2012:

ServiceTest.zip (798 KB) https://mega.co.nz/#!z4JxUDoI!UEnt3TCoMFVg-vXKEAaJrhzjxfhcvirsW2hv1XBnZCc

Or to copy&paste:

using System;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ServiceTest
{
    /// <summary>
    /// A service that doesn't rely on anything else
    /// </summary>
    public interface IService1
    {
    }
    class Service1 : IService1
    {
    }

    /// <summary>
    /// The Project
    /// </summary>
    public interface IProject
    {
        string Name { get; }
    }
    public class Project : IProject
    {
        public Project(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    /// <summary>
    /// A Service that relies on a Project
    /// </summary>
    public interface IService2
    {
        IProject Project { get; }
        string GetProjectName();
    }
    /// <summary>
    /// The implementation shows it also relies on IService3
    /// </summary>
    public class Service2 : IService2
    {
        public Service2(IProject project, IService3 service3)
        {
            Project = project;
            Service3 = service3;
        }

        public IProject Project { get; private set; }
        public IService3 Service3 { get; private set; }

        public string GetProjectName()
        {
            return Project.Name;
        }
    }
    /// <summary>
    /// IService3 is a Service that also relies on the Project
    /// </summary>
    public interface IService3
    {
        IProject Project { get; }
    }
    public class Service3 : IService3
    {
        public Service3(IProject project)
        {
            Project = project;
        }

        public IProject Project { get; private set; }
    }

    /// <summary>
    /// Class1 uses the service without any dependencies so it will be easy to resolve
    /// </summary>
    public class Class1
    {
        public Class1(IService1 service1)
        {
            Service1 = service1;
        }

        public IService1 Service1 { get; private set; }
    }

    /// <summary>
    /// Class2 also uses that service, but it also relies on a Project ánd IService2
    ///  which as you know also relies on the Project and IService3 which also relies on 
    ///  the Project
    /// </summary>
    public class Class2
    {
        public Class2(IService1 service1, IProject project, IService2 service2)
        {
            Service1 = service1;
            Project = project;
            Service2 = service2;
        }

        public IProject Project { get; private set; }
        public IService1 Service1 { get; private set; }
        public IService2 Service2 { get; private set; }
    }

    /// <summary>
    /// Set up the base services
    /// </summary>
    [TestClass]
    public class UnitTestBase
    {
        protected WindsorContainer Cont;

        [TestInitialize]
        public void BaseSetup()
        {
            Cont = new WindsorContainer();
            Cont.Register(Component.For<IService1>().ImplementedBy<Service1>().LifestyleTransient());
            Cont.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
            Cont.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());

            Cont.Register(Component.For<Class1>().LifestyleTransient());
            Cont.Register(Component.For<Class2>().LifestyleTransient());
        }

        [TestMethod]
        public void Class1_Resolves()
        {
            Cont.Resolve<Class1>();
        }
    }

    /// <summary>
    /// Set up the base unit tests
    /// </summary>
    [TestClass]
    public class UnitTestClass2Base : UnitTestBase
    {
        protected void RunTest3Times(Func<string, IWindsorContainer> getContainer)
        {
            const string projNameBase = "MyProjectName";
            Func<int, string> getProjectName = i => projNameBase + i;

            for (var i = 0; i < 3; i++)
            {
                var pName = getProjectName(i);
                GetClass2ForProject(getContainer(pName), pName);
            }
        }

        protected void GetClass2ForProject(IWindsorContainer cont, string projName)
        {
            var c2 = cont.Resolve<Class2>();

            Assert.IsTrue(c2.Project.Name == projName);
            Assert.IsTrue(c2.Service2.Project.Name == projName);
            Assert.IsTrue(c2.Service2.GetProjectName() == projName);
        }
    }

    /// <summary>
    /// This will fail on the second request because we cannot 
    ///  overwrite the earlier registration. And iirc containers can't
    ///  be altered after the first resolve.
    /// </summary>
    [TestClass]
    public class Attempt_1 : UnitTestClass2Base
    {
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests()
        {
            RunTest3Times(s =>
                {
                    Cont.Register(Component.For<IProject>().Instance(new Project(s)));
                    return Cont;
                });
        }
    }

    /// <summary>
    /// It looks like we have to create a new container for every Project
    /// So now the question remains; how do we get to keep using the base IService implementations
    ///  in the container that is scoped for the IProject?
    /// </summary>
    [TestClass]
    public class Attempt_2 : UnitTestClass2Base
    {
        static IWindsorContainer CreateContainer(IProject p)
        {
            var ret = new WindsorContainer();
            ret.Register(Component.For<IProject>().Instance(p));
            return ret;
        }

        /// <summary>
        /// This will fail because the services in the main 
        ///  container can't access the IProject in the new container
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_1()
        {
            RunTest3Times(s =>
            {
                //Add the project container as a Child to the Main container
                var projCont = CreateContainer(new Project(s));
                Cont.AddChildContainer(projCont);
                return Cont;
            });
        }

        /// <summary>
        /// Doing the previous approach the other way around works.
        /// But now we can only resolve one thing at a time
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_2()
        {
            IWindsorContainer projCont = null;

            //Add the Main container as a Child to the project container
            // (in other words set the Parent of Main to Project)
            // and then resolve using the main container.
            //A container can only have one parent at a time so we can only
            // resolve one scoped thing at a time.
            RunTest3Times(s =>
                {
                    if (projCont != null)
                        projCont.RemoveChildContainer(Cont);

                    projCont = CreateContainer(new Project(s));
                    projCont.AddChildContainer(Cont);
                    return Cont;
                });
        }

        /// <summary>
        /// The only way around that issue seems to be to register all project-dependent 
        ///  services in the new container. Then re-register all original services
        ///  in the new container and pass the resolving on to the main container; 
        ///  a maintenance nightmare and especially painful for named registrions.
        /// </summary>
        [TestMethod]
        public void Class2_Resolves_Project_Scoped_Requests_3()
        {
            Func<IProject, IWindsorContainer> createContainer2 = p =>
                {
                    var contNew = new WindsorContainer();

                    //Pass resolving of the non-dependent services on to the main container.
                    // this way it will respect it's lifestyle rules and not create new 
                    // instances of services we wanted to use as a singleton etc.
                    contNew.Register(Component.For<IService1>().UsingFactoryMethod(() => Cont.Resolve<IService1>()).LifestyleTransient());
                    contNew.Register(Component.For<Class1>().UsingFactoryMethod(() => Cont.Resolve<Class1>()).LifestyleTransient());

                    //Register the dependent services directly in the new container so they can access the project
                    contNew.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
                    contNew.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());
                    contNew.Register(Component.For<Class2>().LifestyleTransient());

                    contNew.Register(Component.For<IProject>().Instance(p));

                    return contNew;
                };

            RunTest3Times(s =>
            {
                var projCont = createContainer2(new Project(s));
                return projCont;
            });
        }
    }
}
于 2013-04-25T13:52:45.187 回答