Export (0) Print
Expand All

Inversion of Control and Dependency Injection: Working with Windsor Container

 

Oren Eini

November 2006

Applies to:
   Visual Studio 2005 Team Edition for Software Architects
   .NET Framework
   Inversion of Control (IoC)
   Dependency Injection (DI)
   Windsor Container

Summary: This article discusses how to use Inversion of Control and Dependency Injection, generic specialization, the decorator pattern, chains of responsibilities, and extensible software to build robust, extensible, working software. (16 printed pages)

Contents

How to Build Better Software
The First Feature: Dispatching an Order
Looking at a More Complex Example

How to Build Better Software

Close your eyes, take a deep breath, click your heels three times, and say, "There's no better thing than Inversion of Control and Dependency Injection, generic specialization, the decorator pattern, chains of responsibilities, and extensible software." (There is more on the decorator pattern here.)

This article will discuss using all of the aforementioned to build robust, extensible, working software.

One of the biggest issues in software systems today is managing the dependencies between objects. If my ProcessOrdersService class is using the OrdersDAL and CustomersDAL classes, it has dependencies on them and, through them, each of their dependencies.

Unmanaged, those dependencies can get out of control without you even noticing. If you have ever changed a constructor signature and realized that you have just broken your code in 19 places, or ever tried to instantiate an object only to find that it needs the environment just so because of a dependency three levels down, you know the pain that I am describing.

Inversion of Control and Dependency Injection are two related ways to break apart dependencies in your applications. They are explained in detail by Martin Fowler in his article Inversion of Control Containers and the Dependency Injection Pattern, but a few lines are in order.

Inversion of Control (IoC) means that objects do not create other objects on which they rely to do their work. Instead, they get the objects that they need from an outside source (for example, an xml configuration file).

Dependency Injection (DI) means that this is done without the object intervention, usually by a framework component that passes constructor parameters and set properties.

To explain these concepts, I'm going to use Windsor (an IoC container), but the main point of this article is not to teach you how to use Windsor, but instead to show how you can build great software.

If you want to learn all about Windsor itself, you can go to its Web site.

The example that I am going to use is an online shop. We are going to build the architecture of the shop from scratch, using Windsor as our IoC container. Figure 1 shows the simplified model of the shop.

Aa973811.ioc01(en-us,MSDN.10).gif

Figure 1. Simplified model of online shop, with entities and services

What Is the Difference Between an Entity and a Service?

EntityService
An object that has a unique identity in the domain model of the applicationAn object that performs a distinct part of the application functionality

Here is the basic model of the application, divided by entities, services interfaces, and services implementation. (For now, I am going to ignore the user-interface aspects. Those are usually handled by a Model View Controller framework in these kinds of applications.)

A couple of implementation notes, before we start:

What are we implementing?How we are going to implement this?
Overall designThe overall design is based on domain-driven design (entities, services, repositories), but not fully domain-driven design.
Inversion of Control and Dependency InjectionInversion of Control and Dependency Injection are a core part of the architecture (in this case, Windsor Container).
Data accessWe will be using the unit-of-work pattern together with business objects. The implementation is mostly based on NHibernate.

The Domain Model

Aa973811.ioc02(en-us,MSDN.10).gif

Figure 2. Sample domain model

Figure 2 represents the domain model that I am using for this article. You have probably seen similar object structures many times before; there is nothing new about them, but what might be new to you is the notion of repositories.

A repository is our gateway to dealing with object persistence. Repositories play a central role in domain-driven design. If you are not familiar with them, you can consider them to be strongly typed classes that provide create/read/update/delete for objects.

Figure 3 shows the resulting design.

Aa973811.ioc03(en-us,MSDN.10).gif

Figure 3. Resulting design, with repositories

Implementation note   Because the application is running on the 2.0 version of the .NET Framework, I made an extensive use of generics in order to reduce the amount of code that we must write.

The First Feature: Dispatching an Order

Saving an order should trigger a process that will dispatch it, if it is set to complete, and this is the feature that we are going to implement. Luckily, the code itself is very simple.

First, we create a specialized version of NHibernateRepository<T>, which will handle CRUD functionality for orders objects only (this is, in fact, the reason that we created the OrderRepository in the first place, of course).

Here is the code that implements this functionality.

1   public class OrderRepository : NHibernateRepository<Order>
2   {
3       IOrderDispatcher dispatcher;
4       //constructor implementation...
5       public override void Save(Order item)
6       {
7           if (item.IsComplete)
8              dispatcher.Dispatch(item);
9           base.Save(item);
10       }
11   }

The code itself is not a problem, but there are a couple of things that are not shown in the previous code.

Shall we take a look at the client code that is required to send an order?

1   Order order = new Order(customer, shippingAddress,
billingAddress,ShippingMethod.UPS);//new order
2   //new order line
3   OrderLine line = new OrderLine(order, msdnSubscription, 1);
4   order.OrderLines.Add(line);//add order line to order
5   //create a session provider, has the current database session
6   ISessionProvider sessionProvider = new SessionProvider();
7   //handles dispatching of the order to other systems
8   IOrdersDispatcher ordersDispatcher = new OrdersDispatcher();
9   //order repository has specialized behavior that execute when
10   //saving an order
11   OrderRepository orderRepository = new OrderRepository(
sessionProvider, ordersDispatcher);
12   //save the order to the database
13   orderRepository.Save(order);

As seen in the aforementioned code, we have four lines that have some business value (1–3, 12), create an order and an order line, add the order line to the order, and finally save the new order. But there are three lines that exist, because we must satisfy the OrderRepository constructor's dependencies.

Moreover, those dependencies are hard-coded into the application.

Let us consider another similar scenario, in which we need special functionality when saving a customer. The code that we have at the moment looks somewhat like the following.

1   //create a session provide, provide the current database session
2   ISessionProvider sessionProvider = new SessionProvider();
3   //a generic repository that handles saving the entity to 
4   // the database
5   NHibernateRepository<Customer> customerRepository = 
6   new NHibernateRepository<Customer>(sessionProvider);
7   //save the customer to the database
8   customerRepository.Save(customer);

We start by creating the Session Provider dependency again. However, if we want to create a CustomerRepository to handle that specialized behavior (for instance, to send an e-mail to the customer with the new details), we would have to locate all of the places in which we created a CustomerRepository, and change it.

Remember, this is a relatively simple scenario, in which the dependency depth is very low. In real-world scenarios, the situation is much worse.

Beyond the rigidity of the code that results from this approach, there is also the problem of having to build the dependency graph correctly every time that we want to use those services.

Dependency Graphs

First, what is a dependency?

When we use another object to perform a certain task, we are creating a dependency on that object. That object might use other objects, too, so you are implicitly depending on those other objects, and they too might depend on other objects to do their work, and so on.

Figure 4 shows the dependency graph for the first example (ignoring the dependencies of Order Dispatcher and Session Provider, of course).

Aa973811.ioc04(en-us,MSDN.10).gif

Figure 4. Dependency graph for first example

Dependency graphs are a problem that is often not noticed until you have to change something in the application.

In the previous examples, we separated the functionality of the services in the application into independent pieces, which required us to pass the additional services for OrderRepository explicitly.

We could have done it another way, by having OrderRepository create the Order Dispatcher and Session Provider in its constructor, and have them create their dependencies in their constructors.

On the face of it, this looks like a good way to separate the problem into manageable pieces. The problem with this approach is that your objects are now strongly coupled to one another; we cannot make a change to one without affecting the other. For instance, suppose that we decided to change the Session Provider implementation to accept a new parameter in its constructor. Because we are creating it in place, we now have to change it in any place we use it. When you must make a change in the system, it is not going to be pleasant or easy.

The classic example for this is creating a SqlConnection in order to pull some data from the database. All is fine and good, until you get a new feature request "Work Against Oracle". Then, you realize that you are not in for a fun ride.

But manually handling all of those dependencies any time that I want to use the application's services is just too hard. It is also not very flexible. Changing the dependency graph in any way (adding or removing a constructor parameter, for example) is a change that is propagated through the code base. This isn't a good way to make friends in this business.

Using Windsor

Let us see what we can do to mitigate this problem. We will use Windsor Container to provide Inversion of Control and Dependency Injection, and let the container handle the dependency graph for us.

A container is a lightweight object that assembles different components in the system, in order to produce a cohesive and useful service. You can read more about this in Fowler's article on Inversion of Control.

Figure 5 demonstrates a high-level class diagram.

Aa973811.ioc05(en-us,MSDN.10).gif

Figure 5. Sample high-level class diagram

We will start with a simple configuration sample.

<configuration>
  <components>
    <component id="orders-repository" 
      service="NShop.OrderRepository, NShop"
      type="NShop.OrderRepository, NShop"/>
    <component id="orders-dispatcher" 
      service="NShop.IOrdersDispatcher, NShop"
      type="NShop.OrdersDispatcher, NShop"/>
  </components>
</configuration>

Windsor Configuration in a Paragraph

Each component is composed of a unique name, a service, and a type. Both service and type are fully qualified .NET-type names. The service is the type that exposes the service to the outside world (usually, an interface), and the type is the implementation of the service (usually, a type that implements the interface).

This configuration simply registers two components with the container: the OrderRepository and the OrdersDispatcher.

We can now use it as follows.

IWindsorContainer container = new WindsorContainer(
   new XmlInterpreter(@"..\..\Windsor.Config.xml"));
OrderRepository orderRepository = container.Resolve<OrderRepository>();

How It Works

Windsor has inspected the registered components and found what their required dependencies are. It is checking both constructor parameters and settable properties, and tries to satisfy as many of them as possible.

The configuration specifies what the registered components are, so that it knows what dependencies it can satisfy.

What Just Happened?

Windsor realized that OrderRepository has a dependency on IOrdersDispatcher, and it provided it with the type that we registered. (Covering Windsor in depth is beyond the scope of this article, but you can find all about it here.)

That was simple, wasn't it? It certainly saves some coding, but we still have to know all of the dependencies up front. Let us see how we can leverage Windsor to get more interesting results.

Just to remind you, the following is how we would create this without using Windsor.

1   ISessionProvider sessionProvider = new SessionProvider();
2   IOrdersDispatcher ordersDispatcher = new OrdersDispatcher();
3   OrderRepository orderRepository = new OrderRepository(
sessionProvider, ordersDispatcher);

Generic Specialization

In C++, one of the more powerful tools in the programmer's toolbox is template specialization. In .NET, this is slightly harder to achieve, but just as powerful when used.

In this case, we have a NHibernateRepository<T>, and a specialized version of that for Orders. I don't want to be aware of this difference between Order and the rest of the entities in the model.

What I would like to do is always to work against IRepository<T>, and have someone else figure out what I need. The C# compiler can't do this for me, but Windsor can. In fact, by using Windsor, we can get fine-grain control to do some amazing stuff (I'll talk about that later).

Let us look at our first example, and see how we are saving an order when we are using Windsor, instead of doing it all by hand.

1   WindsorContainer container =
      new WindsorContainer(
            new XmlInterpreter("Windsor.Config.xml") );
2   Order order = new Order(customer, shippingAddress,
billingAddress,ShippingMethod.UPS);
3   OrderLine line = new OrderLine(order, msdnSubscription, 1);
order.OrderLines.Add(line);
4   IRepository<Order>orderRepository =
            container.Resolve<IRepository<Order>>();
5   orderRepository.Save(order);

What do we have now? We create the container, create the order, and then ask for an IRepository<Order>. Windsor resolves this for us and gives us an OrderRepository, which is done without the client-code involvement.

If I would ever need to have a specialized version of the repository to save customers, I would reconfigure Windsor, but the client code will not have to change. After all, we don't know that we have a specialized version of the repository for orders.

Now our code is much more focused on what we are doing, instead of on the particularities of how we are doing it. In the application, the container will not be created whenever we need, of course. It is created at the application startup, and then it is accessible for everything that requires it.

Here is the configuration that is required to make this happen.

<configuration>
  <components>
    <component id="orders-repository"
               service="NShop.IRepository`1[[NShop.Order, NShop]], NShop"
               type="NShop.OrderRepository, NShop"/>
    <component id="generic-repository" service="NShop.IRepository`1, NShop"
               type="NShop.NHibernateRepository`1, NShop"/>
    <component id="orders-dispatcher" type="NShop.OrdersDispatcher, NShop"/>
  </components>
</configuration>

What Are All Those Brackets For?

So, you noticed that weird-looking service type, as seen here.

NShop.IRepository`1[[NShop.Order, NShop]], NShop

This is the standard CLR way to refer to a closed generic type. In C#, we would write this as follows.

IRepository<Order>

The CLR way is a bit more verbose, but it has to be this way in order to make it completely unambiguous.

When we define the configuration for Windsor, we specify that a request for IRepository<Order> be mapped to OrderRepository. The service attribute in the order-repository component specifies the full generic name.

We also specify a generic-repository that maps to IRepository<T>, which means that any type that we passed will be matched, and a NHibernateRepository<T> will be returned, unless the type was specialized (like we did for Order).

Example   We request IRepository<Customer>, Windsor sees that there is no service registered for IRepository<Customer>, but there is one for IRepository<T>. It then makes use of this information to resolve the request to NHibernateRepository<Customer>, because that was defined for the general case.

Inversion of Control and Generics

There are four important characteristics for IoC integration with generics. They:

  1. Can create an object using a generic method (Create<T>() or similar).
  2. Map to and from full generic-type names (NShop.NHibernateRepository`1[[NShop.Customer, NShop]], NShop).
  3. Can infer generic-type parameter information from partial generic name (NShop.NHibernateRepository`1, NShop) when requested.
  4. Can satisfy generic dependencies (constructor or properties that are generic types themselves).

We have already seen that Windsor has the Resolve<T>() generic method that can be used to create objects. We explicitly mapped IRepository<Order> to OrderRepository, and saw that it can infer the generic-type arguments when we create a NHibernateRepository<Customer> when we request IRepository<Customer>, even though the configuration only specifies the partial generic-type name.

The final requirement might seem odd to you. I mean, why would you want to have a generic constructor argument (or generic property) in a generic type?

The simple answer is to allow the following code. (There is more on the List<T> constructor in the MSDN Library.)

1   public class List<T>
2   {
3      public List(IEnumerable<T>enumerable)
4      {
5      }
6   }

The more interesting answer is that it opens up several very interesting possibilities, which we will explore immediately.

Generic Decorators Chains

When accessing a database, there are a few things that must happen beyond just opening a connection and reading data.

Usually, you'll want to add security, logging, and caching when you access the database. Because I view all of those things as separate concerns, I like to separate them into separate classes that can be combined independently.

First, let us look at the class diagram, as shown in Figure 6.

Click here for larger image

Figure 6. Class diagram (Click on the picture for a larger image)

As you can see, there are quite a few dependencies for the classes. Those marked as fields are constructor dependencies (mandatory), and those marked as properties are setter dependencies (optional).

At the end, I want to be able to do the following.

1   IRepository<Order>orderRepository =
container.Resolve<IRepository<Order>>();
2   Order order = orderRepository.Get(35);

And get security validation; get the order from cache, if it is there; log what is happening, so that I can track it later; and finally get it from the database, if I have to.

In order to do this, I'm going to compose a decorators chain in which each part of the chain is responsible for a single action in the chain.

Let us see what we would have to do in order to make this happen manually.

1   IRepository<Order>repository =
   new ValidatingOrderRepository(
    new SecurityRepository<Order>(
      new LoggingRepository<Order>(
       new CachingRepository<Order>(
        new NHibernateRepository<Order>()))));
2   Order order = repository.Get(35);

Figure 7 shows what the call chain to the repository will look like.

Click here for larger image

Figure 7. Diagram of call chain to repository (Click on the picture for a larger image)

A call is made, validated, checked for authorization, logged, and checked on the cache, and finally the database is queried.

I have shown two possible ways to make it happen: by using Windsor and manually. Doing it manually is painful. We must explicitly state exactly what we want, and we must do this in detail. Notice that not all of the repositories along the way are generic types; there is the Validating Order Repository that is validating the order according to the business rules. We give that piece of code a lot more functionality than it deserves, I tend to believe.

The impressive thing about using Windsor to create this object graph is that we get clean code, where none of the parts knows anything about any of the other parts. This allows great flexibility in how you build the application.

Now, let us see how we can make this happen. We must configure Windsor as follows.

<configuration>
  <components>
    <component id="orders-dispatcher"
               service="NShop.IOrdersDispatcher, NShop"
               type="NShop.OrdersDispatcher, NShop"/>
    <component id="cache"
               service="NShop.ICache`1, NShop"
               type="NShop.Cache`1, NShop"/>
    <component id="security"
               service="NShop.ISecurityInformation, NShop"
               type="NShop.SecurityInformation, NShop"/>
    <component id="configuration"
               service="NShop.IConfiguration, NShop"
               type="NShop.Configuration, NShop"/>
    <component id="session-provider"
               service="NShop.ISessionProvider, NShop"
              type="NShop.SessionProvider, NShop"/>
  </components>
</configuration>

Those are simple definitions, simply exposing those services to the world. Now that we have defined that, let us see what goes into the repository configurations.

<!-- generic repository defination -->

Using the logging decorator

<component id="security-decorator"
           service="NShop.IRepository`1, NShop"
           type="NShop.SecurityDecorator`1, NShop">
  <parameters>
    <inner>${logging-decorator}</inner>
  </parameters>
</component>

Using the caching decorator

<component id="logging-decorator"
           service="NShop.IRepository`1, NShop"
           type="NShop.LoggingDecorator`1, NShop">
  <parameters>
    <inner>${caching-decorator}</inner>
  </parameters>
</component>

Using the real repository

<component id="caching-decorator"
           service="NShop.IRepository`1, NShop"
           type="NShop.CachingDecorator`1, NShop">
  <parameters>
    <inner>${generic-repository}</inner>
  </parameters>
</component>

Handling the real database interactions

<component id="generic-repository"
           service="NShop.IRepository`1, NShop"
           type="NShop.NHibernateRepository`1, NShop"/>
<!-- end generic repository defination -->

What is going on here? Well, we define a bunch of repositories and the relations between them. In this case, we have what is shown in Figure 8.

Click here for larger image

Figure 8. Configuration of repositories and their relations (Click on the picture for a larger image)

There are additional dependencies that are neither mentioned in the configuration nor shown in Figure 8 (for instance, generic-repository having a dependency on session-provider). Windsor can figure them out on its own, so I usually don't bother with them unless I want something to be explicit.

When I make a request for an IRepository<Order>, Windsor will create the appropriate object graph and hand it over to me. All of this can happen behind the scenes, without my explicit intervention.

The power that this gives you as a developer is quite amazing. In a well-built system, you often don't even think about this; you add the services to the configuration as you write, and they wire themselves up for you. This means that you are freed from a lot of the trivialities of programming (how do I get this, how do I do that, and so forth). In the previous example, we have a series of small, independent components participating in a complex relationship to provide a robust DAL.

But, wait. I promised that I would talk about specialization, didn't I? Let us see what we can do with that by using Windsor.

Configuring Generic Specialization

You probably noticed that I didn't have a place for OrderRepository and its particular logic in the configuration.

Indeed, the way in which Windsor is set up right now, it will never call OrderRepository. It will create a decorator chain that will end with NHibernateRepository<Order>.

Because we want to have this functionality, let us put it back in. We can't just register the order-repository service again. This is because Windsor prefers to use a fully typed type, rather than a generic type—a fancy way of saying that if you defined IRepository<T> and IRepository<Order>, Windsor will go directly to the service defined as IRepository<Order>, and not try to use the declaration for IRepository<T>.

If we will just register the service, Windsor will create it for us; however, we will not get the decorator chain that we want.

Think about it, and you'll realize that there is a lot of sense in this. If you want to specialize a type, you probably want to specialize on the dependency chain, too. Let us see what we must do in order to get the behavior that we want back, with the decorators that we like.

In this case, I choose to demonstrate the flexibility of this method by forgoing caching on orders. The configuration goes as follows.

<!-- order repository defination -->

<component id="orders-security-decorator"
           service="NShop.IRepository`1[[NShop.Order, NShop]], NShop"
          type="NShop.SecurityDecorator`1[[NShop.Order, NShop]], NShop">
  <parameters>
    <inner>${orders-logging-decorator}</inner>
  </parameters>
</component>

<component id="orders-logging-decorator"
           service="NShop.IRepository`1[[NShop.Order, NShop]], NShop"
           type="NShop.LoggingDecorator`1[[NShop.Order, NShop]], NShop">
  <parameters>
    <inner>${orders-repository}</inner>
  </parameters>
</component>

<component id="orders-repository"
           service="NShop.NHibernateRepository`1[[NShop.Order, NShop]], NShop"
          type="NShop.OrderRepository, NShop"/>

It is rather simple, in the end, as shown in Figure 9.

Click here for larger image

Figure 9. Configuration of repositories and their relations, forgoing caching (Click on the picture for a larger image)

Note that we didn't have to create a special version of security-decorator or logging-decorator in order to support the new cache-less variant. I can use the security and logging decorators independently from the caching decorator (or from one another, for that matter). This allows for much greater capability for reuse in the system, because I can compose the object graphs to get the exact functionality that I need.

Responding to Change

This is the holy grail of developers, likely because we live in a real world and not a museum. Let us see what using an IoC container can give us.

Imagine Joe in the final stages of QA. The testers tell Joe that under heavy load, data is corrupted. Feeling puzzled, Joe looks over the code and realizes that Bob (the evil guy from all the crypto documents) didn't bother to use transactions when talking to the database (a contrived example, I know, but I have seen as much, and worse).

Joe gets to work, and in no time at all he produces a Transaction Decorator. This handy class will handle transaction for its contained repository. All that Joe has to do is to integrate it into the system.

Usually, this would mean going over the entire data-access layer and finding out where stuff happens. This can be a simple endeavor or a complete nervous breakdown, depending on the architecture of the system.

The following table shows two of those scenarios.

With WindsorTimeWithout WindsorTime
Write Transaction Decorator.1 hourAdd transaction handling to CustomersDAL.1 hour
Test to see that it is working according to requirements.1 hourCheck that it is working and you didn't break anything.1 hour
Add it to the decorator chain for generic-repository.1 minuteAdd transaction handling to OrdersDAL.1 hour
Add it to the decorator chain for order-repository.1 minuteCheck that it is working and you didn't break anything.1 hour
  Add transaction handling to ProductsDAL.1 hour
  Check that it is working and you didn't break anything.1 hour
Total time:2 hours and 2 minutesTotal time:6 hours

In this case, all Joe has to do is add the following snippet to the configuration (once for the generic version, and once for the specialized Order version).

<component id="transaction-decorator"
           service="NShop.IRepository`1, NShop"
           type="NShop.TranactionDecorator`1, NShop">
  <parameters>
    <isolationLevel>RepeatableRead</isolationLevel>
    <inner>${logging-decorator}</inner>
  </parameters>
</component>

The nice thing about it is that Joe can even choose different isolation levels, or forgo transactions altogether—at any level that Joe wishes.

Centralizing the responsibilities certainly paid off, in this case. In the remaining hours, I could take the time to implement another couple of features.

Looking at a More Complex Example

Let us look at a slightly more complex example. When the application was developed, the requirement was that the system will be able to send e-mail to the users when certain actions happened.

Our trusty Joe came up with the design that is shown in Figure 10, as well as its associated configuration.

Aa973811.ioc10(en-us,MSDN.10).gif

Figure 10. Joe's more complex design, involving e-mail

<component id="sender"
           service="NShop.ISender, NShop"
           type="NShop.EmailSender, NShop"/>

As you can imagine, things change, and soon afterward Joe was once again calling to add functionality to the application. This time, the customer wants to send SMS messages, as well as e-mail messages, if certain conditions are met.

Fearless Joe got right to work, and shortly afterward Joe came up with the result that is shown in Figure 11.

Aa973811.ioc11(en-us,MSDN.10).gif

Figure 11. Joe's even more complex design, involving e-mail and SMS

With the new design, all Joe had to do was replace the sender's configuration with the following.

<component id="sender"
           service="NShop.ISender, NShop"
           type="NShop.NotificationManager, NShop ">
  <parameters>
    <EmailSender>${email-sender}</EmailSender>
    <SmsSender>${sms-sender}</SmsSender>
  </parameters>
</component>
<component id="email-sender"
           service="NShop.ISender, NShop"
           type="NShop.EmailSender, NShop"/>
<component id="sms-sender"
           service="NShop.ISender, NShop"
           type="NShop.SmsSender, NShop "/>

Bang! The new functionality drops into place, without ever touching the calling code.

The End Result

Well, where does this leave us?

We have seen how utilizing IoC can help us create an application with modular design, with cohesive parts that have low (or no) coupling to the rest of the system.

Feel free to enter the random buzzword lingo of the day: extensible, configurable, dynamic, adaptable, agile, and so on. In the end, it is all about the amount of freedom that you have to move pieces of the application around.

IoC is a powerful way to let you tear apart the binding around your objects, and let them roam free and unencumbered.

Windsor's support for generic specialization brings some of the C++ magic back—this time in a very fancy box, too. The ability to customize the full object graph for a specific type can be used to do some amazing things—all the while leaving the developers blissfully unaware of the gritty details that go on behind the scenes, while they are working on the business domain.

Final Notes

The functionality that I describe is available from the Windsor Web site (Windsor is currently in Release Candidate 2 and will be released in a short time).

There are several other ways to get the same functionality. This is one that I particularly like, because it is easy to work with and understand.

The examples that I came up with are by necessity simple; otherwise, I would have to spend more time explaining the problem than explaining how to solve it.

For instance, I wouldn't usually worry about caching if I'm using NHibernate, because it has very good caching internally.

Show:
© 2014 Microsoft