Soap.SMTP for Indigo Beta 2

A while ago, I wrote an SMTP transport for WSE 2.0. Aaron Skonnard was kind enough to port it to WSE 3.0, and now I've finally gotten around to porting it over to Indigo Beta 2. If you want to just cut to the chase, the code is here.

If you want to use this transport, I should point out that I built this against a random sync of the Indigo sources from late last week. It's basically Indigo Beta 2, but I haven't tried to use it against any of the builds that are currently available publicly. You might be able to use it with the PDC CTP, but I haven't tried it. But don't worry -- Beta 2 will be along Real Soon Now so just keep this around for future reference :)

Using the transport is pretty straightforward -- here's how you would create an output-only custom binding:

   TextMessageEncodingBindingElement encodingElement = new TextMessageEncodingBindingElement();
   SmtpTransportBindingElement transportElement = new SmtpTransportBindingElement();
   transportElement.SmtpServer = "mail.yourserver.com";
   transportElement.RetrySeconds = 30;

   CustomBinding binding = new CustomBinding( encodingElement, transportElement );

You can also plug the MtomMessageEncodingBindingElement in there if you want.

I also wrote up a standard MailBinding that supports two-way communication using STMP for outbound and POP3 for inbound, tied together with Composite Duplex under the hood. That's configurable using something like the following:

<system.serviceModel>
  <bindings>
   <mailBinding>
     <binding name="mailServer" pop3Server="mail.yourserver.com"
               smtpServer="mail.yourserver.com"
              userName="soapin@yourserver.com" password="aPassword"
              retrySeconds="25"
              supportsDuplex="true" />
    </mailBinding>
  </bindings>
</system.serviceModel>

Using this binding, you can do correlated request/response over mail (although it takes a while... :) ). There's a sample project included in the archive which demonstrates that, so go there for more detail. 

A couple notes about addressing in this version: the current model is mailbox-per-service. Really mailbox-per-endpoint, because I haven't settled on a strategy for dealing with path information in the To: uri. I have a couple of ideas in mind but it's not supported yet in this drop. The downside is that I'm also not very good about throwing AddressAlreadyInUseException, so you're on the honor system for now. Sorry. Also, the POP3 user name does double-duty as the From/ReplyTo address when correlated. Keep in mind that the client binding is also subject to the one-mailbox-per-endpoint rule for the time being.

Regarding the implementation...

There's not much left of the original WSE implementation, modulo the Mailbox abstraction that I use to asynchronously poll the server. In the Indigo version, this underlies a pretty standard datagram transport implementation -- there's an ChannelListener that creates IInputChannels, a ChannelFactory that creates IOutputChannels, and a binding element and some configuration goo to tie them all together. From the top down, it looks a lot like the UDP transport implementation that Kenny wrote -- it's only at the bottommost receive loop that you notice tangible differences.

The basic idea behind the listener implementation is to abstract the polling-based nature of the transport from higher layers of the architecture. The Mailbox class is responsible for polling the server every n seconds until at least one message shows up in the inbox. The ChannelListener initiates this polling via Mailbox.BeginReceive(), and will block until a message arrives (which might span multiple polls). When messages arrive, the ChannelListener creates an InputChannel (if one doesn't already exist) and pushes the downloaded messages into the new channel. The channel stays parked in the ChannelListener until someone calls AcceptChannel(), and the messages stay parked in the channel until someone pulls them off via successive calls to channel.Receive().

One class that's really helpful here is the InputQueue, which I blatantly stole from Kenny. The InputQueue is basically just a buffer in which you can stick things, knowing that other people can pull them off when they get around to it. It's different than the Queue implementation in System.Collections.Generic because the dequeue operation can be done asynchronously -- callers waiting for an item can block efficiently until an item arrives in the queue. Architecturally, it makes it really easy to convert from the "push" model preferred by the polling implementation to the "pull" model preferred by rest of the transport stack. From an implementation perspective, you can think of it as something that does the bulk of the heavy lifting around the asynchronous model for you (most of the time you can get away with just delegating methods like BeginReceive() to the equivalent method on the InputQueue). 

Because I had the hard parts already done from the WSE stuff, this wasn't much more than a Saturday afternoon project. The config boilerplate took almost as long as the rest of the transport code (or maybe it just seemed that way, because config code ain't much fun). I did run into a couple of issues relating to encoders, but those were solved by being diligent about my buffer lengths and making sure to use the BufferManager properly.

There are plenty of things that could be done to make this design better, but it's in good enough shape for a sample. I haven't done too much testing on it so please let me know if you find bugs. Happy hacking!

Download:Net.Hyperthink.Samples.SoapSmtp.Indigo.zip

#1 Dennis Mulder on 11.15.2005 at 12:38 AM

Now if only we had Indigo beta 2... :) When will it become available?

#2 Steve Maine on 11.19.2005 at 10:43 AM

Dennis...this should work swimmingly against the recently release November CTP of WinFX. If it doesn't, let me know :)