Chapter 36. Introducing CodeConfig

36.1. A Dependency Injection Example

We will introduce the new code based approach by working with a very simple application that will provide us the context to understand the concepts of CodeConfig. We start by examining a sample application that uses Spring.NET configured via ‘traditional’ XML configuration files. Then we show how CodeConfig can be used to achieve the same results without any XML configuration files at all.

To begin with, let’s explore the sample application that we will be working with. This sample app is included in the Spring.NET CodeConfig download package in the /examples/Spring.CodeConfig.Migration folder.

To keep things simple, it’s just a .NET console application designed to calculate and display the prime numbers between zero and an arbitrary maximum number. There are four classes that must collaborate together to do the work: ConsoleReporter, PrimeGenerator, PrimeEvaluationEngine, and OutputFormatter. ConsoleReporter depends on the PrimeGenerator which in turn depends on the PrimeEvaluationEngine to calculate the prime numbers. ConsoleReporter also depends on OutputFormatter to format the results. The main console application then simply asks ConsoleReporter to write its report and ConsoleReporter goes to work. The following Figure is a UML class diagram showing a simple way to visualize the dependencies between these objects.

A simple Main() method that would do this without the Spring.NET container could look something like Listing 1. Note the in-line injection of dependencies via constructor arguments that builds up the collaborating objects.

//Listing 1 (sample Main method not using Spring.NET)
static void Main(string[] args)
{
     ConsoleReport report = new ConsoleReport(
                              new OutputFormatter(),
                              new PrimeGenerator(new PrimeEvaluationEngine()));

     report.MaxNumber = 1000;
     report.Write();

     Console.WriteLine("--- hit enter to exit --");
     Console.ReadLine();
}

Using Spring.NET, as opposed to manually injecting dependencies as in Listing 1, the collaborating objects are composed together with their dependencies injected by the Spring.NET container at run-time. Initially, the configuration of these objects is controlled from a Spring.NET XML configuration file (see Listing 2).

<!-- Listing 2 (Spring.NET XML Configuration file, application-context.xml) -->
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
 
  <object name="ConsoleReport" type="Primes.ConsoleReport, Primes">
    <constructor-arg ref="PrimeGenerator"/>
    <constructor-arg ref="OutputFormatter"/>
    <property name="MaxNumber" value="1000"/>
  </object>
 
  <object name="PrimeGenerator" type="Primes.PrimeGenerator, Primes">
    <constructor-arg>
      <object type="Primes.PrimeEvaluationEngine, Primes"/>
    </constructor-arg>
  </object>
 
  <object name="OutputFormatter" type="Primes.OutputFormatter, Primes"/>
 
</objects>

In Listing 2 you can also see the use of “ref” element to refer to collaborating objects and the property “MaxNumber” being set to “1000” on the ConsoleReport object after it’s constructed. This is the maximum number up to which we want the software to calculate prime numbers. In Listing 3 we see the construction of the XmlApplicationContext which is initialized by passing it the name of the XML Configuration file. This container is then used to resolve the ConsoleReport object with all of its dependencies properly satisfied and its MaxNumber property assigned the value of 1000.

//Listing 3 (initializing the XmlApplicationContext container)
static void Main(string[] args)
{
     IApplicationContext ctx = CreateContainerUsingXML();
 
     ConsoleReport report = ctx["ConsoleReport"] as ConsoleReport;
     report.Write();
 
     ctx.Dispose();
 
     Console.WriteLine("--- hit enter to exit --");
     Console.ReadLine();
}
 
private static IApplicationContext CreateContainerUsingXML()
{
     return new XmlApplicationContext("application-context.xml");
}

While this XML-based configuration is well-understood by Spring.NET users and others alike as a common method for expressing configuration settings, it suffers from several challenges common to all XML file including being overly-verbose and full of string-literals that are unfriendly to most of the modern refactoring tools.

36.2. Migration to CodeConfig

To reduce or even eliminate the use of XML for configuring the Spring.NET DI container, let’s look at how we can express the same configuration metadata in code using Spring.NET CodeConfig. There are several steps to using CodeConfig. We will look at each of them in the likely sequence that one would follow to convert an existing XML-based configuration for Spring.NET over to use the CodeConfig approach.

36.2.1. The CodeConfig Classes

36.2.1.1. Creating the CodeConfig Classes

First, we need to construct one or more classes to contain our configuration metadata and attribute them properly. Spring.NET CodeConfig relies upon attributes applied to classes and methods to convey its metadata. Shown in Listing 4 is the CodeConfig class (PrimesConfiguration) for our sample application.

// Listing 4, (Spring.NET Configuration Class, PrimesConfiguration.cs)
using System;
using System.Configuration;
using Primes;
using Spring.Context.Attributes;
 
namespace SpringApp
{
    [Configuration]
    public class PrimesConfiguration
    {
        [ObjectDef]
        public virtual ConsoleReport ConsoleReport()
        {
            ConsoleReport report = new ConsoleReport(OutputFormatter(), PrimeGenerator());
 
            report.MaxNumber = Convert.ToInt32(ConfigurationManager.AppSettings.Get("MaximumNumber"));
            return report;
        }
 
        [ObjectDef]
        public virtual IOutputFormatter OutputFormatter()
        {
            return new OutputFormatter();
        }
 
        [ObjectDef]
        public virtual IPrimeGenerator PrimeGenerator()
        {
            return new PrimeGenerator(new PrimeEvaluationEngine());
        }
    }
}

36.2.1.2. Elements of the CodeConfig Classes

Let’s explore the important elements of the CodeConfig file in Listing 4 to understand how it can convey the same information to the Spring.NET container as the XML file in Listing 2.

36.2.1.2.1. The Class

At the class level, you will notice the PrimesConfiguration class has the[Configuration] attribute applied to it. During the initialization phase of the DI container, Spring.NET CodeConfig reads classes with these attributes. Note that there is no specific inheritance hierarchy required of a configuration class: no special base class or interface implementation is required, leaving you free to leverage inheritance and polymorphism to achieve some interesting configuration and composition scenarios. Also note these special identifying attributes are only applied to your CodeConfig classes, not the types for which they are providing object definition metadata. This means that your classes that do the work of your application (e.g., ConsoleReport, PrimeGenerator, etc.) are free to remain undiluted POCO (Plain-Old-CLR-Object) classes that have themselves no direct dependency on the Spring.NET framework.

36.2.1.2.2. The Methods

At the member level of the PrimesConfiguration class, you will notice several methods having the [ObjectDef] attribute. This attribute identifies the method to which it is applied as being the logical representation of a single object definition for the Spring.NET container.

To begin understanding how this works let’s look at the simplest of the definitions, that of the OutputFormatter. Let’s start with the method visibility: all [ObjectDef] methods must be declared both public and virtual.

The method return type, IOutputFormatter, becomes the type that the DI container will be configured to register. The method name itself, OutputFormatter, is the equivalent of the id or name that will be assigned to the object in the container. This name can also be controlled by setting the Names property on the [ObjectDef] attribute itself.

The body of the OutputFormatter() method simply creates a new instance of the OutputFormatter and returns it. In simple terms, we can think of the OutputFormatter() method as a factory method that knows how to construct and return an instance of something that implements the IOutputFormatter interface (in this case, the concrete OutputFormatter class).

36.2.1.2.3. More Complex Methods

To understand a slightly more complex [ObjectDef] method, let’s now examine the PrimeGenerator() method. Given what we already know about CodeConfig, it’s easy to see that the PrimeGenerator() method describes an Object Definition that will be registered with the container under the name “PrimeGenerator” (the method name) and the type IPrimeGenerator (the return type of the method).

The method needs to return a new PrimeGenerator but unlike the OutputFormater class that offers an empty default constructor, the PrimeGenerator class’ only public constructor requires an instance of the PrimeEvaluationEngine class be passed to it. To satisfy this constructor dependency, we simply create a new PrimeEvaluationEngine object in-line and pass it to the new PrimeGenerator class. In this way, the dependency between PrimeGenerator and PrimeEvaluationEngine is satisfied in much the same way as when coded ‘by hand’ as shown in Listing 1.

As a slightly more complex [ObjectDef] example, let’s examine the ConsoleReport() method next. This method needs to return a new ConsoleReport instance, but as with the PrimeGenerator class we lack a zero-argument public constructor. The only public constructor of the ConsoleReport class requires an IOutputFormatter instance and an IPrimeGenerator instance be provided. In our call to new up an instance of the ConsoleReport class in the ConsoleReport() [ObjectDef] method, we are invoking the other [ObjectDef] methods themselves to return these types. Since these other methods in turn return IOutputFormatter and IPrimeGenerator instances respectively, calls to these other methods will satisfy the constructor dependency of the ConsoleReport class and thus permit us to create a new ConsoleReport to return at the end of the ConsoleReport() method itself. In this manner, we are delegating from one [ObjectDef] method to the other [ObjectDef] methods to create the object graph that we seek to return from the call to the ConsoleReport() method.

36.2.1.2.4. Controlling Properties on Objects

But what about the “MaxNumber” property that is set for the ConsoleReport object in the XML file in Listing 2? As you can see from Listing 4, setting this property on our ConsoleReport object is as simple as…well, setting the property on our ConsoleReport object! Since our ConsoleReport() method merely has to return a new ConsoleReport instance, we are completely free to use any approach (in code) we choose to modify the ConsoleReport instance before we return it. In this case, it’s a simple matter of reading the value out of the App.Config file and then setting the property to the desired value before we return the instance of the ConsoleReport object from the method.

36.2.2. Creating and Initializing the Application Context

Once we have translated the XML configuration file in Listing 2 into the CodeConfig class in Listing 4, we need to tell our application to use it. For that, we need to switch from encapsulating our container in the Spring.NET XmlApplicationContext to encapsulating it in the CodeConfigApplicationContext instead. Just as the XmlApplicationContext is designed to use XML as the initial entry point to its configuration settings, the CodeConfigApplicationContext is designed to scan assemblies for [Configuration] classes and [ObjectDef] methods as the initial entry point to its configuration settings. Listing 5 shows the CreateContainerUsingCodeConfig() method from the Program.cs file in the sample application that demonstrates this process.

//Listing 5 (bootstrapping the CodeConfigApplicationContext from Program.cs)
private static IApplicationContext CreateContainerUsingCodeConfig()
{
    CodeConfigApplicationContext ctx = new CodeConfigApplicationContext();
    ctx.ScanAllAssemblies();
    ctx.Refresh();
    return ctx;
}

After instantiating the CodeConfigApplicationContext, we next invoke the ScanAllAssemblies() method to perform the scanning for the [Configuration]-attributed classes and [ObjectDef]-attributed methods within our project. Lastly, the container is initialized by calling the Refresh() method and then the ready-to-use context is returned from the method. In the invocation of the ScanAllAssemblies() method, we are asking the CodeConfigApplicationContext to scan the current AppDomain’s root folder and all subfolders recursively for all assemblies that might contain CodeConfig classes (classes having the [Configuration] attribute).

36.3. More Granular Control Using CodeConfig

The example in Listing 4 and Listing 5 demonstrates only the most basic use-cases for CodeConfig. More granular control over each of the definitions is provided by applying additional attributes to the [Configuration] classes and [ObjectDef] methods and by setting various values on these attributes. Among these are the following:

  • [Scope] for controlling object lifecycle settings on a [ObjectDef] such as singleton, prototype, etc.

  • [Import] for chaining [Configuration] classes together so that you can logically divide your [ObjectDef] methods among multiple classes in much the same way you may do so with multiple XML files that provide pointers to other XML files

  • [ImportResource] for combining [Configuration] classes with any of Spring.NET’s many implementations of its IResource abstraction (file://, assembly://, etc.), the most common one being an XML resource so that you can define part of your configuration metadata in [Configuration] classes and other parts of it in XML files as either embedded resource(s) in your assemblies or as file(s) on disk.

  • [Lazy] for controlling lazy instantiation of singleton objects

  • If you require aliases (additional, multiple names) for the Type in the container, the [ObjectDef] attribute also accepts an array of strings that if provided will be registered as aliases for the Type registration.

In addition, finer-grained control of the specific assemblies to scan, and specific [Configuration] classes to include and/or exclude is supported by the scanning API. It is also possible to compose configurations by dividing your [ObjectDef] methods into multiple different [Configuration] classes and then to assemble them as building blocks to configure your container as it is initialized.

The CodeConfig approach enables us to express the same configuration metadata to our Dependency Injection container as the XML file in Listing 2 had provided, but in a form that is at once both significantly more powerful and flexible as well as more resilient to the refactoring our container-managed application objects.

To address additional common non-XML configuration scenarios, such as the XML schemas for AOP and Transaction management, Spring.NET is also evolving a more fluent-style configuration API that will build upon CodeConfig in even more flexible ways in the near future including convention-based registration of objects.

36.4. Organizing and Composing Multiple [Configuration] Classes

The examples referenced in this document and provided in this distribution almost all employ merely a single [Configuration] class from which their ApplicationContext is to be configured. For these simple examples this approach is viable but just as is the case when configuring the ApplicationContext via XML, any significantly complex solution is likely to require separating your [ObjectDef] methods into multiple [Configuration] classes.

Just as there are several strategies for effectively managing multiple XML configuration files, so too are there many options available to the developer to organize and compose multiple [Configuration] classes together in a larger solution. This section doesn't attempt to cover all of the available options in deep detail, but is intended to provide a high-level understanding of some of the techniques that can be combined together to help manage multiple such classes. Users familair with the common techniques for composing together multiple XML configuration files for Spring.NET will recognize some of these same patterns applied to [Configuration] classes in the following sections as well.

36.4.1. Multiple Stand-Alone Configuration Classes

The simplest organization approach is providing multiple stand-alone [Configuration] classes. In this scenario, [ObjectDef] methods are organized into separate [Configuration] classes but each of the [Configuration] classes is entirely self-contained and unrealted to the others. The decomposition principles can of course be anything of your choosing. One simple possibility might be to divide your configuration data between different kinds of services as in the following example:

[Configuration]
public class WcfServicesConfigurations
{
     [ObjectDef]
     public virtual IWebService MySpecialService()
     {
          //construct and return a IWebService implementation here
     }
}

[Configuration]
public class RepositoryServicesConfigurations
{
     [ObjectDef]
     public virtual ICustomerRepository MyCustomerRepository()
     {
          //construct and return a ICustomerRepository implementation here
     }

     [ObjectDef]
     public virtual IShippingRepository MyShippingRepository()
     {
          //construct and return a IShippingRepository implementation here
     }
}

In this example, both of these [Configuration] classes would need to be explicitly scanned and registered with the CodeConfigApplicationContext since they each are completely stand-alone and both are needed for the proper configuration of the ApplicationContext. Since these two classes in this example have no interdependencies between them, each class may be placed into a different file or even assembly.

36.4.2. High-Level [Configuration] Classes that [Import] Others

Another strategy for composing multiple [Configuration] classes together is to devise one or more 'high-level entry-point' classes and leverage the [Import] attribute so that the scanning of the high-level class automatically imports one or more lower-level classes. The high-level classes may contain [ObjectDef] methods of their own or merely hold reference to one or more [Import] classes as needed.

[Configuration]
[Import(typeof(MyWebServicesConfigurations))]
[Import(typeof(MyMessagingServicesConfigurations))]
[Import(typeof(MyPersistenceServicesConfigurations))]
public class ServicesConfigurations
{
     //rest of class here as needed
}

In this example, only the ServicesConfigurations class needs to be scanned because the [Import] attributes point directly to the other classes to scan for [Configuration] and [ObjectDef] metadata.

36.4.3. Referencing [ObjectDef]s from one [Configuration] Class in Another

Once you decompose your [ObjectDef] methods into separate classes, often you will find that you have a need to reference the objects defined in one [Configuration] class when coding the [ObjectDef] methods in another [Configuration] class. The architecture of Spring CodeConfig for .NET makes it simple to address this need: you can simply ask the ApplicationContext to resolve the needed Type.

To understand how this works, its first important to understand that [Configuration] classes are themselves registered as types in the ApplicationContext in addition to the types defined in their [ObjectDef] methods. Combining that knowledge with the special IApplicationContextAware interface in Spring.NET allows us to ask the ApplicationContext to inject itself into our [Configuration] classes. This ApplicationContext is then available to us to resolve requests for needed types that may be defined elsewhere, whether in other [Configuration] classes or perhaps even other XML files.

Let's explore the following example where the SecondConfiguration class needs access to the TransactionManager that is defined in the FirstConfiguration class in order to properly build and configure a CustomerRepository instance:

[Configuration]
public class FirstConfiguration
{
     [ObjectDef]
     public virtual TransactionManager MySpecialTransactionManager()
     {
          return new TransactionManager();
     }
}

[Configuration]
public class SecondConfiguration : IApplicationContextAware //note the interface implementation
{
     //field to hold the injected context
     private IApplicationContext _context;

     //property setter defined by the IApplcationContextAware interface
     //     so that the context can inject itself into the class
     public IApplicationContext ApplicationContext { set { _context = value; } }

     [ObjectDef]
     public virtual ICustomerRepository CustomerRepository()
     {
          //to construct the CustomerRepository, we need a TransactionManager instance
          //     as a constructor argument so let's ask the injected context to resolve one for us
          return new CustomerRepository(_context.GetObject<TransactionManager>());
     }
}

//somewhere else in your solution the CustomerRepository class is defined as follows...
public class CustomerRespository : ICustomerRespository
{
     private TransactionManager _transactionManager;

     public CustomerRespository(TransactionManager transactionManager)
     {
          _transactionManager = transactionManager;
     }
}

In this way, there is no direct coupling between [Configuration] classes and the SecondConfiguration class is only aware of the ApplicationContext itself and the actual Types it needs to construct the Types described in its [ObjectDef] methods.