Registering components one-by-one can be very repetitive job. Also remembering to register each new type you add can quickly lead to frustration. Fortunately, in majority of cases, you don't have to do it, nor should you. By using Classes
or Types
entry classes you can perform group registration of types based on some specified characteristics you specify. This is the way you will find using most often when writing applications with Windsor.
Registration of multiple types usually takes roughly the following form:
container.Register(Classes.FromThisAssembly()
.InSameNamespaceAs<RootComponent>()
.WithService.DefaultInterfaces()
.LifestyleTransient());
You can identify three distinct steps in the registration call.
First step is to point Windsor to the assembly (or assemblies) it should scan. You do it by using:
Classes.FromThisAssembly()...
or one of its sister methods.
ℹ️ Should I use Classes
or Types
?: There are two ways to start registration by convention. One of them is using Classes
static class, like in the example above. Second is using Types
static class. They both expose exactly the same methods. The difference between them is, that Types
will allow you to register all (or to be precise, if you use default settings, all public ) types from given assembly, that is classes, interfaces, structs, delegates, and enums. Classes
on the other hand pre-filters the types to only consider non-abstract classes. Most of the time Classes
is what you will use, but Types
can be very useful in some advanced scenarios, like registration of interface based typed factories.
AllTypes
?: Both Classes
and Types
are new in Windsor 3. Previous versions had just one type to do the job - AllTypes
. In Windsor 3, usage of AllTypes
is discouraged, because its name is misleading. While it suggests that it behaves like Types
, truth is it's exactly the same as Classes
, pre-filtering all types to just non-abstract classes. To avoid confusion, use one of the two new types.
Once you selected an assembly you do first base step of filtering the types you want to register. This will narrow the scope of the objects in one of the following ways:
- By base type/implemented interface, for example:
Classes.FromThisAssembly().BasedOn<IMessage>()
- By namespace, for example:
- ℹ️ This method (and its overloads) are new in Windsor 3
Classes.FromAssemblyInDirectory(new AssemblyFilter("bin")).InNamespace("Acme.Crm.Extensions")
- By any condtion, for example:
Classes.FromAssemblyContaining<MyController>().Where( t=> Attribute.IsDefined(t, typeof(CacheAttribute)))
- No restrictions at this point:
Classes.FromAssemblyNamed("Acme.Crm.Services").Pick()
Once you selected source for the types and base condition you can also configure the types or additionally filter some of them out. All the details of the API you can use at this point are discussed below.
BasedOn
, Where
and Pick
do logical or
: Be wary when using more than one of BasedOn
, Where
and Pick
at once. The following:
container.Register(
Classes.FromThisAssembly()
.BasedOn<IMessage>()
.BasedOn(typeof(IMessageHandler<>)).WithService.Base()
.Where(Component.IsInNamespace("Acme.Crm.MessageDTOs"))
);
Will register all messages and also all message handlers and also all message DTOs. This is usually not the behavior you want, and to avoid confusion in Windsor 3 this call chaining will give you a compiler warning. Be explicit and register those three sets of components in three separate calls.
Here's an example from a Monorail configuration:
container.Register(
Classes.FromThisAssembly()
.BasedOn<SmartDispatcherController>()
.Configure(c => c.Lifestyle.Transient)
);
We are registering all types which implement SmartDispatcherController
from the executing assembly. This is a quick way of adding all your controllers to the container. It will also automatically register all new controllers as you add them to your application.
Keep in mind that Of
as well as Pick
and other filtering methods only narrow down the set of types we want to register. They do not specify the service that these types provide and unless you specify one the default will be used, that is the implementation type itself. In other words, the above registration will register all types inheriting from SmartDispatcherController
with service being their own type, not the SmartDispatcherController
so trying to call
var controller = container.Resolve<SmartDispatcherController>();
will throw an exception.
In this default case types may be requested only by their implementation type (the default service).
var controller = container.Resolve<MyHomeController>();
By default the service of the component is the type itself. There are several situations in which this is not sufficient. Windsor lets you specify the service type explicitly.
container.Register(
Classes.FromThisAssembly()
.BasedOn(typeof(ICommand<>)).WithService.Base(),
Classes.FromThisAssembly()
.BasedOn(typeof(IValidator<>)).WithService.Base()
);
Here we register all types implementing closed version of ICommand<>
and IValidator<>
, respectively (Now, read it again, slowly). To give you an example - when we request ICommand<AddCustomer>
we'll receive an instance of type implementing ICommand<AddCustomer>
, most likely named something like AddCustomerCommand
.
The most important aspect of this specification, and often most confusing, is the presence of the WithService.Base()
. This tells the registration strategy to choose the closed version of the interface specification as the service type. In this case, something like IValidator<Customer>
might be selected. Without it, the type would be registered against the open version of the interface and you would most likely not get the desired resolution capability.
ℹ️ This method was renamed in Windsor 3 from DefaultInterface
to emphasize the fact that it can match more than just one service for a component.
container.Register(
Classes.FromThisAssembly()
.InNamespace("Acme.Crm.Services")
.WithService.DefaultInterfaces()
);
This method performs matching based on type name and interface's name. Often you'll find that you have interface/implementation pairs like this: ICustomerRepository
/CustomerRepository
, IMessageSender
/SmsMessageSender
, INotificationService
/DefaultNotificationService
. This is scenario where you might want to use DefaultInterfaces
method to match your services. It will look at all the interfaces implemented by selected types, and use as type's services these that have matching names. Matching names, means that the implementing class contains in its name the name of the interface (without the I on the front).
Another very common scenario is having the ability to register all types that share a common interface, but are otherwise unrelated.
container.Register(
Classes.FromThisAssembly()
.BasedOn<IService>().WithService.FromInterface()
);
Here we register service classes from the executing assembly. In this case, the IService
interface might be a marker interface identifying the role of a component in the system. Unlike WithService.Base()
, the service type selected for this registration is chosen from the interface that extended IService
. The following is an example to help illustrate this.
Lets say you have a marker interface IService
to designate all services in your assembly.
public interface IService {}
public interface ICalculatorService : IService
{
float Add(float op1, float op2);
}
public class CalculatorService : ICalculatorService
{
public float Add(float op1, float op2)
{
return op1 + op2;
}
}
The above registration would be equivalent to
container.Register(
Component.For<ICalculatorService>().ImplementedBy<CalculatorService>()
);
As you can see, the actual service interface is NOT IService
, but rather the interface extending IService
, which is ICalculatorService
in this case.
When a component implements multiple interfaces and you want to use it as a service for all of them, use WithService.AllInterfaces()
method.
To register the component implementation type explicitly as a service use WithService.Self()
If none of the above options suits you you can provide your own selection logic as a delegate and pass it to WithService.Select()
method.
ℹ️ Services are cumulative: Multiple calls to WithService.Something()
are allowed and they are cumulative. That means that if you call:
Classes.FromThisAssembly()
.BasedOn<IFoo>()
.WithService.Self()
.WithService.Base()
the types matched will be registered as both IFoo, and themselves. In other words the above would be equivalent to doing the following for each type implementing IFoo
Component.For<IFoo, FooImpl>().ImplementedBy<FooImpl>();
By default only types visible from outside of the assembly will be registered. If you want to include non-public types, you have to start with specifying assembly first, and then call IncludeNonPublicTypes
container.Register(
Classes.FromThisAssembly()
.IncludeNonPublicTypes()
.BasedOn<NonPublicComponent>()
);
When you register your types you can also configure them to set all the same properties as when registering types one by one. For this, you use configure method. The most common case is to assign a lifestyle to your components other than the default Singleton.
container.Register(
Classes.FromAssembly(Assembly.GetExecutingAssembly())
.BasedOn<ICommon>()
.Configure(component => component.LifestyleTransient())
);
This scenario is so common that Windsor 3 provides a shortcut to it
ℹ️ Below method and its sister methods are new in Windsor 3
container.Register(
Classes.FromAssembly(Assembly.GetExecutingAssembly())
.BasedOn<ICommon>()
.LifestyleTransient()
);
In addition to (or instead of) specifying the lifestyle you can set many other configuration options:
container.Register(
Classes.FromAssembly(Assembly.GetExecutingAssembly())
.BasedOn<ICommon>()
.LifestyleTransient()
.Configure(component => component.Named(component.Implementation.FullName + "XYZ"))
);
In here we register classes implementing ICommon
, set their lifestyle and name.
You can do also a more fine grained configuration, setting some additional properties for a subset of your components:
container.Register(
Classes.FromThisAssembly()
.BasedOn<ICommon>()
.LifestyleTransient()
.Configure(
component => component.Named(component.Implementation.FullName + "XYZ")
)
.ConfigureFor<CommonImpl1>(
component => component.DependsOn(Property.ForKey("key1").Eq(1))
)
.ConfigureFor<CommonImpl2>(
component => component.DependsOn(Property.ForKey("key2").Eq(2))
)
);
In here, we do the same thing as above, but in addition for types implementing two other interfaces we set additional inline dependencies.
If you want to configure a set of components that do not have a common base class or interface you can do so with ConfigureIf
container.Register(
Classes.FromThisAssembly()
.BasedOn<ICommon>()
.LifestyleTransient()
.Configure(
component => component.Named(component.Implementation.FullName + "XYZ")
)
.ConfigureIf(
x => x.Implementation.Name.StarsWith("Common"),
component => component.DependsOn(Property.ForKey("key1").Eq(1))
)
);
In here, we do something similar to the above except based on component's implementation type's name.
ℹ️ See the "Conditional component registration" section below for a discussion on better filtering the types you register.