简体   繁体   中英

Castle Windsor Lifestyle configuration

I've spent some time looking around, and there doesn't seem to be an obvious solution to this scenario.

I register all types from an assembly (that's about 80 types, interfaces are in separate assembly)

    public static void RegisterAllFromAssemblies(string a)
    {
        IoC.Container.Register(
            AllTypes.FromAssemblyNamed(a)
                .Pick()
                .WithService.FirstInterface()
                .Configure(o => o.LifeStyle.PerWebRequest)
            );
    }

now say if i want to use a different LifeStyle for one of those objects, i can't override since i'll get the There is a component already registered for the given key error.

i've looked into various ways to modify the lifestyle after this registration, but so far haven't been able to make anything work.

what should be the ideal solution here? i'd prefer not to give up the AllTypes functionality.

i suppose i could specify a .Where filter when registering all and skip a few objects to be registered manually, but that's not a very enterprisey solution..

I believe you're talking about registering all of the types in an assembly where some of the types in the assembly might need to be registered with different lifestyles. So you've got IRepository which needs to be a PerWebRequest and ITypeMapper which can be a singleton.

I clarify because you could also mean that you want to have IRepository be a PerWebRequest at one spot in your code and a singleton in another spot. Without creating crazy lifestyles, you can create your component and register it for the default lifestyle. If you need another lifestyle sometimes you can create a new component and inherit from the existing one just for use in registration (the code sample shows this if this is confusing).

I wrote the sample so that it will work for either scenario and I gave a couple different approaches all focusing around the filtering abilities of configuring multiple items at once.

For this one, I'm calling out configuration for a particular component by type. It's not as "enerprisey" as you put it, but the intent is clearer if you only have a few exceptions to the rule. You'll note you can chain together configures. The unless is required because the second configure will pick up the component for the first configure being that my only condition is the services are based on IService. This assumes that castle processes the configures in order. I believe the assumption is sound, but haven't looked at the source for awhile.

container.Register(
    Classes.FromThisAssembly()
        .BasedOn<IService>()
        .ConfigureFor<MyComponentAsSingleton>(component => component.LifestyleSingleton())
        .Configure(component => component.LifestylePerWebRequest()).Unless(type => container.Kernel.GetAssignableHandlers(type).Count() > 0));

This one uses attributes to more generically deviate from the normal lifestyle "PerWebRequest

container2.Register(
    Classes.FromThisAssembly()
        .BasedOn<IService>()
        .ConfigureIf(
            //condition to check - do we have our custom Attribute?
            registration => registration.Implementation.GetCustomAttributes(false).Any(attr => typeof(ShouldBeSingleton).IsAssignableFrom(attr.GetType())),
            //if true register as singleton
            component => component.LifestyleSingleton(),
            //else register as per web request
            component => component.LifestylePerWebRequest()
            ));

Now that I've given you a few samples that solve your immediate issue (as I understand it) let me give you my advice for free!

First I don't really like WithService.FirstInterface(). As the intelisense states it's non-deterministic when you implement multiple interfaces. Any dev could come in and make a harmless interface change to a class and then break the system. If you can get away with WithService.DefaultInterfaces() you'd have a harder to mess up solution. Default interfaces is just telling castle that when registering the Foo component, use the service IFoo if it implements an interface named IFoo.

Second, I believe if you partition your registration logic into cohesive units you probably wouldn't have run into this problem. The key is to have many installer files that implement IWindsorInstaller. Inside of these installers you only register (using the Classes or Types to keep it enterprisey still) types that make sense for the particular installer. The chances that you have multiple lifestyle concerns in the same installer is pretty low (and if you find this, you probably need more installers)

If you followed this approach you could end up with a RepositoryInstaller, ViewInstaller, ControllerInstaller, etc. More on installers can be found on the castle documentation site

What you could do if you wanted is then have a common boostrapper for all your systems that looks into the application directory and installs all of the installers that are in the directory. Being this wasn't what you asked I'll stop elaborating, but if interested you can ping me and I can show you more about what I'm talking about.

Full sample code as a console app:

using Castle.MicroKernel.Registration;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MultipleLifecyles
{
    [AttributeUsage(AttributeTargets.Class)]
    public class ShouldBeSingleton : Attribute
    {
    }

    public interface IService
    {
        void DoSomething();
    }

    public class MyComponent : IService
    {
        public void DoSomething()
        {
            throw new NotImplementedException();
        }
    }

    [ShouldBeSingleton]
    public class MyComponentAsSingleton : MyComponent
    {

    }

    class Program
    {
        static void Main(string[] args)
        {

            //option 1
            IWindsorContainer container = new WindsorContainer();

            container.Register(
                Classes.FromThisAssembly()
                    .BasedOn<IService>()
                    .ConfigureFor<MyComponentAsSingleton>(component => component.LifestyleSingleton())
                    .Configure(component => component.LifestylePerWebRequest()).Unless(type => container.Kernel.GetAssignableHandlers(type).Count() > 0));

            IWindsorContainer container2 = new WindsorContainer();

            container2.Register(
                Classes.FromThisAssembly()
                    .BasedOn<IService>()
                    .ConfigureIf(
                        //condition to check - do we have our custom Attribute?
                        registration => registration.Implementation.GetCustomAttributes(false).Any(attr => typeof(ShouldBeSingleton).IsAssignableFrom(attr.GetType())),
                        //if true register as singleton
                        component => component.LifestyleSingleton(),
                        //else register as per web request
                        component => component.LifestylePerWebRequest()
                        ));


            Console.ReadLine();
        }
    }
}

Is it an option for you register manually the exceptions first? If so, components manually registered will not be re-registered by "AllTypes"(I suggest you to use Classes instead). If you register manually a component after a "group" registration, an exception will be thrown, but not vice versa.

For example

//YourImplementation lives in assembly 'a'
IoC.Container.Register(
    Component.For<YourInterface>().ImplementedBy<YourImplementation>().LifestyleSingleton()
    );

IoC.Container.Register(
    Classes.FromAssemblyNamed(a)
        .Pick()
        .WithService.FirstInterface()
        .Configure(o => o.LifeStyle.PerWebRequest)
    );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM