This post is an extension of an earlier post on testing container registrations. In this post I cover testing again as well as how I manage component registrations to reduce the amount of xml needed and how I automate component registration as much as possible.
First up, the scenario within which I am using a container:
- ASP.NET MVC application
- controllers managed via the WindsorControllerFactory from MVCContrib.
- actions have only 1 parameter and that type is an interface – the container manages the parameter types via a custom model binder.
- Parameters are 1 of 3 types:
- Commands – do something to the domain and return a result that cannot cause side-effects ( ideally immutable but you cannot use structs in views ).
- Queries – retrieve data from the system and pack it into a return type that is effectively a view model.
- UrlActions – some actions need both commands and queries and must share request state ( form data, query parameters or route parameters ) so until I use special classes that combine commands and queries for this.
- A custom validation approach that is similar to FluentValidation
- NHibernate + NHibernateIntegrationFacility + FluentNHibernate
Semi-automatic Component Registration
Using base or marker interfaces allows you to define a few bulk registrations, rather than a large number of fine-grained registrations. The design of the application outlined above provides four places where I usually add features into the sytem. Each one of these extension points is identified by a base interface, this makes it easier to find and automatically register the associated components. The extension points are:
- Commands – ICommand<T>
- Queries – IQuery<T>
- UrlActions – IUrlAction
- Validators – IValidator<T>
To add a feature ( e.g. logon ), I usually implement a query, a command and two validators. All of these components are covered by bulk registrations, therefore I don’t have to “remember” to register them in the container before using them – it happens automatically.
public static class ComponentRegistrations
{
public static readonly Assembly CommandsAssembly = typeof( RegistrationCommand ).Assembly;
public static readonly IRegistration Commands =
AllTypes.Of( typeof( ICommand<> ) )
.FromAssembly( CommandsAssembly )
.WithService.Select((t, b) => new List<Type> {t.FindMostSpecificChildInterfaceOf( b )} )
.Unless( t => t.IsAbstract || t.IsInterface )
.Configure( c => c.LifeStyle.Transient );
}
Simple Component Registration Testing
Placing all of the registrations in a class like ComponentRegistrations greatly simplifies unit testing. I use a helper class that converts the single registration defined by AllTypes.Of(...) into individual registrations.
When testing registrations, you only need to test the “extra bits”. In the above registration that equates to the .WithService.Select(...) bit. The test looks like this:
[Specification]
public class When_registering_commands
{
private CompontentRegistrationsTestContext _context;
public void Given(CompontentRegistrationsTestContext context)
{
_context = context;
}
public void When()
{
_context.WhenRegisteringWith( ComponentRegistrations.Commands );
}
[Then]
public void the_service_should_be_defined_by_the_most_specific_interface_inheriting_from_ICommand()
{
_context.AllRegistrations(
r => r.ServiceType.ShouldEqual(
r.Implementation.FindMostSpecificChildInterfaceOf( typeof(ICommand<>)) ) );
}
}
The context class that converts the registrations uses an IKernel stub to convert bulk registrations like BasedOnDescriptor into ComponentRegistration instances.
public class CompontentRegistrationsTestContext
{
private List<ComponentRegistration> _registrations;
public void WhenRegisteringWith( IRegistration registration )
{
_registrations = new List<ComponentRegistration>();
var componentRegistration = registration as ComponentRegistration;
if( componentRegistration != null )
{
// We want to capture the component registrations, not execute them
_registrations.Add( componentRegistration );
return;
}
// If they are not component registrations, they will be descriptors that
// generate numerous registrations ( i.e. BasedOnDescriptor )
var kernel = MockRepository.GenerateStub<IKernel>();
kernel.Stub( stub => stub.Register( null ) )
.IgnoreArguments()
.Callback( new RegisterDelegate( CaptureRegistrations ) )
.Return( kernel );
registration.Register( kernel );
}
public void AllRegistrations( Action<ComponentRegistration> assertion )
{
_registrations.Do( assertion );
}
public bool ContainsRegistration( Func<ComponentRegistration, bool> matchCriteria )
{
return _registrations.Any( matchCriteria );
}
private delegate bool RegisterDelegate(IRegistration[] registrations);
private bool CaptureRegistrations(IRegistration[] registrations)
{
_registrations.AddRange(registrations
.Select(r => (ComponentRegistration)r));
return true;
}
}
Simplify Complex Component Registrations
Where I need more complex registrations that need to work with the configuration, I write a custom IRegistration implementation. The following example registers a component to intercept and redirect emails being sent from the website. Usign a custom registration means that I can add all of the configuration required by components to the facility configuration in the simplest possible form, yet still use parameters for configuring components.
public class RedirectingEmailSenderRegistration : IRegistration
{
public const string RedirectingEmailSenderKey = "redirecting.emailsender";
public const string RedirectConfigurationChildKey = "redirectEmails";
public const string RedirectConfigurationToAddressKey = "to";
public const string RedirectConfigurationSkipSendingKey = "skipSending";
private readonly IConfiguration _configuration;
public RedirectingEmailSenderRegistration( IConfiguration facilityConfiguration )
{
_configuration = facilityConfiguration.Children[RedirectConfigurationChildKey];
}
public void Register(IKernel kernel)
{
kernel.ConfigurationStore.AddComponentConfiguration(
RedirectingEmailSenderKey,
BuildRedirectingEmailSenderConfiguration() );
kernel.AddComponent(
RedirectingEmailSenderKey,
typeof( IEmailSender ),
typeof( RedirectingEmailSender ),
LifestyleType.Singleton );
}
private IConfiguration BuildRedirectingEmailSenderConfiguration()
{
if( _configuration == null )
{
throw new ConfigurationErrorsException(
"Missing configuration for the redirecting email sender. Please ensure the following element is present and correct: <redirectEmails to=\"redirect address here\" />" );
}
var redirectAddress = _configuration.Attributes[ RedirectConfigurationToAddressKey ];
if( String.IsNullOrEmpty( redirectAddress ) )
{
throw new ConfigurationErrorsException(
"Missing configuration for the redirecting email address sender. Please ensure the to attribute is populated with a valid email address" );
}
var skipSending = _configuration.Attributes[ RedirectConfigurationSkipSendingKey ];
if( String.IsNullOrEmpty( skipSending ))
{
skipSending = Boolean.FalseString;
}
var configuration = new MutableConfiguration( "component" );
configuration.Attribute( "id", RedirectingEmailSenderKey )
.CreateChild( "parameters" )
.AddChild( "redirectTo", redirectAddress )
.AddChild( "skipSending", skipSending );
return configuration;
}
}
An additional benefit of this is that you can inspect the result of the registration to ensure it works the way you expect it to.
[Specification]
public class When_registering_the_redirecting_email_sender
{
private RedirectingEmailSenderRegistration _registration;
private IKernel _kernel;
private IConfigurationStore _configurationStore;
public void Given(DevelopmentContainerConfigurationContext context)
{
_registration = new RedirectingEmailSenderRegistration( context.BuildFacilityConfiguration() );
_kernel = MockRepository.GenerateStub<IKernel>();
_configurationStore = MockRepository.GenerateStub<IConfigurationStore>();
_kernel.ConfigurationStore = _configurationStore;
}
public void When()
{
_registration.Register(_kernel);
}
[Then]
public void the_redirecting_email_sender_is_configured()
{
_configurationStore.AssertWasCalled(
cs => cs.AddComponentConfiguration( null, null ),
c => c.IgnoreArguments()
.Constraints(
Is.Equal( RedirectingEmailSenderRegistration.RedirectingEmailSenderKey ),
Is.Anything() ) );
}
[Then]
public void the_redirecting_email_sender_is_registered()
{
_kernel.AssertWasCalled(
k => k.AddComponent(
RedirectingEmailSenderRegistration.RedirectingEmailSenderKey,
typeof( IEmailSender ),
typeof( RedirectingEmailSender ),
LifestyleType.Singleton) );
}
}
Simplify Management of Component Registrations
Generally when I am developing a web application, I have two sets of components that I need to manage, one set for production – the components that make up the actual application, and a second set for development that perform tasks like redirecting the email from the application into a specific email address.
Rather than having to remember which components belong to what set, I create two facilities to register the components, the first is <ApplicationName>ConfigurationFacility and the second is DevelopmentConfigurationFacility. Because all the registrations can be accessed via static members of the ComponentRegistrations class I can create a simple facility as shown below. This means it is easy to see what components are being registered in the container and in what order ( you can also register facilities in the same way ):
public class DevelopmentConfigurationFacility : AbstractFacility
{
protected override void Init()
{
Kernel.Register( DevelopmentComponentRegistrations.DatabaseController );
Kernel.Register( DevelopmentComponentRegistrations.RedirectingEmailSender( FacilityConfig ) );
Kernel.Register( DevelopmentComponentRegistrations.NHibernateProfilerInitialisingConfigurationBuilder );
Kernel.Register( DevelopmentComponentRegistrations.EnableStatisticsPersistenceConfigurer );
Kernel.Register( DevelopmentComponentRegistrations.ShowSqlPersistenceConfigurer );
}
}
By using facilities to group component registrations, I can effectively “bootstrap” the container into one of two modes ( development or production ) by adding or removing one facility registration ( instead of 5 component registrations + the associated component configuration ).
Careful thought about the strategies you use to manage and test your component registrations may cost you some time up front, but it is time well invested as once it is up and running, you should end up with a zero-friction environment wrt components.
If it just works and you don’t notice when it does – you’ve done it right