Showing posts with label NServiceBus. Show all posts
Showing posts with label NServiceBus. Show all posts

Saturday, March 10, 2012

NServiceBus 3.0 - SMTP Transport

NServiceBus 3.0 is fresh out of GitHub, and I'm wading through the code and testing out different concepts. You might learn something from this code (the code in this blog post ;), but its far from production stable.

SmtpClient

With NServiceBus 3.0 there is a FtpTransport that enable us to send/receive messages over FTP instead of MSMQ. RFC 2549 is not supported, but before we start with that, lets try something simpler: Send messages using SMTP.

Looking at the code for FtpMessageQueue, ISendMessages and IReceiveMessages are the interfaces that NServiceBus uses for sending and receiving TransportMessages.

.NET includes a SmtpClient, but no POP3 support, so we will now limit ourself to sending messages. Let's start.

The preferred way I like to host NServiceBus applications, are using the NServiceBus.Host.exe. This host has the concept of one input queue and one error queue. At the moment we will not process any input messages (in lack of an pop3 client), but we will send out messages.

Message

It could be a message that would launch a nuclear bomb, brew coffee or buy a movie ticket, but for the example let's use:

public class MyMessage : IMessage
{
  public string Foo;
  public string Bar;
}

IWantToRunAtStartup

There are various ways to interact with the Host, IWantToRunAtStartup is an interface you can decoracte a class with and it will be called at startup. We'll combine the marker interface IConfigureThisEndpoint and IWantCustomInitialization that will setup the smtptransport.

public class Runner :
             IConfigureThisEndpoint,
             IWantToRunAtStartup,
             IWantCustomInitialization
{
  public IBus Bus { get; set; }

  public void Run()
  {
    string line;

    while ((line = Console.ReadLine()) != null)
    {
      Bus.Send(new MyMessage { Bar = "bar", Foo = "foo" });
    }
  }

  public void Stop()
  {
  }

  public void Init()
  {
    Configure
     .With()
     .DefaultBuilder()
     .SmtpTransport()
     .UnicastBus()
     .SendOnly();
    }
}

App.config

The config file is the preferred place to put any configuration that will affect the application; connectionstrings, paths, ....

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="SmtpQueueConfig"
              type="SmtpTransport.SmtpQueueConfig, SmtpTransport" />
    <section name="UnicastBusConfig"
              type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
    <section name="MessageForwardingInCaseOfFaultConfig"
              type="NServiceBus.Config.MessageForwardingInCaseOfFaultConfig, NServiceBus.Core" />
  </configSections>

  <MessageForwardingInCaseOfFaultConfig ErrorQueue="errors"/>

  <SmtpQueueConfig
      SmtpServer = "smtp.gmail.com"
      Port       = "587"
      EnableSsl  = "true"
      From       = "username@gmail.com"
      UserName   = "username@gmail.com"
      Password   = ""/>

  <UnicastBusConfig>
    <MessageEndpointMappings>
      <add Messages="SmtpTransport" Endpoint="username@gmail.com" />
    </MessageEndpointMappings>
  </UnicastBusConfig>

</configuration>

UnicastBusConfig and MessageForwardingInCaseOfFaultConfig is part of NServiceBus, SmtpQueueConfig is created by me and looks like:

public class SmtpQueueConfig : ConfigurationSection
{
  [ConfigurationProperty("SmtpServer", IsRequired = true)]
  public string SmtpServer
  {
    get { return this["SmtpServer"].ToString(); }
    set { this["SmtpServer"] = value; }
  }

  [ConfigurationProperty("Port", IsRequired = true)]
  public int Port
  {
    get { return (int)this["Port"]; }
    set { this["Port"] = value; }
  }

  [ConfigurationProperty("EnableSsl", IsRequired = true)]
  public bool EnableSsl
  {
    get { return (bool)this["EnableSsl"]; }
    set { this["EnableSsl"] = value; }
  }

  [ConfigurationProperty("From", IsRequired = true)]
  public string From
  {
    get { return this["From"].ToString(); }
    set { this["From"] = value; }
  }

  [ConfigurationProperty("UserName", IsRequired = true)]
  public string UserName
  {
    get { return this["UserName"].ToString(); }
    set { this["UserName"] = value; }
  }

  [ConfigurationProperty("Password", IsRequired = true)]
  public string Password
  {
    get { return this["Password"].ToString(); }
    set { this["Password"] = value; }
  }
}

No big surprises here. If you now goes back to the Init method of the Runner class you will see that we call .SmtpTransport() on the Configure class. This is an extension method:

public static class ConfigureSmtpQueue
{
  public static Configure SmtpTransport(this Configure config)
  {
    var smtpQueue = config.Configurer.ConfigureComponent<SmtpMessageQueue>(
                                 DependencyLifecycle.SingleInstance);
    var cfg = Configure.GetConfigSection<SmtpQueueConfig>();

    if (cfg != null)
    {
      smtpQueue.ConfigureProperty(t => t.SmtpServer, cfg.SmtpServer);
      smtpQueue.ConfigureProperty(t => t.Port, cfg.Port);
      smtpQueue.ConfigureProperty(t => t.EnableSsl, cfg.EnableSsl);
      smtpQueue.ConfigureProperty(t => t.From, cfg.From);
      smtpQueue.ConfigureProperty(t => t.UserName, cfg.UserName);
      smtpQueue.ConfigureProperty(t => t.Password, cfg.Password);
    }

    return config;
  }
}

It's the binding between the configuration and our transport class. Then finally:

SmtpMessageQueue

public class SmtpMessageQueue : ISendMessages, IReceiveMessages
{
  public string SmtpServer { get; set; }
  public int Port { get; set; }
  public bool EnableSsl { get; set; }
  public string From { get; set; }
  public string UserName { get; set; }
  public string Password { get; set; }

  public void Send(TransportMessage message, Address address)
  {
    using (var client = new SmtpClient(SmtpServer, Port))
    {
      client.EnableSsl = EnableSsl;
      client.Credentials = new NetworkCredential(UserName, Password);

      var serializer = new JavaScriptSerializer();

      var mailMessage = new MailMessage(From, address.ToString())
                        {
                          Subject = "NServiceBus TransportMessage",
                          Body = serializer.Serialize(CreateMailMetaData(message));
                        };

      mailMessage.Attachments.Add(
        new Attachment(
          new MemoryStream(message.Body),
          "body.bin",
          "application/octet-stream"));

          client.Send(mailMessage);
        }
    }

  private MailMetaData CreateMailMetaData(TransportMessage message)
  {
    return new MailMetaData
    {
      CorrelationId = message.CorrelationId,
      Id = message.Id,
      IdForCorrelation = message.IdForCorrelation,
      ReplyToAddress = message.ReplyToAddress.ToString(),
      TimeToBeReceived = message.TimeToBeReceived,
      Header = message.Headers.Select(
        x => new HeaderPair
             {
               Name = x.Key,
               Value = x.Value }).ToList()
      };
  }

  public void Init(Address address, bool transactional)
  {
    // TODO: add a pop3 client and poll for new messages
  }

  public bool HasMessage()
  {
    return false;
  }

  public TransportMessage Receive()
  {
    return null;
  }
}

public class MailMetaData
{
  public string CorrelationId;
  public string Id;
  public string IdForCorrelation;
  public string ReplyToAddress;
  public TimeSpan TimeToBeReceived;
  public List<HeaderPair> Header;
}

public class HeaderPair
{
  public string Name;
  public string Value;
}

Does it work? This is the email in gmail:

Yes! We'll fix the pop3 / receiver next week.

Friday, June 10, 2011

Starting up with NServiceBus

After many years with a inhouse made API on top of MSMQ, its time to evaluate other frameworks that gives us more out of the box.

NServiceBus looks like a great candidate.

  • Easy install

  • Extensible

  • Strongly typed messages using generics

  • Support for unit testing


The first problem was to use the StructureMap container in a strong name solution. The NServiceBus.ObjectBuilder.StructureMap262.dll extension have no stong name so I had to decompile and assemble it again with a keypair.

This is done using ildasm/ilasm.

sn -k test.snk

ildasm NServiceBus.ObjectBuilder.StructureMap262.dll /out=NServiceBus.ObjectBuilder.StructureMap262.il

ilasm NServiceBus.ObjectBuilder.StructureMap262.il /dll /key=test.snk /output=NServiceBus.ObjectBuilder.StructureMap262-sn.dll


Then its over to programming.

Message
We define a message with some properties that are sent from the client to the handler/server/service

public class PingMessage : NServiceBus.IMessage
{
public string Text;
}


Handler
Then a handler to consume these messages:

public class PingMessageHandler : NServiceBus.IHandleMessages<PingMessage>
{
public ILog Logger = LogManager.GetLogger(typeof(PingMessageHandler));

public void Handle(PingMessage message)
{
Logger.Debug(message.Text);
}
}


Unit testing
Normally I would have made Logger static readonly but we want to access this in our unit test

[TestFixture]
public class PingMessageHandlerFixture
{
[Test]
public void SendMessage()
{
Test.Initialize(typeof(PingMessage).Assembly);
var logger = A.Fake<log4net.ILog>();

Test.Handler<PingMessageHandler>()
.WithExternalDependencies(x => x.Logger = logger)
.OnMessage<PingMessage>(x => x.Text = "hello world");

A.CallTo(() => logger.Debug("hello world")).MustHaveHappened();
}
}


And the test goes green!

Great start :)

References:
www.nservicebus.com
fakeiteasy