Click here to Skip to main content
Click here to Skip to main content

WCF over Twitter

By , 20 Sep 2009
 

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)

About the Author

Nicolas Dorier
Software Developer Freelance
France France
Member
I develop to make you forget what's between you and your work.
 
I teach and, with delight, you'll see that the best castles are done with the dumbest concepts.

Curiosity is the best teacher.
 
If you are interested for working with me, this way Smile | :)

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberPaul Conrad14 Mar '12 - 10:11 
Very well done.
GeneralMy vote of 5memberke4vtw7 Feb '12 - 6:04 
Cool stuff! Just because it's not practical to do this in production doesn't invalidate the value of the code as a learning tool. Keep it up... and you earned a 5 from me!
GeneralMy vote of 5memberWin81287 Apr '11 - 18:47 
Good stuff
QuestionHow to implement two-way message?membernan_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!

QuestionHow about thismemberWilliam Bagley16 Mar '10 - 9:58 
I came across this article because I was asked how hard it would be to create a WCF transport that used SQL Server CE and merge replication. Think of it as similar to using MSMQ except that we have more direct control of the synchronization. Client would be a Windows Mobile device.
 
I have never built a WCF transport before. Based on your Twitter and MSMQ article, can you give me your opinion of:
a) how dumb is this idea?
a) will it even work?
AnswerRe: How about thismemberNicolas Dorier16 Mar '10 - 11:12 
First be sure NetMsmqBinding or MsmqIntegrationBinding are not supported by Window Mobile.
System.Messaging (the msmq API) is supported by mobile device...
 
If the bindings are not supported maybe you can do your own binding based on MSMQ.
 
I'm not an expert on SQL Server CE and Merge replication, so I can't really say if it's a bad idea.
The best would remain to be able to use MSMQ.
 
Will it even work ?
Well, the client must be able to "get" messages, so during the synchronization the server should send relevant messages to the client database.
After that you can poll the db or use some triggers to get messages with WCF at the transport binding level. Theorically it seems a good idea.
 
As I said, I'm not fully aware how work Merge replication so I don't know if it's hard to do.
 
In either way, it's a really interesting use case. MSMQ bindings on mobile device would be a good idea since MSMQ is so well suited in disconnected environment... Not sure you can though. (And if you want to create your own binding, I bet they are not supported)
GeneralMy vote of 1membercaptainplanet012322 Sep '09 - 3:28 
This is THE most ridiculous thing. Find something a bit more productive next time... Thanks!
GeneralRe: My vote of 1 [modified]memberNicolas Dorier22 Sep '09 - 3:59 
Thanks to say why, you vote 1.
Now, I think that's the most ridiculous reason to vote 1, I want you to learn something it's you to turn this learning to something productive. In other words : use your brain and imagination to make it useful.
 
modified on Tuesday, September 22, 2009 10:12 AM

GeneralRe: My vote of 1memberparagme30 Oct '09 - 0:03 
I don't know what the problem is with this guy, but I loved the code, 5 from me!
 
Best Regards,
Parag Mehta
iparag.com

Generalvery nicememberRama Krishna Vavilala20 Sep '09 - 14:08 
and creative. Smile | :)
GeneralCool, but not really usable in production scenariosmemberWillemM20 Sep '09 - 8:47 
Cool sample, but it's not anywhere near usable in production scenarios.
 
You will need too many tweets to send a single message. I've seen quite alot of WCF services in production and they don't do Hello World. They send millions of messages sometimes over a very short period of time.
 
I don't know if this is violating any terms of use on twitter's end, but I can imagine that they don't like it when you use twitter for this Wink | ;)
 
Nonetheless, I love your creativity! It is still a good example on how to build your own channels, message encoders, etc in WCF.
You got my 5 for it.
 

GeneralRe: Cool, but not really usable in production scenariosmemberNicolas Dorier20 Sep '09 - 9:03 
yeah thanks for your feedback, clearly it's of no use, it's just for fun !
Geeks love useless things Wink | ;)
GeneralNeat ideamemberDaniel Vaughan20 Sep '09 - 8:44 
Nicolas,
This is a funky idea. Hijacking twitter as a transport medium. Cool. I like this kind of exploration.
Have a 5.
 
Cheers,
Daniel
 
Daniel Vaughan
Blog: DanielVaughan.Orpius.com


Company: Outcoder

GeneralRe: Neat ideamemberNicolas Dorier20 Sep '09 - 9:05 
I hope Twitter won't hate me for doing that Big Grin | :-D

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 20 Sep 2009
Article Copyright 2009 by Nicolas Dorier
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid