I know, you're plrobably sick of me clogging the CodeBetter feed with this stuff, but I needed to post this just to answer a question on the StructureMap list. A longstanding feature request is to be able to apply setter injection of dependencies into an object that's already constructed. The main culprit is WebForms and other *ahem* frameworks that don't allow you to control the construction of objects. I resisted it for quite some time because a.) I don't do WebForms and b.) I thought it was going to be hard. Well, enought people asked for it, I found a very good reason to use it on our app, and it turned out that it only took an hour or so to implement*, so here it is (excerpted from the StructureMap docs):
Many times you simply cannot control when an object is going to be created
(ASP.Net WebForms), but you may still want to inject dependencies and even
primitive values into an already constructed object. To fill this gap,
StructureMap 2.5.2+ introduces the "BuildUp()" method on Container and
ObjectFactory. BuildUp() works by finding the default Instance for the
concrete type passed into the BuildUp() method (or create a new Instance if one
does not already exist), then applying any setters from that Instance
configuration. At this time, StructureMap does not apply interception
inside of BuildUp().
Let's say that we have a class called "BuildTarget1" like
this:
public class
BuildUpTarget1
{
public
IGateway Gateway {
get; set; }
}
In usage, we'd like to have the Gateway dependency injected into a new instance of the BuildTarget1 class when we call BuildUp():
[Test]
public
void
create_a_setter_rule_and_see_it_applied_in_BuildUp_through_ObjectFactory()
{
var theGateway = new
DefaultGateway();
ObjectFactory.Initialize(x =>
{
x.ForRequestedType<IGateway>().TheDefault.IsThis(theGateway);
// First we create a new Setter Injection Policy
that
// forces StructureMap to inject all public
properties
// where the PropertyType is IGateway
x.SetAllProperties(y =>
{
y.OfType<IGateway>();
});
});
// Create an instance of BuildUpTarget1
var target = new
BuildUpTarget1();
// Now, call BuildUp() on target, and
// we should see the Gateway property assigned
ObjectFactory.BuildUp(target);
target.Gateway.ShouldBeTheSameAs(theGateway);
}
BuildUp() also works with primitive properties (but I'm not sure how useful this
will really be):
public class
ClassThatHasConnection
{
public
string ConnectionString {
get; set; }
}
[TestFixture]
public class
demo_the_BuildUp
{
[Test]
public
void push_in_a_string_property()
{
// There is a limitation to this. As of StructureMap 2.5.2,
// you can only use the .WithProperty().EqualTo() syntax
// for BuildUp()
// SetProperty() will not work at this time.
var container = new
Container(x =>
{
x.ForConcreteType<ClassThatHasConnection>().Configure
.WithProperty(o => o.ConnectionString).EqualTo("connect1");
});
var @class = new
ClassThatHasConnection();
container.BuildUp(@class);
@class.ConnectionString.ShouldEqual("connect1");
}
}
How my Team Uses This
I mentioned earlier that I finally did this because I wanted to use it for my project. We extensively use stuff in our views like:
var dashboardUrl = '<%= this.ActionUrl<HomeController>(c => c.DashboardIndex()) %>'
var consoleUrl = '<%= this.ActionUrl<HomeController>(c => c.Console(null)) %>'
That call in the view to ActionUrl<T>() calls into a Dovetail specific class called IUrlRegistry that "knows" what the actual url is for each controller method. Great, but that means my views need to get at the IUrlRegistry singleton. We started to get ObjectFactory.GetInstance<IUrlRegistry>() calls sprinkled into the base class for our views, but that's an anti pattern. Instead, we now use the BuildUp() function to inject a couple common services into our views. First, I created some properties on the View base page called "Urls" and "Container":
public class DovetailViewPage<VIEWMODEL> : ViewPage<VIEWMODEL>, ITestablePage, IDovetailViewWithModel<VIEWMODEL>
where VIEWMODEL : ViewModel
{
public DovetailViewPage()
{
ObjectFactory.BuildUp(this);
}
public IUrlRegistry Urls { get; set; }
public IContainer Container { get; set; }
}
We didn't find a clean way to intercept the creation of WebForms objects in the MVC framework without rewriting the WebFormsEngine class completely (we're in contact with the MVC team trying to get this relaxed a bit and get a new seam put in there), so I punted and just made a quick call to ObjectFactory.BuildUp(this) in the constructor function to force StructureMap to push in the two setters. It's not ideal, but it's good enough for the moment.
Lastly, how does StructureMap know to set those two properties, and not others? That's where the new setter injection policies come into play. Inside the Registry that we use to bootstrap our MVC application we have a setter policy that just says "always inject IUrlRegistry and IContainer":
public class WebCoreRegistry : Registry
{
public WebCoreRegistry()
{
SetAllProperties(x =>
{
x.OfType<IUrlRegistry>();
x.OfType<IContainer>();
});
}
}
The "IContainer" property is just a reference to the current Container object behind ObjectFactory.
* StructureMap uses Reflection.Emit to write out the "InstanceBuilder" classes that invoke constructor functions and apply setter injection to concrete classes. I did it that way partially to gain experience with IL generation for another project I ended up abandoning. Frankly, emitting IL is a humongous PITA. StructureMap 2.5 represents a near rewrite, but I left the emitting code mostly alone with just some minor cleanup. When .Net 4.0 hits with the ability to do much more with Expressions, I'd at least like to try to replace the emitting code with Expression munging.
Posted
Fri, Jan 16 2009 9:41 AM
by
Jeremy D. Miller