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 | |
---|---|
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. |
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.
The CodeConfigApplicationContext
usage pattern
consists of the following high-level steps:
Instantiate an instance of the
CodeConfigApplicationContext
[optional] Provide one ore more filtering constraints to control the Assemblies and/or Types to participate in the scanning
Perform the actual scanning
Initialize (refresh) the
CodeConfigApplicationContext
which creates the
object definition and eagerly instantiates singleton objects.
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 component-scan XML namespace to reference configuration metadata. Using the component-scan 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:component-scan base-assemblies=""/> <!-- <object/> definitions here --> </objects>
The base-assemblies attribute allows you to express limitations of Assemblies to scan via a comma seperated list of assembly names. The filter is a StartsWith filter
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 { [ObjectDef] public virtual SomeType SomeType() { return new SomeType(); } }
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'.
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 | |
---|---|
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. |
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
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.
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"))));
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.
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.
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).
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.
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);
By default, classes attributed with [Component] , [Repository] , [Service] , [Controller] , [Configuration] or a custom attribute that extends [Component] are the only detected candidate components. However, you can modify and extend this behavior simply by applying custom filters. Add them as include-filter or exclude-filter sub-elements of the component-scan element. Each filter element requires the type and expression attributes. The following table describes the filtering options.
Filter Type | Example Expression | Description |
---|---|---|
attribute | Spring.Stereotype.RepositoryAttribute, Spring.Core | An attribute to be present at the type level in target components. |
assignable | My.Namespace.IFoo, My.Assembly | A class (or interface) that the target components are assignable to (extend/implement). |
regex | My.NameSpace.*.*Dao | A regex expression to be matched by the target components class names. |
custom | My.Namespace.MyTypeFilter, My.Assembly | A custom implementation of the
Spring.Context.Attributes.TypeFilters.ITypeFilter interface. |
The following example shows the XML configuration ignoring all [Repository] attributed classes and only scanning types with names that end with Dao using "stub" repositories instead.
<objects> <context:component-scan base-assemblies="My.Namespace"> <context:include-filter type="regex" expression=".*Dao"/> <context:exclude-filter type="annotation" expression="Spring.Stereotype.RepositoryAttribute, Spring.Core"/> </context:component-scan> </beans>