Click here to Skip to main content
Click here to Skip to main content
Go to top

WCF over Twitter

, 20 Sep 2009
Rate this:
Please Sign up or sign in to vote.
How to code a TransportBindingElement.

Contents

Introduction

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?

Twitter protocol

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.

Splitting a SOAP message in 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 ".

WCF plumbing

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.

Client side

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?

Server side

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 TwitterInputChannels 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);
}

Seems good so far, let’s try it!

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.

What I didn’t want to show you

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.

Conclusion

Well, that’s all. I hope this article and my last one on Duplex MSMQ have demystified channel programming on WCF a little bit.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Nicolas Dorier
Software Developer Freelance
France France
I am a trainer and a curious developer.
 
CEO of AO-IS, we created a tool to make IaaS on Azure more easy IaaS Management Studio.
 
If you are interested for working with me, for fun coding stuff, for freelance stuff, or interested in using our cloud training infrastructure freely for a kickass presentation for the dev community ? this way Smile | :)

Comments and Discussions

 
QuestionHow to implement two-way message? Pinmembernan_shuiyu7-Mar-11 15:18 
sos
pumpkin@126.com
 
when i use CompositeDuplexBindingElement in custombinding,the client side is not recevie return message.
 
using two-way transport.The client will receive message from server;
 
thanks!
Working hard!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 20 Sep 2009
Article Copyright 2009 by Nicolas Dorier
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid