Browse DevX
Sign up for e-mail newsletters from DevX


Bricks and Mortar: Building a Castle : Page 3

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


Convention-over-Configuration means adhering to a set of application conventions rather than relying on explicit configuration. The idea was popularized in the Ruby community, specifically in Ruby on Rails, which uses the idea to great benefit.

To solve problem #1—having to maintain the container configuration as new classes/interfaces are added—you can use a convention-based approach and the AllTypes class to find all components within an assembly that match a particular criterion:

container.Register(    AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Namespace.StartsWith( "JamesKovacs.BricksAndMortar.Services")) .WithService .FirstInterface() );

After finding all the types, you then register the type's first interface as its service. For example, the above code would find the AccountsPayable component and register its first interface, IAccountsPayable, as the service it implements. If you ask the container to resolve IAccountsPayable, it will return an instance of the AccountsPayable class.

Author's Note: WARNING: .NET does not define a strict ordering when implementing interfaces. Although the returned first interface has remained stable since .NET 1.0, there are no guarantees in the future. Generally this isn't a problem because most components implement a single interface.

The Where clause is not required. You could easily make this call:

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .WithService .FirstInterface() );

But you generally don't want to register all types in your assembly. For example, you wouldn't want to register classes such as Customer, Order, Address, and such with your IoC container. (If you are familiar with Domain Driven Design, entities and value objects are generally not registered in your container. They are created and consumed by your domain logic.)

Rather than placing classes to be registered in a common namespace, you could use a naming convention. For example, if all your ASP.NET MVC controller classes were post-fixed with "Controller" (e.g. HomeController, CatalogController, ShoppingCartController, etc.), you could register them all like this:

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Name.EndsWith("Controller")) .WithService .FirstInterface() );

All Together Now

Using Convention-over-Configuration does not preclude the use of the Fluent API or XML configuration. You can actually combine any or all of them:

var container = new WindsorContainer(@".\Windsor.config"); container.Register(   Component.For<IReportGenerator>() .ImplementedBy<ReportGenerator>(),   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Name.EndsWith("Controller")) .WithService .FirstInterface() );

The order in which you configure the container (XML configuration, then Fluent API, and then Convention-over-Configuration) is important, because the first registered component for a service is the one resolved by default. So you need to place any "overrides" in the container first. In the example above, if IReportGenerator is defined in XML configuration, then the Fluent API registered component will not be used. You can change the registration order (i.e., perform XML configuration last) if desired.

You should also avoid registering components twice, because component keys must be unique. Later in the article, you'll see how to override the default component keys if necessary, but it's still good practice to avoid double-registering the same component—if only to minimize confusion while debugging.

Alternate Lifestyles

When you ask Windsor to resolve an implementation, the container determines whether an instance of that component has been created previously. If so, it returns this instance. So the following code always succeeds:

var repo1 = IoC.Resolve<IInvoiceRepository>(); var repo2 = IoC.Resolve<IInvoiceRepository>(); Assert.Same(repo1, repo2);

Thus you have a single instance of each component in the application. This is the default singleton lifestyle.

The concept of fluent API container configuration was borrowed from another popular IoC container, StructureMap.

Windsor, being the open-minded container that it is, provides a variety of lifestyle options. These include transient, per-thread, per-web-request, and pooled. You can also create your own custom lifestyles if none of the built-in ones satisfy your needs.

No Fixed Address

The transient lifestyle is often used for Presenters or ViewModels in Model-View-Presenter (MVP) or Model-View-ViewModel (MVVM) UI architectures. You do not want multiple views sharing the same Presenter/ViewModel because these objects orchestrate a specific view. In the case of MVP in classic ASP.NET Web Forms, the views (ASPX pages) are actually requests for different users. Configuring a transient lifestyle enforces an independent instance per view. (In the case of ASP.NET applications, per-web-request might be a more appropriate lifestyle.)

You specify your component's lifestyle when registering it in the container. Using the Fluent API, you could write:

container.Register(    Component .For<IAccountsPayablePresenter>() .ImplementedBy<AccountsPayablePresenter>() .LifeStyle.Transient );

Using the Convention-over-Configuration API, the code looks like this:

container.Register(   AllTypes .FromAssembly(Assembly.GetExecutingAssembly()) .Where(type=>type.Namespace.StartsWith( "JamesKovacs.BricksAndMortar.Presenters")) .WithService .FirstInterface() .Configure(c=>c.LifeStyle.Transient) );

You can then verify the transient lifestyle of the IAccountsPayablePresenter:

var p1 = IoC.Resolve<IAccountsPayablePresenter>(); var p2 = IoC.Resolve<IAccountsPayablePresenter>(); Assert.NotSame(p1, p2);

Notice that you specify the lifestyle on the component, not on the service. If you have multiple implementers of IAccountsPayablePresenter, the lifestyle is determined by the needs of AccountsPayablePresenter, not the interface that it is implementing.

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