2

We have a web api project which references a library that in turn is shared between many different systems.

The library exposes a "RegisterDependancies(IWindsorContainer container)" function that when called will register all of the dependencies for that particular library.

So when the web api application starts it does its own registrations and then calls the shared library, something like the following pseudo code :

public void SetupWindsor(){

    IWindsorContainer container = new WindsorContainer();

    container.Register(Component.For<iFoo>().ImplementedBy<Foo>());

     SharedLibrary.RegisterDependancies(container);
}

The shared library function looks a bit like this:

public void RegisterDependancies(IWindsorContainer container)
{
    container.Register(Component.For<iBar>().ImplementedBy<Bar>());
    //In this instance Bar is internal so could not be registered by the api project.
}

We now want the web api project to use the PerWebRequest lifestyle but other systems may want to use a different lifestyle (This decision will be down to whoever writes the client app - the shared library will be used in console and windows apps - so perwebrequest isn't suitable for them to use)

We want to change the RegisterDependancies method so that we can pass in the lifestyle.

Is it possible to do this with the PerWebRequest lifestyle? A few of us had a look and couldnt see how to do it - despite having done similar without issues with unity in the past.

We are either looking for a way to resolve the problem in the way described, or if there is a better way to approach this that would also be helpful.

Edit to clarify, the shared library defines the interface IBar as public but the class Bar as internal so the API cannot do the registration - unless there is some magic in windsor that I am unaware of.

5NRF
  • 401
  • 3
  • 12
  • 1
    According to Mark Seeman's book "Dependency Injection" libraries should not have a composition root. So they are not concerned with composing the application. You not only have problems with the lifestyle, also with exchanging implementations. Just leave the complete registration to your app – Creepin May 01 '19 at 20:30
  • 1
    Don't see how that would work. As per the question the library has public interfaces which have internal implementations so the app cannot access the class to do the registration, Edited the question to clarify this. – 5NRF May 01 '19 at 20:59
  • Don't quite agree with Mark Seeman's approach, for exactly the reason mentioned by @5NRF. As a general principle, I would build a library installer in such a way that the behaviour of components supports any deployment scenarios. The 'Per Request' lifestyle should be limited to as few points of composition as possible - ideally outside the library itself. You can then rely on the fact that a 'Transient' component, that is a dependency of a 'Per Web Request' component, in effect, automatically becomes a 'Per Web Request' component. – Phil Degenhardt May 03 '19 at 01:22

2 Answers2

1

You can set the lifestyle on the component registration using LifeStyle.Is(LifestyleType)

Something like this will allow you to pass the lifecycle in.

public void RegisterDependencies(IWindsorContainer container, Castle.Core.LifestyleType lifestyleType)
{
    container.Register(Component.For<IBar>().ImplementedBy<Bar>().LifeStyle.Is(lifestyleType));
}

Alternatively, take a look at registering by convention with the assembly, this would allow control of the lifestyle from within the API project without needing a public implementation https://github.com/castleproject/Windsor/blob/master/docs/registering-components-by-conventions.md

kurtmkurtm
  • 138
  • 1
  • 7
  • I think thats half way there... The enum does not have The LifeStylePerWebRequest :) – 5NRF May 02 '19 at 19:03
  • We did a bit of digging into the differences between Transient and PerWebRequest today and I dont think that there is a significant advantage to PerWebRequest for our usage now so we will probably just end up using transient - would still be nice to know how/if its possible though! – 5NRF May 02 '19 at 19:10
1

the shared library defines the interface IBar as public but the class Bar as internal so the API cannot do the registration

Let's examine that design decision for a moment. While no external callers can create a Bar object, they can create an IBar object:

IWindsorContainer container = new WindsorContainer();
SharedLibrary.RegisterDependancies(container);
IBar bar = container.Resolve<IBar>();

There can be various reasons to keep the Bar class internal, but since you can already run code like the above, why not simply let the shared library expose a factory?

IBar bar = SharedLibrary.CreateBar();

That'd be much simpler and decouple the shared library from the container. In general, that's the best advice I can give regarding Dependency Injection and reusable libraries: Use Dependency Injection patterns, not containers. See my article DI-Friendly Library for more details.

If you still want to use a DI Container in your host project (e.g. Wep API project), you can register IBar against that static factory method.

Internal classes

There are legitimate reasons why one would want to keep some classes internal, but I often see code bases where classes are internal for no apparent reason. My experience with decades of C# code is that the cases where classes are internal for no good reason far outweighs the cases where there's a good reason.

Does Bar really have to be internal? Why?

From the OP it's clear that IBar is implemented by Bar. This could be an artefact of reducing the question to essentials (good job doing that, BTW), but I get the impression that Bar is the only class that implements IBar. Is that the case?

If so, the Bar class must expose the methods defined by IBar. Thus, the API of the class is already public, so why hide the class? The only thing hidden, then, is the constructor...

The perfect storm

Sometimes when I give advice like the above, I'm met with a response that that's not what the question was about. The question is about how to change Castle Windsor lifestyles (which, BTW, you can).

This question, on the other hand, is almost the perfect (little) storm that illustrates why reusable libraries shouldn't depend on DI Containers:

  • The library should be usable in more than one context (web, console, desktop, etc.)
  • The library depends on Castle Windsor
  • Interfaces are public, but implementation classes aren't

This complicates things - hence the question here on Stack Overflow.

My best advice is to keep things simple. Removing the dependency on Castle Windsor will make library development and reuse easier, not harder.

It'd be even easier to use the library if you make most library classes public.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Great answer - we ended up canning the project that this was related to so didn't need to do anything so I have only just seen your answer. You're quite right with what you have said and I think reading your answer has cleared up some bits that I had't thought about before :) – 5NRF Jan 31 '20 at 14:55
  • "It'd be even easier to use the library if you make most library classes public" I think that this is great advice, our thoughts were that we didn't want people to create instances of the internal classes - we didn't want them to be able to interact directly with the DbContext for example (or even be aware of it) but the way that we were trying to hide it away was flawed - I see that now :) Thanks for the advice. – 5NRF Jan 31 '20 at 14:55