torsdag den 22. marts 2012

Working with Facebook Graph API

Facebook is fast becomming the most updated source of information on a lot of companies, especially in the entertainment industry. This post will help you get event data from Facebook onto your own site.

First thing you need to do is get familiar with Graph API Explorer: https://developers.facebook.com/tools/explorer

It's a wonderful tool that will help you construct your API calls. For this tutorial we will only work with events. Events are however one of the most "fragmented" types of data Facebook offers. To get a full list of events, including all data, for a given page we need to make quite a few requests.

Next thing to do is to setup access to an accesstoken. Facebook requires this token for all API calls outside of Graph Explorer. Basicly what we need is ClientSecret and ClientID. Both of those can be obtained from your Facebook App. If you dont have a Facebook App you should read this article: https://developers.facebook.com/docs/authentication/server-side/

Now we have all our basics in place. I will be using a nightclub called Fabric London for this tutorial.

We will be making a lot of call to Facebook, so the first thing we need to create is a method that can call a URL and return the JSON string.

/// 
        /// Gets stream of data from a given website URL
        /// 
        /// URL of the website        /// String
        public static String GetWebStream(String url)
        {
                // used to build entire input
                var sb = new StringBuilder();

                // used on each read operation
                byte[] buf = new byte[8192];

                // prepare the web page we will be asking for
                var request = (HttpWebRequest)
                    WebRequest.Create(url);

                HttpWebResponse response = null;
                try
                {
                // execute the request
                response = (HttpWebResponse)
                    request.GetResponse();
                }
                catch (Exception e)
                {
                    //Handle error
                }

                // we will read data via the response stream
                var resStream = response.GetResponseStream();

                string tempString = null;
                var count = 0;

                do
                {
                    // fill the buffer with data
                    count = resStream.Read(buf, 0, buf.Length);

                    // make sure we read some data
                    if (count != 0)
                    {
                        // translate from bytes to ASCII text
                        tempString = Encoding.ASCII.GetString(buf, 0, count);

                        // continue building the string
                        sb.Append(tempString);
                    }
                }
                while (count > 0); // any more data to read?

                return sb.ToString();
        }

from here on we can obtain our access token using GetWebStream method
//Get a new and valid authtoken
                var AuthToken =
                    WebStream.GetWebStream("https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id=" +
                                 AppID + "&client_secret=" + AppSecret);

With a valid access token we can now get the first part of our event stream. I have added a few extra parameters to limit it to current events (from today and 1 month forward). We also only want the next 10.

var stream = WebStream.GetWebStream("https://graph.facebook.com/FabricLondon/events?since=today&until=1 month&limit=10&" + AuthToken);

You should now have your stream variable containing a lightweight list of Events held at FabricLondon. But these events are missing certain parameters such as the image, description and how many have signed up already. To dig further into the events we will need to serialize the JSON stream into C# objects. Since MVC3 doesn't ship with a JSON serializer for this sort of task you will need to find one. I personally am very fond of JSON.NET (http://json.codeplex.com/) which makes it very easy to serialize and deserialize between C# objects and JSON.

To serialize we will need to recreate the JSON in C# Classes. You can use the following for events:
private class AttendingCollection
        {
            public List<attending> data { get; set; }
        }

        private class Attending
        {
            public String name { get; set; }
            public String id { get; set; }
            public String attending { get; set; }
        }

        private class LightEventCollection
        {
            public List<lightevent> data { get; set; }
        }

        private class LightEvent
        {
            public String name { get; set; }
            public String start_time { get; set; }
            public String end_time { get; set; }
            public String location { get; set; }
            public String id { get; set; }
        }

        private class Event
        {
            public String id { get; set; }
            public String name { get; set; }
            public String description { get; set; }
            public String start_time { get; set; }
            public String end_time { get; set; }
            public String location { get; set; }
            public String ImageURL { get; set; }

            public Venue venue { get; set; }
            public int Attendees { get; set; }
        }

        private class Venue
        {
            public String latitude { get; set; }
            public String longitude { get; set; }
            public String id { get; set; }
        }

        private class EventViewModel
        {
            public String ID { get; set; }
            public String Description { get; set; }
            public String StartTime { get; set; }
            public String EndTime { get; set; }
            public String ImageURL { get; set; }
            public String Name { get; set; }
            public Venue Venue { get; set; }
            public int Attendees { get; set; }
        }

        private class EventViewModelCollection
        {
            public List<eventviewmodel> events { get; set; }
        }
The ViewModels are our custom made Events that hold all the event info we gather from Facebook.


The stream we got from facebook only contains lightweight events, and we will need to serialize those and get the full events for each of the lightweights:
var lightWeightEvents = JsonConvert.DeserializeObject<lighteventcollection>(stream);
var events = GetEventsGraph(lightWeightEvents.data.ToList(), authToken);

private static IEnumerable<event> GetEventsGraph(IEnumerable<lightevent> lightEvents, String authToken)
        {
            var events = new List<event>();
            foreach (var lightEvent in lightEvents)
            {
                var stream = WebStream.GetWebStream("https://graph.facebook.com/" + lightEvent.id + "&" + authToken); 
                var detailedEvent = JsonConvert.DeserializeObject<event>(stream);

                //Get the amount of people signed up for the event
                var attendee = WebStream.GetWebStream("https://graph.facebook.com/" + lightEvent.id + "/attending?limit=999999&" + authToken);
                var attendees = JsonConvert.DeserializeObject<attendingcollection>(attendee);
                detailedEvent.Attendees = attendees.data.Select(m => m.attending == "attending").Count();
                
                events.Add(detailedEvent);
            }
            return events;
        }
What this method does is iterate across all our lightweights, calling facebook for the full details aswell as getting the list of attendees. Once it's un we can return our full list of events and start creating our custom class.

Here we have our full method:

public static String GetEventViewModels(String stream, String authToken)
        {
            var lightWeightEvents = JsonConvert.DeserializeObject<lighteventcollection>(stream);
            var events = GetEventsGraph(lightWeightEvents.data.ToList(), authToken);
            var viewModels = events.Select(item => new EventViewModel()
                                                       {
                                                           ID = item.id, 
                                                           Description = item.description, 
                                                           StartTime = item.start_time, 
                                                           EndTime = item.end_time, 
                                                           Name = item.name, 
                                                           ImageURL = item.ImageURL, 
                                                           Venue = item.venue,
                                                           Attendees = item.Attendees
                                                       }).ToList();
             viewModels.Sort((x, y) => DateTime.Parse(x.StartTime).CompareTo(DateTime.Parse(y.StartTime)));
            var col = new EventViewModelCollection()
                       {
                           events = viewModels
                       };

            return JsonConvert.SerializeObject(col);
        }
We end it up by serializing the data back into JSON, ready to send to your view or client.

For the full FacebookEvent class see this pastebin: http://pastebin.com/jXheywqi

tirsdag den 20. marts 2012

Caching JSONP requests in MVC3

JSONP is quite nice for situations where you have a large number of client running from different domains, and they all need data from you.

MVC3 does not support JSONP natively. An employee from Microsoft called Ranju has blogged his approach to adding JSONP to MVC3. I like his implementation and it's a pretty straight forward copy/paste to use in your projects. Check it out here: blogorama.nerdworks.in

However if the data takes too long to generate you might want to cache the output.

This is where you run into problems when the client uses standard ways to request JSONP data. The callback parameter has to be made static.

This is the standard jQuery behaviour to fetch JSONP:
$.getJSON("http://YourSite/Controller/GetJSONP?callback=?")

The '?' is automaticly set to a unique value by jQuery which will result in either 2 things for a normal Cacheoutput

  • Your cache is bypassed since your parameters are changing by each request
  • The client breaks because the callback you return isnt the one it stated in it's URL
This is one way to fix these issues:
Instead of using $.getJSON() use $.getScript() instead. With getScript you can specify a static callback parameter.
$.getScript(" http://YourSite/Controller/GetJSONP?callback=DataDownloaded", function(data) {
alert('done'); 
});

function  DataDownloaded (data)
{
alert('done');  
} 

jQuery still adds a unique ID to the actual URL but we can work past that.

This is our method in our controller:
[OutputCache(Duration = 60*30 /* 30mins */, VaryByParam = "callback")]
public JsonpResult GetJSONP(String _, String callback)
{
//Your code here
}
It takes 2 parameters. '_' is generated by jQuery as a unique id. Callback is what we manually set to a static value.

I then added the OutputCache annotation, set the duration to 30 minutes and set it to vary only by "callback".

The result is a cache is built for every unique callback parameter, but jquerys '_' parameter is ignored.

Our result is a slender, fast and working cache that works with JSONP

Update 4/4-2012.
Another way to aproach the problem is to deal with it clientside. By using jQuery.ajax and it's cache setting you can remove the timestamp property alltogether.

cacheBoolean
Default: true, false for dataType 'script' and 'jsonp'
If set to false, it will force requested pages not to be cached by the browser. Setting cache to false also appends a query string parameter, "_=[TIMESTAMP]", to the URL.
More info here: http://api.jquery.com/jQuery.ajax/
Thank you Marc Brooks for this information

mandag den 19. marts 2012

Localizing enums in MVC3

Enums are great for a developer to easily remember and set various constants. Sometimes you also need to present the enum to your user. Often this is done with a Listbox or a Combobox where the user chooses between the enums. By default .Net4 doesn't support localizing or even changing the text values to make it readable and understandable by your users.

Here's how you can create an enum, localize it and present it i your view.

Create your Enum:
public enum FacebookModules
        {
            Feed = 1,
            Posts,
            Events,
            Gallery
        }
Create an extension to the enum class and add a new description value to it:
public class LocalizedEnumAttribute : DescriptionAttribute
    {
        private PropertyInfo _nameProperty;
        private Type _resourceType;

        public LocalizedEnumAttribute(string displayNameKey)
            : base(displayNameKey)
        {

        }

        public Type NameResourceType
        {
            get
            {
                return _resourceType;
            }
            set
            {
                _resourceType = value;

                _nameProperty = _resourceType.GetProperty(this.Description, BindingFlags.Static | BindingFlags.Public);
            }
        }

        public override string Description
        {
            get
            {
                //check if nameProperty is null and return original display name value
                if (_nameProperty == null)
                {
                    return base.Description;
                }

                return (string)_nameProperty.GetValue(_nameProperty.DeclaringType, null);
            }
        }
    }

    public static class EnumExtender
    {
        public static string GetLocalizedDescription(this Enum @enum)
        {
            if (@enum == null)
                return null;

            string description = @enum.ToString();

            FieldInfo fieldInfo = @enum.GetType().GetField(description);
            DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attributes.Any())
                return attributes[0].Description;

            return description;
        }
    }

You can now add your new attribute to your enums by using this annotation:
[LocalizedEnum("Facebook_Feed", NameResourceType = typeof(Resources.Resources))]

Your enum should look a bit like this:
public enum FacebookModules
        {
            [LocalizedEnum("Facebook_Feed", NameResourceType = typeof(Resources.Resources))]
            Feed = 1,

            [LocalizedEnum("Facebook_Posts", NameResourceType = typeof(Resources.Resources))]
            Posts,

            [LocalizedEnum("Facebook_Events", NameResourceType = typeof(Resources.Resources))]
            Events,

            [LocalizedEnum("Facebook_Gallery", NameResourceType = typeof(Resources.Resources))]
            Gallery
        }

From here on you can simply call the method MyEnumInstance.GetLocalizedDescription() to get your localized name. If no ressource file is setup or the value can't be found in it, it will default to the LocalizedEnum name.

Now if you want to use this in a standard combobox in your MVC view you can do something like this:

@Html.DropDownListFor(model => model.ContentType, Enum.GetValues(typeof(Enums.FacebookModules)).Cast<enums.facebookmodules>().Select(v => new SelectListItem
                       {
                           Text = v.GetLocalizedDescription(),
                           Value = v.ToString().Replace("_", " ")
                       }))

Thats it!