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.
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.
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 { [Definition] public virtual ConsoleReport ConsoleReport() { ConsoleReport report = new ConsoleReport(OutputFormatter(), PrimeGenerator()); report.MaxNumber = Convert.ToInt32(ConfigurationManager.AppSettings.Get("MaximumNumber")); return report; } [Definition] public virtual IOutputFormatter OutputFormatter() { return new OutputFormatter(); } [Definition] public virtual IPrimeGenerator PrimeGenerator() { return new PrimeGenerator(new PrimeEvaluationEngine()); } } }
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.
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.
At the member level of the
PrimesConfiguration class, you will notice
several methods having the [Definition]
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 [Definition]
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 [Definition]
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).
To understand a slightly more complex [Definition]
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 [Definition]
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() [Definition]
method, we are invoking the other [Definition]
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 [Definition] method to
the other [Definition]
methods to create the object graph that we seek to return from the
call to the ConsoleReport() method.
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.
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 [Definition]
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 [Definition]-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).
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 [Definition]
methods and by setting various values on these attributes. Among these are
the following:
[Scope] for
controlling object lifecycle settings on a [Definition]
such as singleton, prototype, etc.
[Import] for
chaining [Configuration]
classes together so that you can logically divide your [Definition]
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 [Definition]
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 [Definition]
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.
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 [Definition] 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.
The simplest organization approach is providing multiple stand-alone [Configuration] classes. In this scenario, [Definition] 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 { [Definition] public virtual IWebService MySpecialService() { //construct and return a IWebService implementation here } } [Configuration] public class RepositoryServicesConfigurations { [Definition] public virtual ICustomerRepository MyCustomerRepository() { //construct and return a ICustomerRepository implementation here } [Definition] 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.
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
[Definition] 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 [Definition]
metadata.
Once you decompose your [Definition]
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 [Definition]
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 [Definition]
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 { [Definition] 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; } } [Definition] 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 [Definition]
methods.