Chapter 2. CodeConfigApplicationContext Reference

The CodeConfigApplicationContext is an implementation of IApplicationContext designed to gather its configuration from code-based sources as opposed to XML-based sources as is the common case with most other IApplicationContext implementations provided by Spring.NET.

This chapter introduces the CodeConfigApplicationContext and how you can use .NET code to configure the Spring.NET container instead of XML files. For a general overview on the design of the Spring.NET container please see container overview. The distribution includes three sample applications, two console applications (the familiar MovieFinder and a prime number generator) and a ASP.NET MVC web application. Refer to the examples section for more information.

[Note]Note

The code-based configuration support requires .NET 2.0 or higher and solutions that depend upon CodeConfig must be compiled with Visual Studio 2008 or later.

2.1. Concepts

Internally, Spring.NET has a metadata model around the classes it is responsible for managing, the main abstraction in this metadata model is the interface IObjectDefinition. The IObjectDefinition serves as the 'recipie' that Spring.NET uses to perform dependency injection. In the case of XML-based configuration sources, XmlApplicationContext parses XML files to populate the metadata model. In the case of CodeConfig, the CodeConfigApplicationContext scans one or more assemblies containing one or more types attributed with the [Configuration] attribute, parses them to construct appropriate ObjectDefinition instances, and registers those Object Definitions with the IApplicationContext for use. You can also mix-and-match configuration sources, with some object definitions originating in XML and others in code.

2.1.1. Using the CodeConfigApplicationContext

The CodeConfigApplicationContext usage pattern consists of the following high-level steps:

  1. Instantiate an instance of the CodeConfigApplicationContext

  2. [optional] Provide one ore more filtering constraints to control the Assemblies and/or Types to participate in the scanning

  3. Perform the actual scanning

  4. Initialize (refresh) the CodeConfigApplicationContext which creates the object definition and eagerly instantiates singleton objects.

2.1.2. Mixing and matching configuration metadata formats

You can also use a combination of XML and Code base configuration metadata to configure the Spring.NET container. If you are an existing user of Spring.NET, incrementally adopting the code base configuration model will be a common use-case. In this scenario you should use the code-config XML namespace to reference configuration metadata. Using the code-config namespace requires you to register a custom namespace parser in App.config. Below is an example showing the use of the code config namespace

<objects xmlns="http://www.springframework.net"
         xmlns:context="http://www.springframework.net/context">
  
  <context:code-config/>

  <!-- <object/> definitions here -->

  
</objects>

The current XML namespace does not yet allow you to express contraints to limit the scope of the scanning.

You will also need to configure the context namespace parser in the main .NET application configuraiton file as shown below

<configuration>

  <configSections>
    <sectionGroup name="spring">
      <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity -->     
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>        
     </sectionGroup>
  </configSections>

  <spring>
    <parsers> 
      <parser type="Spring.Context.Config.ContextNamespaceParser, Spring.Core.Configuration" />
    </parsers> 
  </spring>

</configuration>

You can also start from a CodeConfig class and import XML based object defintions. The example below shows loading of an embedded XML resource file using the [ImportResource] attribute.

[Configuration]
[ImportResource("assembly://MyApp/MyApp.MyNamespace.MyConfig/ObjectDefinitions.xml"))]
public class DataModuleConfigurationClass
{
    [Definition]
    public virtual SomeType SomeType()
    {
        return new SomeType();
    }
}

2.2. Scanning Basics

The behavior of the CodeConfigApplicationContext scanning operation can be controlled by the following constraints:

  • Assembly Inclusion Constraint

    Only assemblies matching this contraint will be scanned; defaults to 'all assemblies'.

  • Type Inclusion Contraint

    Only types matching this contraint in assemblies matching the Assembly Inclusion Constraint will be scanned; defaults to 'all types'.

2.2.1. Scaning all assemblies

The simplest way to get started using code-based configuration is to instruct the context to scan all assemblies, as shown below

var ctx = new CodeConfigApplicationContext();
ctx.ScanAllAssemblies();
ctx.Refresh();
[Note]Note

Despite the name of the method 'ScanAllAssemblies', not all assemblies need to be scanned for [Configuraiton] attributes. There is an implicit filter that exclude the .NET BCL libraries as well as the Spring.NET assemblies since these will never contain any [Configuration] attributed classes. The name 'ScanAllAssemblies' should be interpreted as scanning all of the other assemblies you reference in your application.

2.2.2. Scanning specific assemblies and types

While the scanning process is very rapid (as compared to the overhead of parsing XML files) you may want to control the scope of the scanning operations to match your deployment or other usage models. To facilitate control of the scope of the scanning operation, the CodeConfigApplicationContext provides several .ScanXXX() methods that accept constraints to be applied to the assemblies and types during the scanning process. The format of these constraints is that of Predicate<T> where T is either System.Reflection.Assembly or System.Type, respectively. Recall that Predicate<T> is any method that accepts a single parameter of Type T and returns a bool.

The list of scan methods and brief description is shown below

Table 2.1. Description
ScanAllAssemblies()Scans all assemblies, except those in the .NET BCL and Spring distribution
ScanWithTypeFilter(Predicate<Type> typePredicate)Scans only those types that match the typePredicate
ScanWithAssemblyFilter(Predicate<Assembly> assemblyPredicate)Scans only those assemblines that match the assemblyPredicate
Scan(Predicate<Assembly> assemblyPredicate, Predicate<Type> typePredicate)Scans the AppDomain root path for assemblies that match the assemblyPredicate and types that match the typePredicate
Scan(AssemblyObjectDefinitionScanner scanner)Performs the scan using the settings encapsulated in the provided ObjectDefintionScanner

Other sections below provide a more detailed description and usage examples for the scan methods.

Note that as with any Predicate<T> construct, the Predicate<Assembly> and Predicate<Type> constraints may be of arbitrary complexity, formulated using any combination of standard C# AND (&&), OR (||), and NOT (!) operators.

The following example matches assemblies with names containing "Config" but NOT containing "Configuration" OR containing "Services":

var ctx = new CodeConfigApplicationContext();
ctx.ScanWithAssemblyFilter(assy => (assy.Name.FullName.Contains("Config") && !assy.Name.FullName.Contains("Configuration")) || assy.Name.FullName.Contains("Services");

Because its merely a .NET delegate, note that it is also possible to pass any arbitrary method that satisfies the contract (Predicate<T>), so assuming that the method IsOneOfOurConfigAssemblies is defined elsewhere as follows...

private bool IsOneOfOurConfigAssemblies(Assembly assy)
{
    return (assy.Name.FullName.Contains("Config") && !assy.Name.FullName.Contains("Configuration")) || assy.Name.FullName.Contains("Services");
};

...its possible to express the constraint in the call to .Scan(...) much more succinctly as follows:

var ctx = new CodeConfigApplicationContext();
ctx.ScanWithAssemblyFilter(IsOneOfOurConfigAssemblies);

None of this is anything other than simple .NET Delegate handling, but its important to take note that the full flexibility of .NET Delegates and lambda expressions is at your disposal for formulating and passing scanning constraints.

2.2.3. Assembly Inclusion Constraints

To facilitate limiting the scope of scanning at the Assembly level, the CodeConfigApplicationContext provides several .ScanXXX() method signatures that accept a constraint to be applied to the assemblies at scan time. The format of this constraint matches Predicate<System.Reflection.Assembly> (e.g. any delegate method that accepts a single System.Reflection.Assembly param and returns a bool).

As an example, the following snippet demonstrates the invocation of the scanning operation such that it will only scan assemblies whose filename begins with the string "MyCompany.MyApplication.Config." and so would match assemblies like MyCompany.MyApplication.Config.Services.dll and MyCompany.MyApplication.Config.Infrastructure.dll but would not match an assembly named MyCompany.MyApplication.Core.dll.

var ctx = new CodeConfigApplicationContext();
ctx.ScanWithAssemblyFilter(assy => assy.Name.FullName.StartsWith("MyCompany.MyApplication.Config."));

Because the Predicate<System.Reflection.Assembly> has access to the full reflection metadata of each assembly, it is also possible to indicate assemblies to scan based on properties of one or more contained types as in the following example that will scan any assembly that contains at least one Type whose name ends in "Config". Note that even though this constraint is dependent upon Type metadata, it is still a functional Assembly contraint, resulting in filtering only at the Assembly level.

var ctx = new CodeConfigApplicationContext();
ctx.ScanWithAssemblyFilter(a => a.GetTypes().Any(assy => assy.GetTypes().Any(type => type.FullName.EndsWith("Config"))));

2.2.4. Type Inclusion Constraints

To limit the scope of scanning of types within assemblies, the CodeConfigApplicationContext provides several .ScanXXX() method signatures that accept a constraint to be applied to include types within assemblies at scan time. The format of this contraint matches Predicate<System.Type> (e.g. any delegate method that accepts a single System.Type param and returns a bool).

As an example, the following snippet demonstrates the invocation of the scanning operation such that it will only scan types whose names contain the string "Config" and so would match types named "MyConfiguration", "ServicesConfiguration", and "ConfigurationSettings" but not "MyClass".

var ctx = new CodeConfigApplicationContext();
ctx.ScanWithTypeFilter(type => type.FullName.Contains("Config");

There are two important aspects to take note of in re: the behavior of the CodeConfigApplicationContext with regards to Type Inclusion Constraints

  • Type Inclusion Constraints are applied only to types defined in assemblies that also satisfy the Assembly Inclusion Constraint

    No matter whether any given Type satisfies the Type Inclusion Contstraint, if the Type is defined in an assembly that fails to satisfy the Assembly Inclusion Constraint, the Type will not be scanned for Object Defintions.

  • There is always an implicit additional Type Inclusion Constraint of "...and the Type must have the [Configuration] attribute applied to it"

    No Type that does not have the [Configuration] attribute applied to its declaration will ever be scanned regardless of any Type Inclusion Constraint.

2.3. Advanced Scanning Behavior

In some cases, you may want more fine-grained control of the scanning behavior the CodeConfigApplicationContext. In this section, we explore the various techniques for achieving this level of control.

2.3.1. Combining Root Path, Assembly Constraints, and Type Constraints

The CodeConfigApplicationContext provides several .ScanXXX(...) method overloads that accept both an Assembly Constraint and a Type Constraint. These may be combined as in the following example:

var ctx = new CodeConfigApplicationContext();
ctx.Scan(assy => assy.FullName.Name.BeginsWith("Config"), type => type.Name.Contains("Infrastructure"));

Note that it is not possible to exclude specific types from the scanning process using these overloads of the .Scan(...) method. To get type-exclusion control, you must instantiate and pass in your own instance of the AssemblyObjectDefinitionScanner as described in the following section(s).

2.3.2. Using your own AssemblyObjectDefintionScanner Instance

If you need more fine-grained control of the scanning behavior of the CodeConfigApplicationContext, you can instantiate and configure your own instance of the AssemblyObjectDefinitionScanner and pass it to the .Scan(...) method directly. The AssemblyObjectDefinitonScanner provides many methods for defining the contraints that will control the scanning process.

2.3.2.1. Scanning Specific Assemblies and Types

The AssemblyObjectDefinitionScanner provides methods that permit specific assemblies or types to be included or excluded.

var scanner = new AssemblyObjectDefinitionScanner();

scanner.AssemblyHavingType<MyConfigurations>(); //add the assembly containing this type to the list of assemblies to be scanned
scanner.IncludeType<MySpecialConfiguration>(); //add this specific type the list of types to be scanned
scanner.ExcludeType<MyConfigurationToBeIgnored>(); //exclude this specific type from the list of types to be scanned

var ctx = new CodeConfigApplicationContext();
ctx.Scan(scanner);

For those that prefer a more fluent feel to the AssemblyObjectDefinitionScanner API, there are methods that permit successive filter criteria to be strung together in a sequence as in the following example:

var scanner = new AssemblyObjectDefinitionScanner();

scanner
  .WithAssemblyFilter(assy => assy.FullName.Name.StartsWith("Config"))
  .WithIncludeFilter(type => type.Name.Contains("MyApplication"))
  .WithExcludeFilter(type => type.Name.EndsWith("Service"))
  .WithExcludeFilter(type => type.Name.StartsWith("Microsoft"));

var ctx = new CodeConfigApplicationContext();
ctx.Scan(scanner);