So, I got some crazy idea that I wanted to take another stab at writing a facebook application. A friend at work was showing off his WPF app that utilizes Netflix webservices and it gave me an idea about a facebook app. Unfortunately, someone already wrote my idea (http://apps.facebook.com/mynetflix ).
I got the idea of how to use wcf from this twitter app example on Kirk Evans Blog
(here's the code class library minus the config file Download Here)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="FacebookConfig">
<security mode="None"/>
</binding>
</webHttpBinding>
</bindings>
<client>
<endpoint
address="http://api.facebook.com/restserver.php"
binding="webHttpBinding"
bindingConfiguration="FacebookConfig"
contract="Facebook.Service.IFacebookService"
name="FacebookClient" />
</client>
</system.serviceModel>
</configuration>
My service contract ended up looking like this after a few times banging on my head getting an error that said “No POST data to POST” when trying WebInvoke Attribute.
[ServiceContract(Namespace=http://api.facebook.com/1.0/)]
[XmlSerializerFormat(Style= OperationFormatStyle.Document,Use=OperationFormatUse.Literal)]
public interface IFacebookService
{
[OperationContract]
[WebGet(
UriTemplate="?method=admin.getBannedUsers&api_key={apiKey}&sig={sig}&v=1.0",
BodyStyle=WebMessageBodyStyle.Bare)]
Message getBannedUsers(string apiKey, string sig);
[OperationContract]
[WebGet(
UriTemplate="?method=admin.banUsers&api_key={apiKey}&sig={sig}&v=1.0&uids={userIds}",
BodyStyle=WebMessageBodyStyle.Bare)]
Message banUsers(string apiKey, string sig, string userIds);
[OperationContract]
[WebGet(
UriTemplate="?method=admin.unbanUsers&api_key={apiKey}&sig={sig}&v=1.0&uids={userIds}",
BodyStyle=WebMessageBodyStyle.Bare
)]
Message unbanUsers(string apiKey, string sig, string userIds);
}
I chose these 3 methods because they do not require a session so I can just pass in my API key without needing a facebook signon to test my mad experiments.
So let’s move on to actually using this thing and the speed bumps I ran into there.
This is a work in progress, so far I’ve only done the “admin.unbanUsers” call
public class FacebookCustomProxy
{
private readonly WebChannelFactory<IFacebookService> channelFactory;
private readonly IFacebookService facebookChannel;
public FacebookCustomProxy(string endpointName)
{
channelFactory =
new WebChannelFactory<IFacebookService>(endpointName);
facebookChannel = channelFactory.CreateChannel();
}
[Obsolete]
public Message getBannedUsers(string apiKey, string sig)
{
return facebookChannel.getBannedUsers(apiKey, sig);
}
[Obsolete]
public Message banUsers(string apiKey, string sig, string userIds)
{
return facebookChannel.banUsers(apiKey, sig, userIds);
}
public Message unbanUsers( long[] userIds)
{
Dictionary<string, string> args = new Dictionary<string, string>();
args.Add("method", "admin.unbanUsers");
args.Add("api_key", "<INSERT YOUR API_KEY HERE>");
args.Add("uids",JasonMe(userIds));
args.Add("v","1.0");
string sig = MakeSig(args);
return facebookChannel.unbanUsers(args["api_key"], sig, args["uids"]);
}
string JasonMe(long[] arrayOfLongs)
{
StringBuilder builder = new StringBuilder("[");
bool adComma = false;
foreach (long val in arrayOfLongs)
{
if (adComma)
{ builder.Append(","); }
builder.Append(val.ToString());
adComma = true;
}
builder.Append("]");
return builder.ToString();
}
string MakeSig(IDictionary<string, string> parameters)
{
StringBuilder signatureBuilder = new StringBuilder();
// Sort the keys of the method call in alphabetical order
List<string> keyList = new List<string>(parameters.Keys);
keyList.Sort();
// Append all the parameters to the signature input paramaters
foreach (string key in keyList)
signatureBuilder.Append(String.Format(CultureInfo.InvariantCulture, "{0}={1}", key, parameters[key]));
// Append the secret to the signature builder
signatureBuilder.Append("<INSERT YOUR APP SECRET HERE>");
MD5 md5 = MD5.Create();
// Compute the MD5 hash of the signature builder
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(signatureBuilder.ToString().Trim()));
// Reinitialize the signature builder to store the actual signature
signatureBuilder = new StringBuilder();
// Append the hash to the signature
foreach (byte hashByte in hash)
signatureBuilder.Append(hashByte.ToString("x2", CultureInfo.InvariantCulture));
return signatureBuilder.ToString();
}
}
The first thing I ran into was I tried to get back just a string or a specific strongly typed object. But I kept running into problems with that. One, I couldn’t deserialize into a string, and two, sometimes I would get back <admin_unbanUsers_response> but sometimes I’d get back <error_response>. So I ended up using System.ServiceModel.Channels.Message class.
The second thing I hit was getting “Invalid Sig” errors from facebook.php. props for the MakeSig method to midowazzan on this facebook forum post
Lastly, I had issues because I wasn’t sending a JSON array to facebook. Unfortunately facebook doesn’t use a regular JSON array like this: {“user ids” : [378173,738127]} they use one that looks like this “[47387483,4783743]”, which is ok, just strange and you have no way of knowing without pouring through the awful wiki site.
Finally the code to actually execute this one method becomes trivial:
FacebookCustomProxy proxy = new FacebookCustomProxy("FacebookClient");
Message msg = proxy.unbanUsers( new long[]{789789,12344});
string result = msg.ToString();
Console.WriteLine(result);