StructureMap is an open source tool I’ve written for Dependency Injection support in .Net. This past week I’ve had a couple requests for a gentle introduction to StructureMap, so here’s my first attempt. Typically I use StructureMap in a handful of ways:
- Configuration and creation of services (little “s”) that my code depends upon. By service here I don’t necessarily mean SOA and web services, but web service proxy classes are an automatic candidate for StructureMap configuration. I’m referring to subsystems of your application like logging modules, persistence, gateways to other systems, or really anything that you don’t want your code to be tightly coupled to.
- Object composition. Code reuse and unit testing is easier when you favor composition over inheritance relationships, but composition implies that you need to some mechanical coding work just to put the object graph together. This is where StructureMap comes into play.
- Plugin support. Occasionally there is a real need for pluggable, extensible application frameworks.
A Contrived Example
Let’s say you’re an integration developer in a shop with a crazily mixed bag of applications floating around a big central database. You want to write a pluggable infrastructure for batch scheduling little integration tasks. You may need to pull data by a sql query, a stored procedure, or by opening a file – then do something with the data. All you really know is that the possible sources of the data and the actions on the data will change over time. You might decide to create a pluggable framework using StructureMap to handle the configuration (Okay, in reality you would probably buy something off of the shelf or utilize DTS out of the box, but I needed an example).
First off, let’s ignore the configuration schema and concentrate on defining a set of interfaces and roles for our “Trigger” framework. The main interface will be this:
public interface ITrigger
{
// Do whatever it is that you do
void Execute();
// Tell me when you want to run next
DateTime NextExecutionTime();
}
Configuring the most basic trigger class will define the mechanism to retrieve the data, an action to perform on the data, and a strategy for determining the next run time (next Monday, the top of the next hour, the last day of the month, etc.). That adds the following code:
[Pluggable("Basic")] // Tell StructureMap that it "builds" this class and that it's alias is "Basic"
public class Trigger : ITrigger
{
private readonly IScheduler _scheduler;
private readonly IDataSource _source;
private readonly IAction _action;
public Trigger(IScheduler scheduler, IDataSource source, IAction action)
{
_scheduler = scheduler;
_source = source;
_action = action;
}
public void Execute()
{
DataTable table = _source.FetchTable();
_action.Process(table);
}
public DateTime NextExecutionTime()
{
return _scheduler.GetNextRunTime(DateTime.Now);
}
}
public interface IDataSource
{
DataTable FetchTable();
}
public interface IAction
{
void Process(DataTable table);
}
public interface IScheduler
{
DateTime GetNextRunTime(DateTime currentTime);
}
The first Trigger that we’re going to need to build is going to daily run a SQL query against the central database, and then email the results to an unlucky support person for resolution. First we create the little classes for the scheduling, data source, and the action.
[Pluggable("SQL")]
public class SqlDataSource : IDataSource
{
private readonly string _sql;
private readonly IDatabase _database;
public SqlDataSource(IDatabase database, string sql)
{
_sql = sql;
_database = database;
}
public DataTable FetchTable()
{
return _database.FetchDataTable(_sql);
}
}
[Pluggable("Email")]
public class EmailAction : IAction
{
public EmailAction(string to, string body){…}
public void Process(DataTable table){…}
}
[Pluggable("Daily")]
public class DailyScheduler : IScheduler
{
public DailyScheduler(){}
public DateTime GetNextRunTime(DateTime currentTime){…}
}
SqlDataSource itself depends upon the IDatabase interface that provides access to an underlying database and handles the raw ADO.Net manipulation. Let’s say that the central database is a Sql Server database (but I’m starting to miss good old reliable Oracle). That adds the following to the code:
public interface IDatabase
{
DataTable FetchDataTable(string sql);
void SetStoredProcedureName(string storedProcedureName);
void SetParameter(string parameterName, object parameterValue);
DataTable FetchDataTableFromStoredProcedure();
}
public class SqlDatabase : IDatabase {…}
Okay, now we can finally make our StructureMap configuration to create the object graph for our first Trigger. The first thing we need to do is create a file named “StructureMap.config” that will be copied to the application directory. At a minimum, we need to define four things for StructureMap:
- <Assembly> - The .Net assemblies to scan for possible interfaces and classes to be built by StructureMap
- <PluginFamily> - The .Net types that can be requested from StructureMap. In our case this would be the ITrigger, IDataSource, IAction, and IScheduler interfaces. It can also be abstract or concrete classes as well.
- <Plugin> - The concrete .Net types that will implement the interfaces of the PluginFamily types.
- <Instance> - The actual object graph configurations. Notice that we didn’t do anything special in our classes to enable StructureMap (except the [Pluggable] attributes). All you need to do to enable your class to be configured by StructureMap is to take in all dependencies and properties in a constructor function. StructureMap also supports “Setter Injection,” but that’s really just meant for Types that aren’t under your control.
Jumping into the StructureMap.config file we’ll first configure the default IDatabase instance to point to the central Sql Server database.
<?xml version="1.0" encoding="utf-8" ?>
<StructureMap>
<!-- Tell StructureMap to scan the StructureMapSample.dll assembly for possible PluginFamily's and Plugin's -->
<Assembly Name="StructureMapSample" />
<!-- Define a PluginFamily for IDatabase, making the default instance "CentralDatabase" -->
<PluginFamily Assembly="StructureMapSample" Type="StructureMapSample.IDatabase" DefaultKey="CentralDatabase">
<!-- Tell StructureMap that the SqlDatabase class is a plugin to IDatabase called "MicrosoftSqlServer" -->
<Plugin Assembly="StructureMapSample" Type="StructureMapSample.SqlDatabase" ConcreteKey="MicrosoftSqlServer"/>
<!-- Configure the "CentralDatabase" instance of IDatabase. The <Property> nodes define the arguments to the constructor
functions -->
<Instance Key="CentralDatabase" Type="MicrosoftSqlServer">
<Property Name="connectionString" Value="connection string to somewhere..."/>
</Instance>
</PluginFamily>
</StructureMap>
With this configuration we could at anytime in our code type IDatabase database = (IDatabase) ObjectFactory.GetInstance(typeof(IDatabase)); to get a configured instance of the SqlDatabase class with the connection string set. If the database changed to Oracle or DB2 later, only the configuration to create a different concrete IDatabase class would be changed (yeah right). In this case I defined the Plugin explicitly in the XML configuration. The [Pluggable(“My Plugin Alias”)] attribute is an equivalent mechanism to using the <Plugin> node. I prefer the attributes wherever possible, but some people feel differently.
Moving on to the first Trigger instance, we would add a <PluginFamily> node for ITrigger and an <Instance> node for the object graph for the first Trigger.
<PluginFamily Assembly="StructureMapSample" Type="StructureMapSample.ITrigger">
<!-- Create a BasicTrigger object, and give it a DailyScheduler, -->
<!-- Uses the public Trigger(IScheduler scheduler, IDataSource source, IAction action) constructor function -->
<Instance Type="Basic" Key="MyFirstTrigger">
<!-- DailyScheduler -->
<Property Name="scheduler" Type="Daily" />
<!-- SqlDataSource(IDatabase database, string sql) -->
<!-- By leaving off an explicit definition for "database" StructureMap will use the default
"CentralDatabase" instance -->
<Property Name="source" Type="SQL">
<Property Name="sql" Value="select * from errorlog"/>
</Property>
<!-- public EmailAction(string to, string body) -->
<Property Name="action" Type="Email">
<Property Name="to" Value="poorfool@helpdesk.company.com"/>
<Property Name="body" Value="Fix these now!" />
</Property>
</Instance>
</PluginFamily>
The Trigger framework can now create this instance by calling into StructureMap as something like this:
public void RunTriggers()
{
string[] triggersToRun = this.findTriggersToRun();
foreach (string triggerName in triggersToRun)
{
// Ask StructureMap to build the object instance
ITrigger trigger =
(ITrigger) ObjectFactory.GetNamedInstance(typeof(ITrigger), triggerName);
trigger.Execute();
DateTime nextTime = trigger.NextExecutionTime();
this.rescheduleTrigger(triggerName, nextTime);
}
}
Additional trigger’s could be created by simply configuring the object graphs into the StructureMap.config file as a new <Instance> node. The big advantage in this case is that StructureMap can quite happily accommodate any new classes that you create later to implement the ITrigger/IDataSource/IAction interfaces. You could even “plugin” all new assemblies to be used by your application without recompilation. That’s potentially a huge win for my company because of the sheer number of per client customizations that we have to do.
There is far more information and an actual schema document at http://structuremap.sourceforge.net. I threw this together in a hurry, so please let me know if this is useful or if something else is necessary to make the tool more clear. Sometime in the near-ish future I’ll post much more on some of the new capabilities for configuration management and environment testing that we’re building into StructureMap for complex build and staging scenarios.
Posted
11-18-2005 8:39 AM
by
Jeremy D. Miller