Contents
The purpose of this article is learning for fun how to code your own TransportBindingElement
in WCF. There is no practical reason to use Twitter as a transport media for SOAP messages; do not try this at your company (at home, it’s OK).
You can consult the project on CodePlex. To make the code work, you just need two accounts on Twitter which follow each other, and modify the app.config file. (You can run the unit tests to see if it works.)
Everybody knows Twitter! Well, at least everybody here knows Twitter. So it makes things simple to explain. A crazy idea crossed my mind one day. In reality, it was from someone who wanted to remotely control his computer via Twitter. As a .NET developer, when I hear the word "remote", I think Web Services, I think WCF.
After reading this article, Twitter seemed to be a very reliable transport media.
…And if I could send SOAP messages over Twitter?
I’m not a user of Twitter, because I don’t socialize, and I prefer good old MSN, and yes, that was the first time I used Twitter! So my first task was to analyze an everyday conversation on Twitter.
This is the sample I used to reverse engineer the Twitter protocol:
@Nico and what is your IP address ?
@Admin 127.0.0.1
@Nico are you… […]
@Nico kidding?
@Admin, I’m not an administrator, I’m a developer!
"@Nico, " is the header of a message to specify the recipient of the message. "[…]" is the footer of a message which means that the message is split in several Twits. My first question was: Why split a message? I didn’t understand why in the latest sample. But an expert told me that a Twit is limited to 140 characters. A hello world SOAP message has more than 140 characters.
After rigorous profiling, I found out that I need 3-5 Twits to send a Hello World SOAP message. ~40 Twits when the message is encrypted with the service’s certificate.
Obviously, I needed a way to split the SOAP message in several Twits.
I didn’t know how to send a message on Twitter, but I knew that it wouldn’t be a problem. So my first reflex was to create an interface TwitterProvider
:
Twit
is just a class with the content of the message inside, but it ensures that the content’s length is not more than 140.
I worked on a mock in my tests, and later I found a project: MiniTwitter on CodePlex. It does what it’s supposed to do very well; "it just works", so I just created my implementation with this API.
Now, I wanted to create a class which takes a string instead of a Twit to send a message on Twitter, and automatically splits this message in Twits and forwards them to the provider.
Finally, this is how it works:
I won’t explain the implementation details of my class TwitterApi
, the TransportBindingElement
is far more interesting!
Keep in mind that TwitterApi
can support other Twitter protocols if you want, for example using "…" instead of "[…]" or "To Nico " instead of "@Nico ".
I highly recommend you to read the section called WCF Plumbing of my last article about Duplex MSMQ (it’s not very long, and not MSMQ related, it’s just pure WCF stuff). You’ll then better understand what I’m doing.
My binding will support IOuputChannel/IInputChannel
; this means that we’ll be able to send messages only in OneWay.
So first, what is a IOutputChannel
anyway?
I won’t show you the entire interface, because you will be really scared and you’ll run away… I prefer to show you only the important parts. I will come back on everything I hide to you at the end of my article. I promise you, it’s not that hard.
The purpose of IOutputChannel
is to send a Message
.
So, only two methods are really interesting to me: Open()
and Send(Message message)
.
For my class TwitterOutputChannel
(which implements IOutputChannel
), in the Open
method, I call open on my instance of MiniTwitterProvider
to authenticate on Twitter.
Now, I need to find a way to serialize the message passed to the Send
method to a String
and send that String
on Twitter.
Theoretically, I should use BindingParameters
to retrieve the EncodingBindingElement
passed by the BindingElement
stack (as explained in my article Duplex MSMQ).
Practically, Twitter only accepts to send plain text, so I just instantiate TextMessageEncodingBindingElement
and serialize my message with that.
Here is the code:
protected override void OnOpen(TimeSpan timeout)
{
provider.Open();
}
I will explain Address.ApplyTo(message)
at the end of the article:
TwitterApi.TwitterApi _Api;
public void Send(Message message)
{
Address.ApplyTo(message);
MemoryStream memory = new MemoryStream();
new TextMessageEncodingBindingElement().CreateMessageEncoderFactory().
Encoder.WriteMessage(message, memory);
memory.Position = 0;
StreamReader reader = new StreamReader(memory);
String messageStr = reader.ReadToEnd();
_Api.Send(messageStr, this.RemoteAddress.Uri.Host);
}
In my constructor, I just instantiate my class TwitterApi
with MiniTwitterProvider
.
public TwitterOutputChannel(TwitterChannelFactory factory, EndpointAddress address)
: base(factory)
{
_RemoteAddress = address;
var creds = factory.Binding.Credentials;
provider = new MiniTwitterProvider(creds.UserName, creds.Password);
_Api = new TwitterApi.TwitterApi(provider)
{
ContinuationString = factory.Binding.ContinuationString,
ToUserFormat = factory.Binding.ToUserFormat
};
Address = address;
}
ContinuationString
is the string at the end of a Twit to specify that it is not complete and that another Twit should follow (by default "[…]").
ToUserFormat
is by default "@{0} "; it’s the header of every Twit message and it specifies the recipient of the message.
If you have read my last article, you should know that the class which has the responsibility to create TwitterOutputChannel
is TwitterChannelFactory
.
This sequence diagram shows you that the method CreateChannel
of TwitterChannelFactory
is called when a client calls ChannelFactory<IServiceType>.CreateChannel
. This method returns a proxy, and every method called on it will be translated to a Message
and the Send(Message)
method of my OutputChannel
will be called. Again, I really recommend you to read the section called "WCF plumbing" on Duplex MSMQ. This diagram is simplified, it doesn't show the Binding
class and how the BindingElements
stack works.
Without any surprise, this is the code of TwitterChannelFactory
:
protected override IOutputChannel OnCreateChannel(
System.ServiceModel.EndpointAddress address, Uri via)
{
return new TwitterOutputChannel(this, address);
}
And finally, the object which is responsible to create TwitterChannelFactory
is my TwitterTransportBindingElement
!
I clone the BindingElement
before passing in to TwitterChannelFactory
, because a channel manager is not expected to modify a BindingElement
.
public override IChannelFactory<TChannel>
BuildChannelFactory<TChannel>(BindingContext context)
{
if(!CanBuildChannelFactory<TChannel>(context))
ThrowInvalidChannelType<TChannel>();
return (IChannelFactory<TChannel>)new
TwitterChannelFactory((TwitterTransportBindingElement)this.Clone(), context);
}
public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
{
return typeof(TChannel) == typeof(IOutputChannel);
}
That was not so hard after all, was it?
When there is something to send, there is something to receive, that’s the goal of IInputChannel
.
And again, in TwitterInputChannel
, two methods are really interesting: OnOpen()
and Receive()
.
The open method opens the MiniTwitterProvider
(it will try to authenticate on Twitter, and future calls to MiniTwitterProvider.Receive
will only return the most recent Twits from this point).
protected override void OnOpen(TimeSpan timeout)
{
var provider = new MiniTwitterProvider(_Binding.Credentials.UserName,
_Binding.Credentials.Password);
provider.Open();
_Api = new TwitterApi.TwitterApi(provider)
{
ContinuationString = _Binding.ContinuationString,
ToUserFormat = _Binding.ToUserFormat
};
}
And, the Receive()
method will block on TwitterAPI.Receive()
until an entire SOAP message is received from Twitter. And then, I just need to deserialize the SOAP message to a Message
object using TextMessageEncodingBindingElement
.
public Message Receive()
{
String message = _Api.Receive();
MemoryStream memory = new MemoryStream();
StreamWriter writer = new StreamWriter(memory);
writer.Write(message);
writer.Flush();
memory.Position = 0;
var soapMess = new TextMessageEncodingBindingElement().
CreateMessageEncoderFactory().Encoder.ReadMessage(memory, 999999);
return soapMess;
}
You might guess that TwitterChannelListener
will create this channel. That’s right, but the code is a little more subtle (not to say it's a boilerplate code).
In theory, a ChannelListener
can create more than one channel. The classic example is TcpChannelListener
. TCPChannelListener
will create a channel (IReplyChannel
) by using a TCP socket, and each channel will listen for incoming requests of their clients.
But TwitterChannelListener
just needs to listen to a specific Twitter account (the account of the server). So, I must find a way to limit the number of TwitterInputChannel
s for each TwitterChannelListener
to one and only one at a time.
This explains this boilerplate code (let’s call it subtle):
public TwitterChannelListenner(TwitterTransportBindingElement binding,
BindingContext context)
{
_Binding = binding;
_Uri = new Uri(context.ListenUriBaseAddress,
context.ListenUriRelativeAddress);
}
void _Channel_Closed(object sender, EventArgs e)
{
((IInputChannel)sender).Closed -= _Channel_Closed;
_Reset.Set();
}
protected override IInputChannel OnAcceptChannel(TimeSpan timeout)
{
var waited = _Reset.WaitOne(timeout);
if(!waited)
throw new TimeoutException();
_Channel = new TwitterInputChannel(this);
_Channel.Closed += new EventHandler(_Channel_Closed);
return _Channel;
}
And finally, the BuildChannelListener
of TwitterBindingElement
:
public override IChannelListener<TChannel>
BuildChannelListener<TChannel>(BindingContext context)
{
if(!CanBuildChannelListener<TChannel>(context))
ThrowInvalidChannelType<TChannel>();
return (IChannelListener<TChannel>)new TwitterChannelListenner(
(TwitterTransportBindingElement)this.Clone(), context);
}
Let’s run my client:
static void Main(string[] args)
{
Console.WriteLine("Press one key to send a message to the server");
Console.Read();
CustomBinding binding = new
CustomBinding(new TwitterTransportBinding.TwitterTransportBindingElement()
{
Credentials = new System.Net.NetworkCredential(ConfigHelper.ClientAccount,
ConfigHelper.ClientPassword), ContinuationString = "Continuing...",
ToUserFormat = "To {0}"
});
ChannelFactory<IHelloWorldService> facto =
new ChannelFactory<IHelloWorldService>(binding,
new EndpointAddress("twitter://" + ConfigHelper.ServerAccount));
var channel = facto.CreateChannel();
channel.SayHello("Nico");
}
And my server:
static void Main(string[] args)
{
Console.WriteLine("Press one key to start server");
Console.Read();
var bind = new CustomBinding(new TwitterTransportBinding.TwitterTransportBindingElement()
{
Credentials = new System.Net.NetworkCredential(ConfigHelper.ServerAccount,
ConfigHelper.ServerPassword),
ContinuationString = "Continuing...",
ToUserFormat = "To {0}"
});
ServiceHost host = new ServiceHost(typeof(HelloWorldService),
new Uri("twitter://" + ConfigHelper.ServerAccount));
var end = host.AddServiceEndpoint(typeof(IHelloWorldService), bind, "");
end.Behaviors.Add(new SynchronousReceiveBehavior());
host.Open();
Console.WriteLine("Service open");
while(true)
{
}
}
With this implementation:
public class HelloWorldService : IHelloWorldService
{
#region IHelloWorldService Members
public void SayHello(string name)
{
Console.WriteLine("Hello " + name + " !");
}
#endregion
}
It seems to work…
Let’s check Twitter…
It works well… but like I said earlier, I’ve hidden some nasty details in my implementation to not complicate the explication. Now I will show you this nasty stuff.
Have you noticed this line when I open the service:
end.Behaviors.Add(new SynchronousReceiveBehavior());
It’s here because I have not implemented the asynchronous methods on TwitterChannelListener
and TwitterInputChannel
.
As you can see below, IInputChannel
and IOutputChannel
have a lot of methods to implement, and as I’ve told you, only a few are really interesting in my example. Here is the thing I didn't want to show you:
And surprisingly enough, as you can see, the implementations of these methods were really simple:
public Message EndReceive(IAsyncResult result)
{
throw new NotImplementedException();
}
public bool EndTryReceive(IAsyncResult result, out Message message)
{
throw new NotImplementedException();
}
public bool EndWaitForMessage(IAsyncResult result)
{
throw new NotImplementedException();
}
For the channel managers, it's the same problem (even if we have some base implementations provided by Microsoft). However, there are two asynchronous methods always called by WCF, so I implemented them.
ChannelListener
:
protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout,
AsyncCallback callback, object state)
{
return new CompletedAsyncResult(callback, state);
}
protected override IInputChannel OnEndAcceptChannel(IAsyncResult result)
{
CompletedAsyncResult.End(result as CompletedAsyncResult);
return OnAcceptChannel(this.DefaultOpenTimeout);
}
ChannelFactory
:
protected override void OnEndOpen(IAsyncResult result)
{
CompletedAsyncResult.End(result);
}
protected override void OnOpen(TimeSpan timeout)
{
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout,
AsyncCallback callback, object state)
{
return new CompletedAsyncResult(callback, state);
}
CompletedAsyncResult
is taken from an example on TransportBindingElement
from Microsoft: http://msdn.microsoft.com/en-us/library/ms751494.aspx, and it just calls the callback synchronously.
The other thing to show you is that TwitterChannelOutput
is responsible to set the To header of the message:
public void Send(Message message)
{
Address.ApplyTo(message);
In reality, I should check TransportBindingElement.ManualAddressing
to know if my channel is responsible to set the To header. I didn't do that because I thought my example would be less comprehensible.
Well, that’s all. I hope this article and my last one on Duplex MSMQ have demystified channel programming on WCF a little bit.