Yammer – SharePoint, beyond the Yammer App for SharePoint – Leverage Open Graph from SharePoint

Hi,

If you didn’t read my previous blog posts on integrating Yammer and SharePoint, I encourage you to do so since I’m not going to repeat all the basic steps you need to undertake before going ahead. In this post, we’re going to see how to surface SharePoint activities in Yammer using Open Graph.
First thing first, Open Graph is a protocol that is adopted by Facebook, Yammer and Linkedin. The goal of the Open Graph protocol is to render an object into a Social Media in a specific way using some specific HTML meta tags. When you share a link on Facebook and Linkedin, the system performs an HTTP request to the page targeted by the link and tries to render it in the Social Media. It will analyze in priority whether the page contains Open Graph meta tags or not. By default, it is intended to share anonymously reachable pages…However, since my post is about sharing pages from SharePoint online (SPO) to Yammer, anonymous isn’t really an option here. Fortunately, Yammer’s Open Graph will not try to read the target page if all the Open Graph data is transmitted when posting the activity. Therefore, it’s still possible to share protected pages.

That said, in Yammer, the Open Graph API isn’t only about rendering content in a specific way, it’s also a way to surface custom and out of the box activities in the newsfeed. In this blog post, we’re going to create a SharePoint provider-hosted App that registers Remove Event Receivers (RER) to a host web list named Calendar in order to surface the creation of the event into Yammer. As we did with my workflow example, we’ll impersonate the SharePoint user to act on Yammer on his behalf. What happens under the hood is the creation of a new object in Yammer that has its own lifecycle and to which people can start attaching documents and have conversations about. For sake of simplicity, I will not handle all the events nor will I explain everything in detail about SharePoint stuff or I’d need to make a blog post of 10 pages..

Now, let’s create our custom Yammer action and object. In order to do that, you need to go to https://www.yammer.com/client_applications. I’ve shown it many times in my other posts but if you didn’t read them, go read the Getting Started one where I explain how to register an App in Yammer and how to retrieve the Global Access Token (GTA) that we’ll use afterwards. So, if you followed my other posts, you should already have a SPO application in your Yammer environment:
yammer17
By clicking on the SPO app, you should see a Open Graph on the left hand side. Click on it, register a namespace, I used spo. Define a namespace and click directly on Use. In the section labelled Object Types, add the singular and plural forms of your object along with the type:
yammer18
Do the same with the action, which is a verb where you need to specify the present and past forms:
yammer19
Click on save for both. Don’t worry if you see the turning wheel turning for ages…Once done, you should now have the spo:event object type and the spo:shedule action type defined. If you used another namespace, it will just be yournamespace:event and yournamespace:schedule.
Now, let’s get started with the code by performing the following actions:

  • Create a new Visual Studio project of type App for SharePoint and choose Provider-Hosted
  • Launch the properties of the SharePoint Project and set Handle App Installed property to true

The purpose of this post is not to explain RER since there is plenty of documentation about that. In a nutshell, if you want to be able to debug/test it from a local environment, you need to enable Azure Service Bus debugging. To do that, perform the following actions:

  • Go to the Azure Management Portal
  • If you already have a service bus, click on it and copy the Connection Information (ACS). If you don’t already have a service bus, create one and copy the connection info
  • In Visual Studio, launch the SP project properties ==> SharePoint ==> Enable Service Bus and paste the connection info

Now, you can come back to the code that was created by enabling the App Installed event. Here we’re going to do the following:

  • Register a RER on ItemAdded for a list called Calendar
  • Define the Yammer activity data that we will send to Yammer
  • Implement the code that handles item added in order to push this event to Yammer via Open Graph

For sake of simplicity, I don’t handle all the other events nor the App Uninstalling event because our focus is more Yammer related than SharePoint. Ok, let’s first define the entry point of the RER:

public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
{
    SPRemoteEventResult result = new SPRemoteEventResult();

    switch (properties.EventType)
    {
        case SPRemoteEventType.AppInstalled:
            OnAppInstalled(properties);
            break;
        case SPRemoteEventType.ItemAdded:
            OnnItemAdded(properties);
            break;
    }
    return result;
}

we just trap the event type in order to launch the corresponding event. Remember that in AppInstalled, we will add our event receiver to the Calendar list which will basically register the same web service so that the same ProcessEvent method will be called later on whenever we add an item to the calendar.
Let’s now define the OnAppInstalled method:

private void OnAppInstalled(SPRemoteEventProperties properties)
{
    using (ClientContext ctx = TokenHelper.CreateAppEventClientContext(properties, false))
    {
        if (ctx != null)
        {
            List CalendarList = ctx.Web.Lists.GetByTitle("Calendar");
            EventReceiverDefinitionCreationInformation receiver =
                new EventReceiverDefinitionCreationInformation();
            receiver.EventType = EventReceiverType.ItemAdded;
            receiver.ReceiverUrl =
                OperationContext.Current.RequestContext.RequestMessage.Headers.To.ToString();
            receiver.ReceiverName = "Yammer";
            receiver.Synchronization = EventReceiverSynchronization.Synchronous;
            CalendarList.EventReceivers.Add(receiver);
            ctx.ExecuteQuery();
        }
    }
}

This code is the bare minimum. Again, I’m not handling exceptions etc..to keep it as simple as possible. The important part here is the assignment of the ReceiverUrl property that points to the current service.
Now, we need to define the Yammer data that we will send through whenever a new event gets created in SharePoint. Remember that we have recorded a custom action (spo:schedule) and a custom object type (spo:event). The data to send to Yammer is just a basic JSON feed that we could transmit as a simple string to the HTTP request. However, it’s better to model it a little bit and use JSON libraries to do the work. Here are the classes that will be serialized and sent to Yammer:

public class Actor
{
    public string email { get; set; }
}
public class Object
{
    public string url { get; set; }
    public string title { get; set; }
    public string image { get; set; }
    public string type { get; set; }
    public string description { get; set; }
}
public class Activity
{
    public Activity() { }
    public Actor actor { get; set; }

    [JsonProperty(PropertyName = "object")]
    public Object obj { get; set; }
    public string action { get; set; }

    [JsonProperty(PropertyName = "private")]
    public string priv { get; set; }
}

We have two exceptions to handle here since “object” and “private” are two keywords so we must give them another name and specify how this should be converted to JSON using the JsonProperty attribute. By the way, I’m simply using Newtonsoft.Json. You can add it to your web project via Nuget. Now, the last thing to do is to implement the OnItemAdded method:

private void OnnItemAdded(SPRemoteEventProperties properties)
{
    StringBuilder EventTitle = new StringBuilder();

    EventTitle.Append((properties.ItemEventProperties.AfterProperties["Title"] != null) ?
        properties.ItemEventProperties.AfterProperties["Title"].ToString() : "");
    EventTitle.Append("(");
    EventTitle.Append((properties.ItemEventProperties.AfterProperties["EventDate"] != null) ?
        properties.ItemEventProperties.AfterProperties["EventDate"].ToString().Substring(0,10) + " - " : " - ");
    EventTitle.Append((properties.ItemEventProperties.AfterProperties["EndDate"] != null) ?
        properties.ItemEventProperties.AfterProperties["EndDate"].ToString().Substring(0, 10) + " - " : " - ");
    EventTitle.Append(")");
    string UserEmail=properties.ItemEventProperties.UserLoginName.Substring(
        properties.ItemEventProperties.UserLoginName.LastIndexOf("|") + 1);

    string TargetUserIdUrl =
        string.Concat("https://api.yammer.com/api/v1/users/by_email.json?email=",
            UserEmail);

    string ImpersonationToken=
        MakeYammerRequest(
            string.Concat("https://api.yammer.com/api/v1/oauth/tokens.json?consumer_key=app_client_id&user_id=",
                MakeYammerRequest(TargetUserIdUrl, null, WebConfigurationManager.AppSettings["GAT"],"id")),
            null,
            WebConfigurationManager.AppSettings["GAT"],
            "token");

    Activity act = new Activity();
    act.priv = "false";
    act.action = "spo:schedule";
    act.obj = new Object
    {
        title = EventTitle.ToString(),
        image = "https://cdn3.iconfinder.com/data/icons/linecons-free-vector-icons-pack/32/calendar-64.png",
        type = "spo:event",
        description = string.Empty,
        url = string.Format("{0}Lists/Calendar/DispForm.aspx?ID={1}&IsDlg=1",
            properties.ItemEventProperties.WebUrl,properties.ItemEventProperties.ListItemId)
    };

    act.actor = new Actor
    {
        email = UserEmail
    };
    JsonSerializer js = new JsonSerializer();
    StringWriter sw = new StringWriter();
    dynamic o = new
    {
        activity = act
    };
    js.Serialize(sw, o);
    MakeYammerRequest("https://api.yammer.com/api/v1/activity.json",
        sw.ToString(), ImpersonationToken, null);           

}

This piece of code requires a little more explanation. We first start by extracting metadata information from the event that was created in SharePoint via the AfterProperties dictionary. We need to know who is the author of the event, for that, we retrieve the e-mail information from the UserLoginName (it would be nicer to get an instance of a user object but again, out of scope of this post). TargetUserIdUrl is the url of the Yammer endpoint we need to call in order to retrieve the Yammer user id that will be required afterwards to get an impersonation token as we want to post the event to Yammer on behalf of the Calendar item author. Right after, we get the ImpersonationToken I’ve just mentioned. In order to get the impersonation token, we need a Global Access Token (GAT) that I stored in the web.config file of the service. We also need the consumer_key parameter which is the client_id of the Yammer App (see my previous posts if you don’t know how that works). Then, we create an instance of the Activity class in order to prepare the data that must be sent to Yammer. Note that the important things here are the action and the object type where we specify our custom values. Also, an important thing is the url property of the object which uniquely identifies it. It means that if you repost something using the same URL, it will not create a new object but rather update it. You can define private & public activities. For instance, you can set private to “true” and define a list of persons who should be notified by including a Users array. Last step is to send the data to Yammer. All the HTTP requests are performed using a helper method:

private string MakeYammerRequest(string url,string data,string token,string key)
{
    HttpWebRequest YammerRequest = HttpWebRequest.Create(
        url) as HttpWebRequest;
    YammerRequest.ContentType = "application/json; odata=verbose";
    YammerRequest.Accept = "application/json; odata=verbose";
    YammerRequest.Headers.Add("Authorization", string.Concat("Bearer ", token));
    if(!string.IsNullOrEmpty(data))
    {
        YammerRequest.Method = "POST";
        byte[] ByteData = UTF8Encoding.UTF8.GetBytes(data);
        YammerRequest.ContentLength = ByteData.Length;
        Stream YammerRequestStream = YammerRequest.GetRequestStream();
        YammerRequestStream.Write(ByteData, 0, ByteData.Length);
    }            

    HttpWebResponse YammerResponse = YammerRequest.GetResponse() as HttpWebResponse;
    dynamic resp = JsonConvert.DeserializeObject(
        new StreamReader(YammerRequest.GetResponse().GetResponseStream()).ReadToEnd());

    return (key != null) ? resp[0][key] : null;
}

The Yammer endpoints that we address to post the event are:

  • users/by_email.json to get the Yammer user id corresponding to the SharePoint user’s email address
  • oauth/tokens.json to get a token in order to act on behalf of that user
  • activity.json to post the activity on behalf of that user using the token we retrieved at the previous step

Once you have posted the new activity, Yammer responds to you with the object ID if you want to keep things in sync, such as reporting back this ID to SharePoint.
Note that there is a .NET SDK for Yammer but it’s still at a very early stage with almost no documentation. Depending on what you need to do with Yammer, it might be interesting but don’t forget that at the end of the day, it’s only a matter of sending HTTP requests. The Yammer API documentation is pretty good and for the things you don’t know, you can always monitor the network traffic from Yammer itself to identify endpoints/JSON feeds.

The code is ready, if you followed everything and enabled Service Bus debugging properly, you should be able to test it but don’t forget to create the Calendar list in your SPO site up front.

Happy Coding!

Advertisements

About Stephane Eyskens

Office 365, Azure PaaS and SharePoint platform expert
This entry was posted in Office 365, SharePoint Online, Yammer and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s