Part 10 - Setter Injection

10_setter_injection.png

So, lets have a look at setter injection - for this part I'll be refactoring the example in part 9 ... so if you recall in the last part we had a class that could send messages (to the console at least) - and it had a hard-coded string being used for the format... we'll I think it would be nice to "plug in" different message formatting, if we needed to... so first thing to do is create a necessary abstraction for formatting a message - which I've called the IMessageFormatter interface:


public interface IMessageFormatter
{
string FormatMessage(string from, string to, string body);
}


Now, our old method for formatting a message (as part of the SecretMessageSender class) used to look like this:


public void SendMessage(string to, string body)
{
Console.WriteLine("to: {0}rnfrom: {1}rnrn{2}", to, _from, _encoder.Encode(body));
}


So we just refactor that out into it's own class, like so:


public class DefaultFormatter : IMessageFormatter
{
public string FormatMessage(string from, string to, string body)
{
return string.Format("to: {0}rnfrom: {1}rnrn{2}", to, from, body);
}
}


And update the secret message sender to use this default formatter... by default!


public class SecretMessageSender
{
private readonly IEncoder _encoder;
private readonly string _from;
private IMessageFormatter _formatter = new DefaultFormatter();

public SecretMessageSender(string from, IEncoder encoder)
{
_from = from;
_encoder = encoder;
}

public IMessageFormatter Formatter
{
get { return _formatter; }
set { _formatter = value; }
}

public void SendMessage(string to, string body)
{
string encodedBody = _encoder.Encode(body);
Console.WriteLine(_formatter.FormatMessage(_from, to, encodedBody));
}
}



Now notice we automatically create a default formatter for the class to use... so at this point we can run the program just as in part 9 (Without having to make any changes to the container configuration) and everything works as it did... however, we can also optionally override the message formatter by registering an implementation in the container... the MicroKernel is smart enough to discover any publicly accessible properties with setters, look for any implementations, and inject them if they're registered in the container.



So first off lets create a different implementation - I'm going to use NVelocity (a template engine, which is used for one of the View engines that comes with monorail) this time along with a template - so first we have the alternative message formatter class:


public class NVelocityMessageFormatter : IMessageFormatter
{
private readonly VelocityEngine _velocity;
private readonly Template _template;

public NVelocityMessageFormatter(string templateFile)
{
_velocity = new VelocityEngine();
ExtendedProperties props = new ExtendedProperties();
_velocity.Init(props);
_template = _velocity.GetTemplate(templateFile);
}

public string FormatMessage(string from, string to, string body)
{
VelocityContext context = new VelocityContext();
context.Put("from", from);
context.Put("to", to);
context.Put("body", body);
context.Put("today", DateTime.Now);

StringWriter writer = new StringWriter();
_template.Merge(context, writer);

return writer.ToString();
}
}



And of course, we also need a template file (Which we will call message.vm) - this is what our template will look like:


To:   $to
From: $from
Sent: $today

----------------------

$body

----------------------



And finally the last part of the puzzle is to update the container configuration to include our alternative message formatter:




type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />




service="IoC.Tutorials.Part10.IEncoder, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.SillyEncoder, IoC.Tutorials.Part10" />


service="IoC.Tutorials.Part10.IEncoder, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.NullEncoder, IoC.Tutorials.Part10" />


type="IoC.Tutorials.Part10.SecretMessageSender, IoC.Tutorials.Part10">

SecretMessageSender
alex@bittercoder.com
${encoder.null}


service="IoC.Tutorials.Part10.IMessageFormatter, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.NVelocityMessageFormatter, IoC.Tutorials.Part10">

message.vm





And now if we give it a run, we get this output:



To:   hammet

From: alex@bittercoder.com

Sent: 25/04/2007 6:56:37 p.m.



----------------------



castle is great!



----------------------




So you can now see how a mix of constructor and setter injection can be used to achieve compulsory and optional dependencies - of course, we can also wire up properties to specific implementations - for instance in this last example we could change the config to read:



type="IoC.Tutorials.Part10.SecretMessageSender, IoC.Tutorials.Part10"> SecretMessageSender
alex@bittercoder.com
${encoder.null}
${fancyMessageFormatter}


service="IoC.Tutorials.Part10.IMessageFormatter, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.DefaultFormatter, IoC.Tutorials.Part10" />


service="IoC.Tutorials.Part10.IMessageFormatter, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.NVelocityMessageFormatter, IoC.Tutorials.Part10">

message.vm




Where even though the default message formatter is registered first in the container, the fancy message formatter will be used because it's manually wired up in the messageSender's component parameters.



Next time will be having a look at factories and how to fit in with the Windsor container.
Written on April 25, 2007