Setting different Castle Windsor lifestyle from different applications using the same installer

StackOverflow https://stackoverflow.com/questions/22425481

  •  15-06-2023
  •  | 
  •  

Question

I have a problem with figuring out what the best way is to register different lifestyles for different applications using the same installers.

I have a web project that use Castle Windsor IoC. Some things are scoped to use PerWebRequest lifestyle. The classes are registered with installers in the respective projects.

All good so far, but I'm thinking about moving some stuff to a windowsService to do some scheduled stuff. This service will resolve the same domain as the web project but get trouble when the classes are registered with PerWebRequest lifestyle. I'd like to change this to Scoped for the service but keep it as it is in the web app. The configuration is done in the installer kept in the respective assemblies.

The installers doesn't know which app is trying to register, but the registrations needs to be with different lifestyle depending on if the installer is registered from the web app or the windows service.

Right now I have restorted to an ugly Hack until I get a better solution.

private static bool scopeLifetime = false;
public static void SetScopedLifetime()
{
    scopeLifetime = true;
}

public void Install(IWindsorContainer container, IConfigurationStore store)
{
    var registrations = Classes.FromThisAssembly()
        .WithService.DefaultInterfaces();

    if (scopeLifetime)
    {
        container.Register(registrations.LifestyleScoped());
    }
    else
    {
        container.Register(registrations.LifestylePerWebRequest());
    }
}

What is the correct way to tell windsor what I would like to do, without the installer needing to know the context?

Was it helpful?

Solution 2

Krzysztof Kozmic's comment is on the spot, however it is possible to "cheat" by letting the value of your component lifestyle be injected by a component existing only in each codebase.

Let's say there exists an interface in your common libraries, with the following signature:

public interface ISetLifestyle
{
    BasedOnDescriptor SetLifestyle(BasedOnDescriptor descriptor);
}

And an implementation in each of your specific components (ie service and web app)

// this class exists only in your webapp
public class SetLifestyleOnWebApp: ISetLifestyle
{
    public BasedOnDescriptor SetLifestyle(BasedOnDescriptor descriptor)
    {
        return descriptor.LifestylePerWebRequest();
    }
}

// this class exists only in your windows service
public class SetLifestyleOnService : ISetLifestyle
{
    public BasedOnDescriptor SetLifestyle(BasedOnDescriptor descriptor)
    {
        return descriptor.LifestyleTransient(); // or whatever scope you need
    }
}

In your common configuration, you could then start by registering the ISetLifestyle service, and then using it to define the lifestyle of your components.

container.Register(Classes.FromAssemblyInThisApplication().BasedOn<ISetLifestyle>().WithServiceBase());
var myCustomLifestyleSetter = container.Resolve<ISetLifestyle>();

var customLifestyleRegistrations = myCustomLifestyleSetter.SetLifestyle(Classes.FromThisAssembly().Pick().WithServiceDefaultInterfaces());
container.Register(customLifestyleRegistrations);

You would need to make sure that

  • each application defines a ISetLifestyle service, or that a default one can always be found
  • classes with common lifestyles between applications are not passed to this method

I don't know if this is a good idea since it obscures the configuration but it answers your question

OTHER TIPS

After some more practice with the castle framework, I would like to point out a much better way to handle this question. There is an interface called IContributeComponentModelConstruction that lets you manipulate the configuration of a component after it has been registered. It is used after each registration so it can be used to cast a wide net on the configuration of your components. Your particular problem was neatly described in a blog post by Mark Seeman

Given this installer:

public class FooInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container,
        IConfigurationStore store)
    {
        container.Register(Component.For<IFoo>().ImplementedBy<Foo>().LifeStyle.Transient);
    }
}

You may want to change the lifestyle in another context, so create an instance of IContributeComponentModelConstruction and add it to the container

public class SingletonEqualizer :
    IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, 
        ComponentModel model)
    {
        model.LifestyleType = LifestyleType.Singleton;
    }
}

/*  ---  */

var container = new WindsorContainer();
container.Kernel.ComponentModelBuilder
    .AddContributor(new SingletonEqualizer()); // before the intaller is called
container.Install(new FooInstaller());

Every component will then have its lifestyle changed; you could also refine the behavior depending on the component that is being registered. In retrospect I really think this is the best answer to your question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top