Monday, April 26, 2010

Spring.NET & Attribute Driven Configuration

I've been using Spring.NET for years for dependency injection and database transaction handling. I love it's ability to be extensible, but at the same time despise it's learning curve for new users. I get a lot of push back from people when they start using Spring because they don't like putting in the XML configuration and then spend time reading through exceptions to see what kind of typos they've had.

I was playing around with the Managed Extensibility Framework (MEF) and decided to steal some of its ideas. I wanted to provide an attribute drive dependency injection framework built on Spring.NET. This is something that is easier for developers to pick up, and avoids the error prone XML configuration. I wanted to put something together that would play nice with existing Spring configurations and was declarative. Although placing attributes in your classes couples them with the dependency injection framework, it provides a readable up front way to see how dependencies are being resolved.

The idea here, is to have something scan all classes in an assembly and for each class marked with an Export attribute, create a Spring.NET object for it. Any class being exported can also have its dependencies set by marking such properties with an Import attribute. Any property marked with an Import attribute will attempt to have its dependencies set.

[Import] & [Export] Attributes
[Export]
public class ViewModel : INotifyPropertyChanged
{
[Import]
public IModel Model { get; set; }

:
}
[Export(typeof(IModel))]
class Model : IModel
{
public string LoadData()
{
return "Here is some data!";
}
}

When a class is exported, it defines a contract name. This contract name is later used by those who wish to import an implementation of that contract. To make things easier, you can also use a System.Type as the contract, which is simply converted to its string representation.


Placing the Import attribute on a property tells the scanner to look at the specified contract name and find an implementation that has been exported with that contract name. Just like the Export attribute, if no contract name is specified it uses the System.Type of the property as the contract name. If you had some object definitions defined in a higher (or the same) IApplicationContext, you can use those object IDs as contract names and have them injected into the property.

Startup
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

//
// Load Spring
//
GenericApplicationContext context = new Spring.Context.Support.GenericApplicationContext();
ObjectDefinitionScanner scanner = new ObjectDefinitionScanner(context);
scanner.Scan(System.Reflection.Assembly.GetExecutingAssembly());

//
// Load Main Window
//
this.MainWindow = (Window)context.GetObject(typeof(MainWindow).FullName);
this.MainWindow.Show();
}
}

To get things started, you pass in your current IApplicationContext into an ObjectDefinitionScanner; which scans the assembly and creates object definitions from the Import and Export attributes found. Notice that you can pass in an existing IApplicationContext that already has definitions in it (i.e. from XML) and reference those definitions in the attributes.

Download sample code here.