Click here to Skip to main content
15,867,835 members
Articles / Programming Languages / C#

WCF over Twitter

Rate me:
Please Sign up or sign in to vote.
4.97/5 (32 votes)
20 Sep 2009Ms-PL7 min read 57.6K   393   42   16
How to code a TransportBindingElement.

Image 1

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:

Image 2

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.

Image 3

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.

Image 4

Finally, this is how it works:

Image 5

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:

C#
protected override void OnOpen(TimeSpan timeout)
{
    provider.Open();
}

I will explain Address.ApplyTo(message) at the end of the article:

C#
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.

C#
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.

Image 6

Without any surprise, this is the code of TwitterChannelFactory:

C#
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.

C#
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).

C#
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.

C#
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):

C#
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:

C#
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:

C#
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:

C#
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:

C#
public class HelloWorldService : IHelloWorldService
{
    #region IHelloWorldService Members

    public void SayHello(string name)
    {
        Console.WriteLine("Hello " + name + " !");
    }

    #endregion
}

It seems to work…

Image 7

Let’s check Twitter…

Image 8

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:

C#
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:

Image 9

And surprisingly enough, as you can see, the implementations of these methods were really simple:

C#
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:

C#
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:

C#
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:

C#
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)


Written By
Software Developer Freelance
France France
I am currently the CTO of Metaco, we are leveraging the Bitcoin Blockchain for delivering financial services.

I also developed a tool to make IaaS on Azure more easy to use IaaS Management Studio.

If you want to contact me, go this way Smile | :)

Comments and Discussions

 
QuestionProtobuf + WCF Pin
makerofthings71-Aug-14 16:58
makerofthings71-Aug-14 16:58 
AnswerRe: Protobuf + WCF Pin
Nicolas Dorier1-Aug-14 22:04
professionalNicolas Dorier1-Aug-14 22:04 
GeneralMy vote of 5 Pin
Paul Conrad14-Mar-12 10:11
professionalPaul Conrad14-Mar-12 10:11 
GeneralMy vote of 5 Pin
ke4vtw7-Feb-12 6:04
ke4vtw7-Feb-12 6:04 
GeneralMy vote of 5 Pin
Win81287-Apr-11 18:47
Win81287-Apr-11 18:47 
QuestionHow to implement two-way message? Pin
nan_shuiyu7-Mar-11 15:18
nan_shuiyu7-Mar-11 15:18 
QuestionHow about this Pin
William Bagley16-Mar-10 9:58
William Bagley16-Mar-10 9:58 
AnswerRe: How about this Pin
Nicolas Dorier16-Mar-10 11:12
professionalNicolas Dorier16-Mar-10 11:12 
GeneralMy vote of 1 Pin
captainplanet012322-Sep-09 3:28
captainplanet012322-Sep-09 3:28 
GeneralRe: My vote of 1 [modified] Pin
Nicolas Dorier22-Sep-09 3:59
professionalNicolas Dorier22-Sep-09 3:59 
GeneralRe: My vote of 1 Pin
paragme30-Oct-09 0:03
paragme30-Oct-09 0:03 
Generalvery nice Pin
Rama Krishna Vavilala20-Sep-09 14:08
Rama Krishna Vavilala20-Sep-09 14:08 
GeneralCool, but not really usable in production scenarios Pin
WillemM20-Sep-09 8:47
WillemM20-Sep-09 8:47 
GeneralRe: Cool, but not really usable in production scenarios Pin
Nicolas Dorier20-Sep-09 9:03
professionalNicolas Dorier20-Sep-09 9:03 
GeneralNeat idea Pin
Daniel Vaughan20-Sep-09 8:44
Daniel Vaughan20-Sep-09 8:44 
GeneralRe: Neat idea Pin
Nicolas Dorier20-Sep-09 9:05
professionalNicolas Dorier20-Sep-09 9:05 

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

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