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

0

いくつかの奇妙な (しかしおそらく有効な) 理由により、Windsor の子コンテナーは親コンテナーにアクセスできますが、その逆はできません。つまり、メイン コンテナに登録されているサービスを新しいコンテナから使用するには、新しいコンテナの Parent ではなく、メイン コンテナの Parent を設定する必要があります。

コンテナーは親を 1 つしか持つことができないため、これは非常に不便です。

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;
        }
    }
}

そして、将来、これらの新しいコンテナをさらに新しいコンテナで使用したい場合、親が1つしかないために再び行き詰まると思います....どうすればこれを適切に行うことができますか?

アップデート:

VS 2010 および 2012 の単体テスト:

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

またはコピー&ペーストするには:

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 に答える