Browse DevX
Sign up for e-mail newsletters from DevX


Bricks and Mortar: Building a Castle : Page 4

Discover how to use the Castle Windsor Inversion of Control container to build flexible applications.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Have I Got an Instance for You!

Sometimes, you don't have control over the instantiation of certain instances, but you would like those instances to participate in dependency resolution. Maybe you are integrating with a third-party framework or are refactoring an existing application to use an IoC container. You need to supply the container with existing instances. You can add instances to the container via the Fluent API:

var smtpServer = new SmtpServer("localhost", 25); container.Register(   Component.For<ISmtpServer>() .Instance(smtpServer) );

Author's Note: "localhost" and "25" would typically be read from App.config or Web.config rather than being hard-coded into the application.

Convention-over-Configuration means adhering to a set of application conventions rather than explicitly configuring an application.

You have taken responsibility for creating the SmtpServer instance, but you can pass the dependency to the controller so other components can utilize the ISmtpServer service provided by this component.

Value Types as Dependencies

Let's take another look at the SmtpServer. What if you want to allow the container to control its lifetime, but you need to provide the host and port as constructor arguments. You can provide dependent parameters to be used in construction in the form of an anonymous type or an IDictionary:

container.Register(   Component .For<ISmtpServer>() .ImplementedBy<SmtpServer>() .DependsOn( new { host = "localhost", port=25 }) );

If you are using Convention-over-Configuration, you can provide additional configuration data to a single component:

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Namespace.StartsWith( "JamesKovacs.BricksAndMortar.Services")) .WithService .FirstInterface() .ConfigureFor<ISmtpServer>( c=>c.DependsOn( new { host = "localhost", port=25 })) );

You can also provide additional configuration data for all components being configured:

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Namespace.StartsWith( "JamesKovacs.BricksAndMortar.Services")) .WithService .FirstInterface()     .Configure(c=>c.DependsOn( new { host = "localhost", port=25 })) );

Just as before, in the example that supplied constructor arguments during a call to Resolve<T>(), the constructor argument names and anonymous type (or IDictionary key) must match exactly. You should have tests in place to ensure that names match.

A Component by Any Other Name

If left unspecified (as in the examples above), a component is keyed by its full type name. So if you want to retrieve the IDbGateway instance by key, you would write:

var gateway = IoC.Resolve<IDbGateway>( "JamesKovacs.BricksAndMortar.Data.DbGateway");

Unfortunately, not only is this overly verbose, but trying to register two DbGateway instances with different connection strings, one for Corporate and one for Reporting, will fail with a duplicate key violation. (Keys in the container must be unique.) To work around this problem, you can explicitly specify the key for a component using the Named() method:

container.Register(   Component.For<IDbGateway>() .ImplementedBy<DbGateway>() .Named(DatabaseKey.Reporting) .DependsOn( new { connectionString = reportingConnectionString }), Component.For<IDbGateway>() .ImplementedBy<DbGateway>() .Named(DatabaseKey.Corporate) .DependsOn( new { connectionString = corporateConnectionString }) );

Blueprints for the Mason

Sometimes you need to provide the container with some blueprints for building an object. Usually, this is because you have registered multiple service implementations and a component needs a particular one. (Relying on Windsor to supply the first one is brittle and you should always specify the dependency's key if there are multiple components for the same service.)

Let's say that you have a ReportGenerator and it needs the IDbGateway to the Reporting database:

public class ReportGenerator : IReportGenerator { private readonly IDbGateway dbGateway;      public ReportGenerator(IDbGateway dbGateway) {     this.dbGateway = dbGateway;    } }

Without explicit configuration, if you ask the container for the component implementing IReportGenerator, you would get a ReportGenerator with whichever IDbGateway was registered first in the container. You need to explicitly tell the ReportGenerator to use the key for the IDbGateway via a ServiceOverride:

container.Register(   Component.For<IReportGenerator>() .ImplementedBy<ReportGenerator>()     .ServiceOverrides( ServiceOverride.ForKey("dbGateway") .Eq(DatabaseKey.Reporting)),   Component.For<IDbGateway>() .ImplementedBy<DbGateway>() .Named(DatabaseKey.Corporate) .DependsOn(new { connectionString = corpdb }),  Component.For<IDbGateway>() .ImplementedBy<DbGateway>() .Named(DatabaseKey.Reporting) .DependsOn(new { connectionString = rptdb }) );

The ServiceOverride key, dbGateway, is the name of ReportGenerator's constructor parameter, and the value is the key associated with the component that you want to use—in this case "DatabaseKey.Reporting." (Note: DatabaseKey.Reporting is a static class containing string constants.)

If you are using Convention-over-Configuration, you can perform the same configuration through Configure() or ConfigureFor<T>():

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Namespace.StartsWith( "JamesKovacs.BricksAndMortar.Services")) .WithService .FirstInterface() .ConfigureFor<IReportGenerator>( c=>c.ServiceOverrides( ServiceOverride.ForKey("dbGateway") .Eq(DatabaseKey.Reporting))) );

Thanks for your registration, follow us on our social networks to keep up-to-date