I'm assuming some prior knowledge of Inversion of Control/Dependency
Injection tools in this post. The most exciting addition to
StructureMap 2.0 for me is the new Fluent Interface API for
programmatic configuration of object composition. There are two
entry points into this functionality:
- You can make calls directly to the static StructureMapConfiguration class in some sort of application startup.
- Create your own Registry subclass in an assembly that
StructureMap. You will have to direct StructureMap to scan the
assembly containing your custom Registry class. I meant the
Registry class to be an easy way to segregate the configuration into
more manageable chunks and to make the configuration of composite
applications easier. More on this in a later post.
QuickStart
Let's jump right in. The simplest possible usage of
StructureMap is to define the default concrete type to use for a given
abstraction. In this case, I have an interface called IService, and the default instance I'd like to use within the application is a concrete implementation called LocalService.
// You know what, I don't want to use the StructureMap.config file
StructureMapConfiguration.UseDefaultStructureMapConfigFile = false;
StructureMapConfiguration.BuildInstancesOf<IService>()
.TheDefaultIsConcreteType<LocalService>();
Before I configured the IService interface, I shut off the usage of
the default StructureMap.config file so StructureMap doesn't whine
at you if that file doesn't exist. Now, you're completely ready
to request IService instances from ObjectFactory like this:
IService service = ObjectFactory.GetInstance<IService>();
Scoping
That's the simplest possible and most common usage of
StructureMap, but there are plenty more usages of Dependency Injection
supported by StructureMap. Let's take another step and control
the scoping of the IService
instances. By default, StructureMap builds a brand new instance
for each request to GetInstance<T>(), but that behavior can be
overridden. For example, when you use an ORM like NHibernate you
typically want to share the NHibernate ISession across all classes
executing in the current thread or HttpContext. In that case a
"PerRequest" instance isn't desirable, so I'm going to make the
scope of IService be ThreadLocal. Anytime an IService
is requested, StructureMap is going to first check if there is an
existing instance cached in ThreadLocalStorage and return that instance
if it exists. Otherwise, it will build a new instance and cache
the new instance on ThreadLocalStorage.
StructureMapConfiguration.BuildInstancesOf<IService>()
.TheDefaultIsConcreteType<LocalService>()
.CacheBy(InstanceScope.ThreadLocal);
Notice the bolded part of the code above. Other options
for scoping available today are a Singleton, HttpContext, and
a Hybrid model that looks for a current HttpContext first and
defaults back to ThreadLocalStorage if the code isn't running inside
ASP.Net. Because it's so common in usage, there is a convenience
method for marking instances as a Singleton.
StructureMapConfiguration.BuildInstancesOf<IService>()
.TheDefaultIsConcreteType<LocalService>()
.AsSingletons();
Primitive Constructor Arguments
Connecting abstract types to concrete types is the most common usage
of a Dependency Injection tool, but many concrete types are going to
need additional information like connection strings, server names,
filenames, and any other number of configuration items.
StructureMap has always supported that need, and it's actually how I
generally do configuration. Let's introduce a new type of IService that needs a couple of constructor arguments:
public class RemoteService : IService, ICloneable
{
public RemoteService(string host, int port)
{
_host = host;
_port = port;
}
}
RemoteService above exposes a pair
of constructor arguments to connect to a remote server. Let's say
that you're perfectly fine with just embedding the server name and port
into your code. The configuration of RemoteService would look like this:
StructureMapConfiguration.BuildInstancesOf<IService>().TheDefaultIs(
Registry.Instance<IService>().UsingConcreteType<RemoteService>()
.WithProperty("host").EqualTo("localhost")
.WithProperty("port").EqualTo(5018)
);
In reality, you're going to pull most of this kind of information
from some sort of configuration. I've added a convenience
method to the new Fluent Interface to grab information from the
AppSettings in the application config file.
StructureMapConfiguration.BuildInstancesOf<IService>().TheDefaultIs(
Registry.Instance<IService>().UsingConcreteType<RemoteService>()
.WithProperty("host").EqualToAppSetting("SERVER-HOST")
.WithProperty("port").EqualToAppSetting("SERVER-PORT")
);
There are a lot of other tricks and traps for managing configuration
over multiple environments, but I'm going to leave that for it's own
post. What I will say is that I've found that doing configuration
in this way is very beneficial. By just injecting information
through constructor arguments your configuration reflects your code
instead of the other way around. Your code is now perfectly able
to run without any hard coupling to the existence of a certain
configuration strategy. That decoupling leads to code that's
easier to test and promotes better opportunities for reuse.
I want to build the instance, you just deliver it!
This is all new for version 2.0. You might just want to create
the objects yourself for some reason or another and just direct
StructureMap to deliver your instance on calls to ObjectFactory.GetInstance<T>(). That can be done in a couple ways like these examples shown below:
// Register a prebuild instance, this exact instance will be returned on
// any request to ObjectFactory.GetInstance<IService>()
StructureMapConfiguration.BuildInstancesOf<IService>().TheDefaultIs(
Registry.Object<IService>(new LocalService())
);
// Register a prebuild instance, a clone of this instance will be returned on
// any request to ObjectFactory.GetInstance<IService>(). The concrete type
// will have to implement ICloneable for this to work
RemoteService service = buildService();
StructureMapConfiguration.BuildInstancesOf<IService>().TheDefaultIs(
Registry.Prototype<IService>(service)
);
There will be more. Next time I'll take a look at auto
wiring, configuring child dependencies, and scanning assemblies for
classic StructureMap attributes.