CloudTopia: Connecting o365, Apps, Azure and Cortana – Part 5

In Part 4 of this series we looked at the integration with various Azure services in the CloudTopia app. In this part we are going to explore all of the integration that was done with Office 365 and how we did it.

Here's some quick links to the whole series:

  1. Intro
  2. Open Graph
  3. Web API
  4. Azure Integration
  5. Office 365 Integration
  6. Cortana, Speech and Windows Phone Integration

Let’s start by looking at all of the different integration that was done with o365 and then we’ll examine each one in more detail:

  • SharePoint App that runs in the Events host site

  • Create hidden SharePoint list to track all of the events

  • Create new sites for each new event

  • Remove the out of box Site Feed web part as each new event site is created

  • Add script editor web part with JS to render Yammer Open Graph item discussion

  • Create, read, update and delete entries from the hidden SharePoint list of events

 

SharePoint App

Of course the piece that drives CloudTopia is the SharePoint App. Let’s look first at how everything got package, deployed and installed. I started out by going to one of the existing o365 sites I had and just navigating to the /_layouts/15/appregnew.aspx page to create a new client ID and secret for my application. I then updated my AppManifest.xml file with the client ID, and added the client ID and secret to the web.config file for my web project.

With that configuration data in hand, I deployed my web project to its Azure web site using the publishing profile, as I described in Part 4 of this series. I then published my SharePoint App, which really just created a .app file for me. I uploaded my .app file to the App Catalog for my o365 tenant and installed into my Events site. My permissions in the app were for the full enchilada – Full rights in the Tenant. This is because I need to be able to create new site collections in the tenant, add and remove web parts, etc.

 

Create Hidden List

When the app installed it fired a remote event receiver that created the list for tracking events, made it hidden, and removed it from the left Quick Nav bar in o365. The complete steps and relevant code for doing that were described in my previous post here: https://blogs.technet.com/b/speschka/archive/2014/05/07/create-a-list-in-the-host-web-when-your-sharepoint-app-is-installed-and-remove-it-from-the-recent-stuff-list.aspx.

 

Create New Sites

The code for creating new sites borrowed liberally from the Office365 Development Patterns and Practices project (OfficeDev PnP) at https://github.com/OfficeDev/PnP. It’s part of the bigger process that I alluded to in Part 1 of this series that occurs when someone clicks a couple of buttons and says create me a new event site. Under the covers that makes a request to the REST endpoint we created for this action and these four activities happen:

  • Create a new site

  • Get the Url and create a new Open Graph item

  • Record the Url, event name, event date, and TwitterTag data in the SharePoint list

  • Record the Yammer Open Graph ID, o365 Url, and Twitter tags data in SQL Azure

I’ve already covered creating the new Open Graph item in Part 2 of this series, so I’ll just focus on the other items in the list. In terms of creating a new site, it’s probably better to just review the content from the OfficeDev PnP repo. In CloudTopia the primary modification I made to their code was to check to ensure that the Url was available, and if not add an incrementally larger number to the end of it until I found a Url that is available. For example if you had 10 “Crazy Sell-a-thon” events they couldn’t all have the same Url. In addition to that, even after a site is deleted it’s not really deleted – not at first – so you need to check deleted sites for Url availability as well. Here’s the code I used to ensure a uniquely available Url: 

//create the client context for the admin site

//the token is obtained in code not shown here,

//webUrl is a parameter to this method, and

//tenantAdminUri is from code in this method

//that is not shown here, but represents the admin

//site for the tenant, i.e.

// https://yourTenantName-admin.sharepoint.com

//uniqueUrl is just an integer initialized to 0

//baseUrl is initialized to webUrl

using (var adminContext =

TokenHelper.GetClientContextWithAccessToken(

tenantAdminUri.ToString(), token))

{

var tenant = new Tenant(adminContext);

 

//look to see if a site already exists at that Url; if it does then create it

bool siteExists = true;

 

while (siteExists)

{

try

{

//look for the site

Site s = tenant.GetSiteByUrl(webUrl);

adminContext.Load(s);

adminContext.ExecuteQuery();

 

//if it exists then update the webUrl and

//do the while loop again;

                     //if it doesn’t exist it will throw an exception                           

uniqueUrl += 1;

webUrl = baseUrl + uniqueUrl.ToString();

}

catch

{

try

{

//doesn't exist, need to check deleted sites too

DeletedSiteProperties dsp =

                                  tenant.GetDeletedSitePropertiesByUrl(webUrl);

adminContext.Load(dsp);

adminContext.ExecuteQuery();

 

//if it exists then update the webUrl

//and do the while loop again                           

uniqueUrl += 1;

webUrl = baseUrl + uniqueUrl.ToString();

}

catch

{

//okay it REALLY doesn't exist, so go ahead

//and grab this url and set the flag to

//exit the while loop

siteExists = false;

}

}

}

 

//now we can create the site using the webUrl

//follow OfficeDev PnP and use

//SiteCreationProperties here

                      

Once the site is created I can go ahead and add the Url and other information to the hidden list in SharePoint so that it shows up in my list of events. That code is pretty simple and looks like this:

using (ClientContext ctx = TokenHelper.GetClientContextWithAccessToken

(hostUrl, accessToken))

{

//get our event list

List eventsList = ctx.Web.Lists.GetByTitle(LIST_NAME);

 

//create the list item

ListItemCreationInformation ci = new ListItemCreationInformation();

ListItem newItem = eventsList.AddItem(ci);

 

newItem["Title"] = eventName;

newItem["SiteUrl"] = siteUrl;

newItem["EventName"] = eventName;

newItem["EventDate"] = eventDate;

newItem["TwitterTags"] = twitterTags;

newItem["ObjectGraphID"] = objectGraphID;

 

//update the list item

newItem.Update();

 

//add the item to the list

ctx.ExecuteQuery();

}

Now, finally, I’ll go ahead and add the data to SQL Azure. There’s absolutely nothing new here, this is basically ADO.NET code from earlier this century…but, for completeness here you go:

//record the OG ID, OG URL, and TwitterTag data in SQL

using (SqlConnection cn = new SqlConnection(conStr))

{

cn.Open();

 

SqlCommand cm = new SqlCommand("addEvent", cn);

cm.CommandType = CommandType.StoredProcedure;

 

cm.Parameters.Add(new SqlParameter("@ObjectGraphID", Double.Parse(gi.object_id)));

cm.Parameters.Add(new SqlParameter("@ObjectGraphUrl", newUrl));

cm.Parameters.Add(new SqlParameter("@TwitterTags", se.twitterTags));

cm.Parameters.Add(new SqlParameter("@EventName", se.eventName));

cm.Parameters.Add(new SqlParameter("@EventDate", se.eventDate));

 

cm.ExecuteNonQuery();

 

cn.Close();

}

 

Remove Site Feed Web Part

The code to remove the Site Feed web part was really pretty much just pulled from the OfficeDev PnP project. I’ll include an abbreviated version of it here so you get an idea of how it looks:

//create the client context

using (ClientContext ctx =

TokenHelper.GetClientContextWithAccessToken(SiteUrl, token))

{

ctx.Load(ctx.Web, w => w.RootFolder, w => w.RootFolder.WelcomePage,

w => w.ServerRelativeUrl);

ctx.ExecuteQuery();

 

Microsoft.SharePoint.Client.File webPage =

ctx.Web.GetFileByServerRelativeUrl(ctx.Web.ServerRelativeUrl +

ctx.Web.RootFolder.WelcomePage);

 

ctx.Load(webPage);

ctx.Load(webPage.ListItemAllFields);

ctx.ExecuteQuery();

 

string wikiField = (string)webPage.ListItemAllFields["WikiField"];

 

LimitedWebPartManager wpm =

              webPage.GetLimitedWebPartManager(

Microsoft.SharePoint.Client.WebParts.PersonalizationScope.Shared);

 

//remove the OOB site feeds web part

WebPartDefinitionCollection allParts = wpm.WebParts;

ctx.Load(allParts);

ctx.ExecuteQuery();

 

for (int i = 0; i < allParts.Count; i++)

{

WebPart almostDeadPart = allParts[i].WebPart;

ctx.Load(almostDeadPart);

ctx.ExecuteQuery();

 

if (almostDeadPart.Title == "Site Feed")

{

allParts[i].DeleteWebPart();

ctx.ExecuteQuery();

break;

}

}

Add Script Editor Web Part to Render Yammer Open Graph Discussion

This chunk of code was pretty nice and really speaks to the flexibility that you get with the Yammer JavaScript Embed library. I actually created the code for it in a simple HTML page, and then copied into a script editor web part I added to a page in a SharePoint site. Once I validated that it was all working there I exported the web part and copied everything out and plugged it into my CloudTopia code. This code runs in the same abbreviated code block I showed above for removing the web part, so the steps are remove the Site Feed web part, then add the Script Editor web part and populate it with JavaScript that pulls from the Open Graph item discussion. Here’s what that looks like (it got it’s ClientContext from the code shown above):

const string URL_REPLACE = "$URL$";

 

string partTxt = @"<webParts>

  <webPart xmlns=""https://schemas.microsoft.com/WebPart/v3"">

    <metaData>

      <type name=""Microsoft.SharePoint.WebPartPages.ScriptEditorWebPart, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"" />

      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>

    </metaData>

    <data>

      <properties>

        <property name=""ExportMode"" type=""exportmode"">All</property>

        <property name=""HelpUrl"" type=""string"" />

        <property name=""Hidden"" type=""bool"">False</property>

        <property name=""Description"" type=""string"">Allows authors to insert HTML snippets or scripts.</property>

        <property name=""Content"" type=""string""> &lt;script type=""text/javascript""

        src=""https://assets.yammer.com/assets/platform_embed.js""&gt;&lt;/script&gt;

 

    &lt;div id=""embedded-feed"" style=''height:400px;width:500px;''&gt;&lt;/div&gt;

 

    &lt;script&gt;

 

        yam.connect.embedFeed({

            container: ""#embedded-feed"",

            network: ""yammo.onmicrosoft.com"",

            feedType: ""open-graph"",

            objectProperties: {

                url: ""$URL$""

            }

        });

    &lt;/script&gt;

</property>

        <property name=""CatalogIconImageUrl"" type=""string"" />

        <property name=""Title"" type=""string"">Script Editor</property>

        <property name=""AllowHide"" type=""bool"">True</property>

        <property name=""AllowMinimize"" type=""bool"">True</property>

        <property name=""AllowZoneChange"" type=""bool"">True</property>

        <property name=""TitleUrl"" type=""string"" />

        <property name=""ChromeType"" type=""chrometype"">None</property>

        <property name=""AllowConnect"" type=""bool"">True</property>

        <property name=""Width"" type=""unit"" />

        <property name=""Height"" type=""unit"" />

        <property name=""HelpMode"" type=""helpmode"">Navigate</property>

        <property name=""AllowEdit"" type=""bool"">True</property>

        <property name=""TitleIconImageUrl"" type=""string"" />

        <property name=""Direction"" type=""direction"">NotSet</property>

        <property name=""AllowClose"" type=""bool"">True</property>

        <property name=""ChromeState"" type=""chromestate"">Normal</property>

      </properties>

    </data>

  </webPart>

</webParts>";

 

partTxt = partTxt.Replace(URL_REPLACE, SiteUrl);

 

Microsoft.SharePoint.Client.File webPage =

ctx.Web.GetFileByServerRelativeUrl(ctx.Web.ServerRelativeUrl +

ctx.Web.RootFolder.WelcomePage);

 

ctx.Load(webPage);

ctx.Load(webPage.ListItemAllFields);

                   

string wikiField = (string)webPage.ListItemAllFields["WikiField"];

 

LimitedWebPartManager wpm =

webPage.GetLimitedWebPartManager(

Microsoft.SharePoint.Client.WebParts.PersonalizationScope.Shared);

 

//add the new part

WebPartDefinition wpDef = wpm.ImportWebPart(partTxt);

WebPartDefinition wp = wpm.AddWebPart(wpDef.WebPart, "wpz", 1);

 

ctx.Load(wp);

ctx.ExecuteQuery();

 

//run some other clean up code from OfficeDev PnP to get the

//web part displaying correctly; see their project for those

//details

 

Read, Update and Delete Items from the Hidden SharePoint List

The code in the other REST controller methods are really so generic and mirror the code I’ve already shown so closely that there’s not really a point to show them as well. If you’re really interested then please go to the repo on GitHub for this project and review it up there.

Okay, we’ve now wrapped up all of the code in the CloudTopia application. If you’ve read everything so far, congratulations – good job! As it turns out though there is one other piece of application integration I did for CloudTopia, but it’s not in the cloud – it’s on a phone. In the next and final part of this series we’ll look at writing a Windows Phone app that uses speech, voice recognition, and of course, Cortana!

 

CloudTopia Part 5.docx