Reputation: 413
I have an object which needs a dependency injected into it
public class FootballLadder
{
public FootballLadder(IMatchRepository matchRepository, int round)
{
// set initial state
this.matchRepo = matchRepository;
this.round = round;
}
public IEnumerable<LadderEntry> GetLadderEntries()
{
// calculate the ladder based on matches retrieved from the match repository
// return the calculated ladder
}
private IMatchRepository matchRepo;
private int round;
}
For arguments sake, lets assume that I can't pass the round parameter into the GetLadderEntries call itself.
Using StructureMap, how can I inject the dependency on the IMatchRepository and set the initial state? Or is this one of those cases where struggling against the framework is a sign the code should be refactored?
Upvotes: 1
Views: 780
Reputation: 172646
Most DI frameworks allow you to inject primitives in constructors as Spinon showed you. When possible, I try to refactor my code in a way that I don't need complex configurations. Often this makes my application code the most understandable, with the least surprises (low number of WTFs per minute ;-)). You have to balance this out carefully, because sometimes complex configurations could make your application code simpler.
Here are some possible suggestions for refactorings:
1) Use a factory:
Using a factory is useful when clients must control the round
value:
public interface IFootballLadderFactory
{
FootballLadder CreateNew(int round);
}
This way you can inject a IFootballLadderFactory
and allow clients to call:
var ladder = this.footballLadderFactory.CreateNew(3);
2) Use a property:
You can remove the round
argument from the constructor and change it in to a get/set property. This is both useful when clients must be able to control the round
value or when using a factory:
public class FootballLadder
{
private IMatchRepository matchRepo;
public FootballLadder(IMatchRepository matchRepository)
{
}
public int Round { get; set; }
}
And implementation of the IFootballLadderFactory
for instance could look like this:
public class CastleFootballLadderFactory : IFootballLadderFactory
{
public IWindsorContainer Container;
public FootballLadder CreateNew(int round)
{
var ladder = this.Container.Resolve<FootballLadder>();
ladder.Round = round;
return ladder;
}
}
Or a client could set the Round
property:
public class Client
{
public Client(FootballLadder ladder)
{
ladder.Round = 3;
}
}
Please be careful with this last example. The client should normally not have to care about the lifetime of the dependency. In this case, we're changing the state of an injected dependency. This prevents us from changing the lifetime of this dependency, because in that case the state of the ladder
instance, could be changed from under the client's feet. Besides this, the FootballLadder
class should throw an InvalidOperationException
when the Round
was never set. I think such a check is nice and clean, but does make you write a bit more code.
3) Inject a IRoundProvider
into the FootballLadder
constructor:
As Spinon wrote, you can implement a IRoundProvider
, but instead of using it in your configuration, you can use it as constructor argument.
public class FootballLadder
{
private IMatchRepository matchRepo;
private int round;
public FootballLadder(IMatchRepository matchRepository,
IRoundProvider roundProvider)
{
this.round = roundProvider.GetRound();
}
}
4) Create a sub type specific for your DI configuration:
public class DIFootballLadder : FootballLadder
{
private const int Round = 3;
public DIFootballLadder(IMatchRepository matchRepository)
: base(matchRepository, Round)
{
}
}
Now you can register the it as follows:
x.For<FootballLadder>().Use<DIFootballLadder>();
Downside of this is that you have this extra code with itself is plain configuration code. Besides that, when the dependencies of the FootballLadder
change, you have to change the DIFootballLadder
as well.
I hope this helps.
Upvotes: 1
Reputation: 10847
You can always use constructor parameters for default values. I used the following for a default instance of a sqlconnection.
this.For<SqlConnection>().Use(c => new SqlConnection(ConfigurationManager.ConnectionStrings["conn"].ConnectionString));
There are other ways as well but I don't remember them off the top of my head.
EDIT: Here is another way it could be done as well. I found this one from here: http://www.theabsentmindedcoder.com/2010/05/structure-map-26-constructor-arguments.html
x.For<MatchRepository>().Use<MatchRepository>();
x.For<IFootballLadder>().Use<FootballLadder>()
.Ctor<int>("round")
.Is(3);
If the value of round was determined from a method you could specify it with a lambda expression to load the value like so
.Is(c => c.GetInstance<IRoundProvider>().GetRound())
Hope this makes sense. But to answer your question yes it is possible and pretty easily.
Upvotes: 2