Creating Office 365 applications (The Complete Guide 2019)

Creating Office 365 applications

How to Create Office 365 applications

The goal is to create a custom ASP.NET MVC solution that extends Office 365 to create custom digital workplaces to manage business projects.

 

Overall, the solution will give you some guidance on how to accomplish the following tasks:

  1. Creating an Office 365 application by using Microsoft Visual Studio 2015
  2. Configuring the application to act on behalf of the current user via OAuth 2.0 or with an app-only access token
  3. Leveraging the Office UI Fabric to provide a common and well-known user experience to the application users
  4. Using the Microsoft Graph to interact with Office 365 Groups and Microsoft Exchange Online
  5. Using the Microsoft SharePoint REST API to consume SharePoint Online
  6. Leveraging controls and libraries provided by the Office 365 Developer Patterns & Practices (PnP) community project
  7. Leveraging an asynchronous development pattern and a Microsoft Azure WebJob to create more scalable and reliable solutions
  8. Creating an Office 365 Connector to asynchronously interact with an Office 365 Group

 

Most of the topics covered in this blog will be useful for your everyday work when you are developing custom solutions for Office 365.

 

Creating and registering the Office 365 application

The development platform for the sample application will be Microsoft Visual Studio 2015 (Update 2).

 

First, you have to create a new empty solution, which in the samples related to this blog is called BusinessApps.O365ProjectsApp, together with a new project of type ASP.NET web application, which in the current sample is called BusinessApps.O365ProjectsApp.WebApp.

 

You should also select to have read access to directory data so that Visual Studio will register a new Client Secret for your application, from an Open Authorization perspective. This will also enrich the Startup.Auth.cs file with statements to handle not only the OpenID Connect authentication but also the OAuth 2.0 authorization.

 

Try to start your application by pressing F5 in Visual Studio, and you will be prompted for authentication against the Microsoft Azure Active Directory (Azure AD) tenant that is under the cover of your target Office 365 tenant. Just after the authentication phase, you will see the home page of The ASP.NET MVC site.

 

So far, starting the application will give you quite a bitter feeling because the UI will be the one available out of the box for an ASP.NET  MVC application, which is unlike the user interface and experience of Office 365.  Nevertheless, even with the out-of-box ASP.NET MVC UI, you will see the currently logged-in username in the upper-right corner of the screen.

 

Azure AD application general registration

So far, you have created and registered the application in Azure AD. However, to set up the application properly, you probably will also need to configure a custom logo for it. The custom logo will be used in the Office 365 app launcher to show your application.

 

To configure a custom logo, go to the Azure AD tenant under the cover of your Office 365 tenant, open the Applications tab, search for your custom application (by using either the Client ID or the application name), and open the application Configuration tab.

 

At the beginning of the page, you will find the default application logo, and in the lower part of the screen, you will have an Upload Logo button. Click that button and choose a custom logo image, which has to adhere to the following requirements:

 

Image dimensions of 215 × 215 pixels

Central image dimensions of 94 × 94 pixels, with the remainder of the image as a solid field

Supported file types: BMP, JPEG, and PNG

File size less than 100 KB

 

In the current sample, we will use the OfficeDev PnP logo because this sample application will be hosted under the PnP family of samples. After registering the application logo, you can open the browser and go to your Office 365 tenant account. Click the app launcher and select the View All My Apps link in the lower-left corner of the app launcher.

 

There, you will find the full list of native and custom Office 365 applications available to your user, including the just-configured application. If you want to pin the new application to the app launcher, you can click the ellipses in the upper-right corner of the app logo and select the Pin To App Launcher menu item. 

 

If later you would like to remove the application from the app launcher, just come back to the View All My Apps page, select the app, click the ellipses, and select the Unpin From App Launcher menu item.

 

Note

At the time of this writing, is not possible to automate the process of pinning an application to the app launcher. In the future, Microsoft may make an API to automate the process, but this is not guaranteed.

 

App-only authorization registration

The sample application that is built throughout this blog requires you to accomplish some tasks acting as “app-only” from an authorization perspective. Thus, in this section, you will learn how to configure the application in Azure AD to be able to interact with SharePoint Online and the Microsoft Graph with an app-only token.

 

First of all, you will need to create a self-signed X.509 certificate that will be used to authenticate your application against Azure AD while providing the app-only access token. The certificate can be created using the makecert.exe command-line tool, which is included in the Windows SDK. In the following excerpt, you can see the syntax to invoke the makecert command:

 

makecert -r -pe -n "CN=ApplicationName" -b 05/01/2016 -e 05/01/2018 -ss my -len 2048

 

Another option that you have is to leverage the PowerShell cmdlet called New-SelfSignedCertificate, which is newer and more powerful. This option is my favorite because you can easily create a PowerShell script that automates the configuration process. However, if you don’t like PowerShell, or if you are not familiar with it, you can fall back to the makecert option.

 

More info

The PnP Partner Pack is a sample solution provided to the community as an open source project in GitHub. The goal of the PnP Partner Pack is to show how to leverage the patterns, guidance, and tools that PnP provides through a real solution that can be considered a startup project for real business use cases.

 

The main capabilities of the PnP Partner Pack are: it is an Office 365 application; it provides the capability to do self-service site and site collection creation based on PnP provisioning templates; it allows you to save a site as a template (PnP provisioning template) directly from the web UI of SharePoint, and it provides sample jobs for governance purposes.

 

Once you have created the certificate, you can browse to the Configuration page of the Office 365 application in the Azure AD management portal and click the Manage Manifest button, which is available in the lower part of the screen.

 

Select the Download Manifest option, and the browser will start to download a JSON file that represents the manifest of the app. 

 

LISTING The .JSON manifest file of an Office 365 application registered in Azure AD

{
"appId": "74c393a9-b865-48b7-b2b6-0efa7a2305a1",
"appRoles": [],
"availableToOtherTenants": false,
"displayName": "BusinessApps.O365ProjectsApp.WebApp",
"errorUrl": null,
"groupMembershipClaims": null,
"homepage": "https://localhost:44304/",
"identifierUris": [
"https://tenant.onmicrosoft.com/BusinessApps.O365ProjectsApp.WebApp"
],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": false,
"oauth2AllowUrlPathMatching": false,
"oauth2Permissions": [
{
"adminConsentDescription": "Allow the application to access BusinessApps. O365ProjectsApp.WebApp on behalf of the signed-in user.",
"adminConsentDisplayName": "Access BusinessApps.O365ProjectsApp.WebApp",
"id": "11851dd6-95ef-4336-afca-8e8d9212d5ec",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to access BusinessApps. O365ProjectsApp.WebApp on your behalf.",
"userConsentDisplayName": "Access BusinessApps.O365ProjectsApp.WebApp",
"value": "user_impersonation"
}
],
"oauth2RequirePostResponse": false,
"passwordCredentials": [
{
"customKeyIdentifier": null,
"endDate": "2017-04-25T16:18:21.3676329Z",
"keyId": "1892ef1f-f2c3-448c-89df-d3b77cf0628c",
"startDate": "2016-04-25T16:18:21.3676329Z",
"value": null
},
{
"customKeyIdentifier": null,
"endDate": "2017-04-25T15:56:46.1588957Z",
"keyId": "8ff689f8-724a-417f-b754-a11ef88eff88",
"startDate": "2016-04-25T15:56:46.1588957Z",
"value": null
}
],
"publicClient": false,
"replyUrls": [
"https://localhost:44304/"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null,
"extensionProperties": [],
"objectType": "Application",
"objectId": "695258ca-fe41-44f8-8a56-2e3560164e7c",
"deletionTimestamp": null,
"createdOnBehalfOf": null,
"createdObjects": [],
"manager": null,
"directReports": [],
"members": [],
"memberOf": [],
"owners": [],
"ownedObjects": []
}

So far, the interesting part for you is the property named keyCredentials, of type array, which is highlighted in bold. 

 

There are many other useful settings stored within the manifest file, but they are out of the scope of this book. To configure an X.509 certificate as a credential set for the application, you will have to provide a value for the key credentials array.

 

Luckily, by using another PowerShell cmdlet that is available within the set of PowerShell cmdlets of PnP, you will be able to use the following syntax to create the KeyCredentials array from the X.509 certificate that you have just created.

Get-SPOAzureADManifestKeyCredentials -CertPath <path to your .cer file> | clip
This statement will copy onto the clipboard of your machine a JSON excerpt like the following:
"keyCredentials": [
{
"customKeyIdentifier": "<Base64CertHash>",
"keyId": "<KeyId>",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": "<Base64Cert>"
}
],

 

The values <Base64CertHash><KeyId>, and <Base64Cert> are just sample placeholders. In reality, they will hold the corresponding values generated from the X.509 certificate that you generated.

 

You just need to paste that JSON excerpt, replacing the empty keyCredentials array. Then, save the updated manifest file and upload it back to Azure by using the Upload Manifest option, which is available under the Manage Manifest menu item. 

 

Later in this blog, you will learn how to use the certificate to access resources with an app-only access token.

 

Setting Azure AD permissions

Setting up credentials of Office 365 applications enables you to leverage the authorization rules through Azure AD.

 

Thus, it is now time to configure proper permissions for the application both when acting on behalf of the current user and when acting as app-only. You are now ready to implement the solutions, leveraging the services and capabilities provided by Azure AD and the Microsoft Graph.

 

Basic UI elements with Office UI Fabric

A professional and real business-level Office 365 application has to provide the users with a UI/UX that makes them feel like they are using Office 365 and not an external solution. Most users already know how to interact with Office 365 and its core services by leveraging a set of well-known controls and icons.

 

Since late 2015, Microsoft has provided an open source project called Office UI Fabric which provides a rich set of tools, markup, and styles to mimic the UI/UX of Office 365 in any custom software solution. 

 

In the current sample project, the easiest way to use Office UI Fabric is to reference its corresponding NuGet package, which is named office fabric.

 

Here, you can see the short command to install the package using the NuGet Package Manager Console: PM> Install-Package OfficeUIFabric

 

The NuGet package will install all the needed.CSS and.JS files into your project so that you will be ready to benefit from using Office UI Fabric, as you will see in the following paragraphs.

 

Another option you have is to reference those files directly from a content delivery network (CDN). Regardless of how you access the Office UI Fabric files, what matters is what you can do with them.

 

When getting the Office UI Fabric through NuGet, you will have to reference its.CSS and.JS files in the BundleConfig.cs file of the MVC project. Thus, open the BundleConfig.cs file under the App_Start folder and update it according to what is highlighted in bold in Listing.

 

LISTING The updated version of BundleConfig.cs, with added or updated parts highlighted in bold

using System.Web;
using System.Web.Optimization;
namespace BusinessApps.O365ProjectsApp.WebApp { public class BundleConfig {
public static void RegisterBundles(BundleCollection bundles) {
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*"));
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( "~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"));
bundles.Add(new ScriptBundle("~/bundles/fabric").Include( "~/Scripts/jquery.fabric.*"));
bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/bootstrap.css",
"~/Content/Office365SuiteBar.css",
"~/Content/fabric.css",
"~/Content/fabric.components.css",
"~/Content/site.css"));
}
}
}

You need to add the script bundle named ~/bundles/fabric, and you have to add the fabric.css and fabric.components.css files to the default style bundle named ~/Content/css.

 

In Listing, you can also see the Office365SuiteBar.css file, which will be explained in the following section and is not related directly to the Office UI Fabric project.

 

Moreover, you will need to update the shared layout CSHTML file to reference the new Office UI Fabric JavaScript bundle, as you will see in Listing in the next section.

 

Office 365 suite bar and top navigation

The first UI element that you should provide within your application is the Office 365 suite bar, which is the one placed in the upper edge of the screen, with the app launcher, the title of the current application, the current user’s picture and profile, and some other context-related links and menu items.

 

Unfortunately, at the time of this writing, there isn’t a ready-to-go component in the Office UI Fabric to provide the Office 365 suite bar to your custom applications. Maybe it will come in the future, but for now, you have to build it yourself. Of course, you can try to copy and reuse as much as you can from the real suite bar.

 

Nevertheless, it can be a painful task. In Listing, you can see a sample custom layout template for Microsoft ASP.NET MVC, which reproduces a minimalist version of the UI and the behavior of the Office 365 suite bar, excluding the app launcher and some other functionalities.

 

LISTING The CSHTML code of a _Layout.cshtml file that partially mimics the behavior of the Office 365 suite bar

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<meta name=viewport content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title</title>
<script type=text/javascript class='lazy' data-src="https://ajax.aspnetcdn.com/ajax/4.0/1/ MicrosoftAjax.js"></script>
@Scripts.Render("~/bundles/jquery")
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
@Html.Partial("~/Views/Shared/_Office365SuiteBar.cshtml")
@Html.Partial("~/Views/Shared/_Office365NavBar.cshtml")
<div class=scrollableContent>
<div id=mainContent>
<div class=ms-Grid>
<div class=ms-Grid-row>
<div class="ms-Grid-col ms-u-sm1 ms-u-md1 ms-u-lg2"> <img class='lazy' data-src=/AppIcon.png class=siteIcon />
</div>
<div class="ms-Grid-col ms-u-sm11 ms-u-md11 ms-u-lg10"> <h1>@ViewBag.Title</h1>
</div>
</div>
<div class=ms-Grid-row>
<div class="ms-Grid-col ms-u-sm1 ms-u-md1 ms-u-lg2"> </div>
<div class="ms-Grid-col ms-u-sm11 ms-u-md11 ms-u-lg10"> @RenderBody()
</div>
</div>
<div class=ms-Grid-row>
<div class="ms-Grid-col ms-u-sm12 ms-u-md12 ms-u-lg12"> <hr />
<footer>
(C) Office 365 Developers Patterns &amp; Practices,
2016
</footer>
</div>
</div>
</div>
</div>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/fabric")
@RenderSection("scripts", required: false)
<script type=text/javascript>Initialize the NavBar object if($.fn.NavBar){$(".ms-NavBar").NavBar();}</script>
</body>
</html>
As you can see, the SuiteBar is included through a couple of custom MVC partial views. The first one (“~/Views/Shared/_Office365SuiteBar.cshtml”) mimics the top suite bar, while the second one (“~/Views/Shared/_Office365NavBar.cshtml”) is a custom top navigation bar that looks like the top navigation bar of OneDrive for Business by leveraging the Office UI Fabric icons and menu styles.

 

Moreover, there are some statements to include the bundled scripts and styles that we discussed in the previous section. However, in Listing, you can see an interesting excerpt, in which the rendering of the current user’s photo is managed by leveraging the Microsoft Graph API and a custom MVC controller.

 

Moreover, in Listing, you can see the use of the Persona control of Office UI Fabric, which renders a user’s picture and online status by using a well-known layout.

@if (System.Security.Claims.ClaimsPrincipal.Current != null && System.Security.Claims.
ClaimsPrincipal.Current.Identity != null && System.Security.Claims.ClaimsPrincipal.
Current.Identity.IsAuthenticated) {
<div role="banner" aria-label="User settings">
<div class="o365cs-nav-topItem o365cs-rsp-tn-hideIfAffordanceOn"> <div class="ms-Persona ms-Persona--s">
<div class="ms-Persona-imageArea">
<div class="ms-Persona-initials ms-Persona-initials-- blue">@(BusinessApps.O365ProjectsApp.WebApp.Components.MSGraphAPIContext. CurrentUserInitials)</div>
<img class="ms-Persona-image" class='lazy' data-src="/Persona/GetPhoto?

upn=@(BusinessApps.O365ProjectsApp.WebApp.Components.MSGraphAPIContext.CurrentUs

4&width=64" title="@(BusinessApps.O365ProjectsApp.WebApp.Components.MSGraphAPIContext.

CurrentUserDisplayName)">
</div>
<div class="ms-Persona--offline"></div> </div>
</div>
</div>
}

 

As you can see, highlighted in bold there is an IMG element that renders a dynamic image, which is rendered through a custom controller named PersonaController. In Listing, you can see the full implementation of that PersonaController.

 

LISTING The source code of the PersonaController, which renders the current user’s profile picture by using the Microsoft Graph

using BusinessApps.O365ProjectsApp.WebApp.Components; using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using http://System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace BusinessApps.O365ProjectsApp.WebApp.Controllers {
[Authorize]
public class PersonaController : Controller {
public ActionResult GetPhoto(String upn, Int32 width = 0, Int32 height =
0) {
Stream result = null;
String contentType = "image/png";
var sourceStream = GetUserPhoto(upn);
if (sourceStream != null && width != 0 && height != 0) { Image sourceImage = Image.FromStream(sourceStream);
Image resultImage = ScaleImage(sourceImage, width, height);
result = new MemoryStream();
resultImage.Save(result, ImageFormat.Png);
result.Position = 0;
}
else {
result = sourceStream;
}
if (result != null) {
return base.File(result, contentType);
}
else {
return new
HttpStatusCodeResult(system.net - Domain Name For Sale | Undeveloped.HttpStatusCode.NoContent);
}
}
<summary>
This method retrieves the photo of a single user from Azure AD
</summary>
<param name="upn">The UPN of the user</param>
<returns>The user's photo retrieved from Azure AD</returns> private static Stream GetUserPhoto(String upn) {
String contentType = "image/png";
var result = MicrosoftGraphHelper.MakeGetRequestForStream( String.Format("{0}users/{1}/photo/$value",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, upn), contentType);
return (result);
}
private Image ScaleImage(Image image, int maxWidth, int maxHeight) { var ratioX = (double)maxWidth / image.Width;
var ratioY = (double)maxHeight / image.Height; var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(image.Width * ratio); var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(newImage)) graphics.DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
}
}

 

The key implementation is in the GetUserPhoto method, which uses the Microsoft Graph API according to retrieve the binary content of the user’s profile picture. There are also some plumbing functions to resize and convert the image into the proper output format for the target browser.

 

 For the sake of completeness, the sample navigation bar leverages some of the most useful primitives of Office UI Fabric for creating menus, just to show you how to use them.

 

LISTING The CSHTML code of the _Office365NavBar.cshtml file that provides the top navigation bar

<div class="ms-NavBar">
<div class="ms-NavBar-openMenu js-openMenu"> <i class="ms-Icon ms-Icon--menu"></i>
</div>
<div class="ms-Overlay"></div>
<ul class="ms-NavBar-items">
<li class="ms-NavBar-item ms-NavBar-item--search ms-u-hiddenSm"> <div class="ms-TextField">
<input class="ms-TextField-field">
</div>
</li>
<li class="ms-NavBar-item"><a class="ms-NavBar-link" href="/Home/Index"> <i class="ms-Icon ms-Icon--home" aria-hidden="true"></i> Home</a>
</li>
<li class="ms-NavBar-item ms-NavBar-item--hasMenu"> <a class="ms-NavBar-link">Rendering Mode</a>
<i class="ms-NavBar-chevronDown ms-Icon ms-Icon--chevronDown" aria-hidden="true"></i>
<ul class="ms-ContextualMenu">
<li class="ms-ContextualMenu-item">
<a class="ms-ContextualMenu-link">Simple</a></li> <li class="ms-ContextualMenu-item">
<a class="ms-ContextualMenu-link">Normal</a></li> <li class="ms-ContextualMenu-item">
<a class="ms-ContextualMenu-link">Full</a></li>
</ul>
</li>
<li class="ms-NavBar-item"><a class="ms-NavBar-link" href="/Home/StartNewProcess">
<i class="ms-Icon ms-Icon--documentAdd" aria-hidden="true"></i> Start New Process</a></li>
<li class="ms-NavBar-item"><a class="ms-NavBar-link" href="/Home/MyProcesses">
<i class="ms-Icon ms-Icon--listCheckbox" aria-hidden="true"></i> List My Processes</a></li>
<li class="ms-NavBar-item"><i class="ms-Icon ms-Icon--globe" aria-hidden="true"></i> <a class="ms-NavBar-link" href="@BusinessApps.

O365ProjectsApp.WebApp.Components.O365ProjectsAppContext.CurrentSiteUrl">Back to
Target
Site</a></li>
@if (BusinessApps.O365ProjectsApp.WebApp.Components.MSGraphAPIContext. CurrentUserIsAdmin) {
<li class="ms-NavBar-item ms-NavBar-item--right"> <a class="ms-NavBar-link" href="/Home/Settings"> <i class="ms-Icon ms-Icon--gear" aria-hidden="true"></i> Settings</a></li>
}
</ul>
</div>

 

Notice the search text box, which leverages a hidden textbox that will be shown if necessary. Also notice the fake Rendering Mode menu, which is made of three sub-menu items and is also highlighted with the --menu style.

 

Last, look at the conditional code, highlighted in bold, that checks if the current user is an Admin before showing the Settings menu item. The Settings menu is rendered on the right side of the screen by using the --right version of the menu item CSS class.

 

To work properly, the top navigation bar will also need to have a bunch of JavaScript code, which has already been added to the project by the Office UI Fabric NuGet package and has been included in that page through the script bundle named ~/bundles/fabric.

 

Because you could have multiple navigation bars within a unique page, you also have to invoke the prototype JavaScript function called NavBar targeting the proper navigation bar. 

 

Furthermore, the menu items of the top navigation bar are branded with some fancy icons, which are available thanks to the Office UI Fabric project.

 

At the time of this writing, there are about 338 custom icons that you can use in your projects to provide branding for menu items, buttons, and graphical elements in the UI of your software solutions. You can see the full list of available icons at the following URL: http://dev.office.com/fabric/styles#icons.

 

All the icons are based on a custom font that contains glyphs you can customize by changing their color, scale, and style. Every icon can be rendered by using an HTML syntax like the following:

<i class="ms-Icon ms-Icon--home" aria-hidden="true"></i>

 

The CSS class ms-Icon defines that the element represents an icon, and the ms-Icon--[Icon Name] class defines the specific kind of icon. The attribute aria-hidden with a value of true instructs any screen reader to skip the current icon, which is not text but just a glyph icon.

 

Responsive grid

So far, the UI of the custom Office 365 application has an Office 365 suite bar and a usable top navigation bar, but it would have awful page content and body—especially from a responsiveness perspective—unless you use the grid styles provided by Office UI Fabric.

 

The Office UI Fabric project provides a responsive grid made of up to 12 columns that behaves almost like the bootstrap grid. You just need to have a DIV element with the CSS class .ms-Grid and fill it with children DIV elements with the CSS class .ms-Grid-row.

 

Each row can be made of one or more DIV elements styled with the CSS class .ms-Grid-col, followed by some other CSS classes that define how large the column will be on small, medium, and large devices. For the sake of completeness.

 

Row #1 Partitioned into three columns, each with a width of 4 blocks of 12

Row #2 Partitioned into four columns, each with a width of 3 blocks of 12

Row #3 Partitioned into two columns, the one on the left with a width of 4 blocks of 12 and the one on the right with a width of 8 blocks of 12

 

The styles applied to each column instruct the browser how to render the columns. The following is an explanation of the three kinds of styles:

ms-u-sm[size] Defines the size, with values between 1 and 12, for small-screen devices

ms-u-md[size] Defines the size, with values between 1 and 12, for medium-screen devices

ms-u-lg[size] Defines the size, with values between 1 and 12, for large-screen devices

Of course, you can have different column sizes based on the size of the device. For the sake of simplicity.

 

LISTING Sample HTML excerpt that renders a responsive grid of Office UI Fabric

<div class="ms-Grid">
<div class="ms-Grid-row">
<div class="ms-Grid-col ms-u-sm4 ms-u-md4 ms-u-lg4">First</div> <div class="ms-Grid-col ms-u-sm4 ms-u-md4 ms-u-lg4">Second</div> <div class="ms-Grid-col ms-u-sm4 ms-u-md4 ms-u-lg4">Third</div>
</div>
<div class="ms-Grid-row">
<div class="ms-Grid-col ms-u-sm3 ms-u-md3 ms-u-lg3">First</div> <div class="ms-Grid-col ms-u-sm3 ms-u-md3 ms-u-lg3">Second</div> <div class="ms-Grid-col ms-u-sm3 ms-u-md3 ms-u-lg3">Third</div> <div class="ms-Grid-col ms-u-sm3 ms-u-md3 ms-u-lg3">Fourth</div>
</div>
<div class="ms-Grid-row">
<div class="ms-Grid-col ms-u-sm4 ms-u-md4 ms-u-lg4">First</div> <div class="ms-Grid-col ms-u-sm8 ms-u-md8 ms-u-lg8">Second</div>
</div>
</div>

Based on what you learned in this section, you can now understand the shared CSHTML page layout.

 

Custom components and styles

The Office UI Fabric provides not only the responsive grid styles, the glyphicons, the Persona control, and the NavBar control, but also about 30 different controls that we can use to improve the user experience of our projects.

 

Moreover, because it is an open source project with tens of committed people contributing to the worldwide community of Office 365 developers, the Office UI Fabric is a continuously evolving project.

 

It is fundamental to stress that all the Office UI Fabric controls are responsive and support three different sizes/formats: SmartPhone (small: max width of 320 pixels), Tablet (medium: max width of 640 pixels), and Desktop (large: max width up to 840 pixels).

 

Last but not least, in the Office UI Fabric, you also find typography styles, which provide about 10 base font classes that allow you to adhere to the Office Design Language.

 

Moreover, there are some CSS styles to use predefined color palette sets and animations for showing or hiding elements (enter/exit animations), for moving or sliding elements (move up, down, left, right), and to control the duration of the animations in the UI.

 

Note

You can find further details about the Office Design Language and the general UI guidelines for creating Office 365 applications and Office Add-ins at the following URL: https://dev.office.com/docs/add-ins/design/add-in-design.

 

Extending and consuming SharePoint Online

Now that the UI and UX of the application are consistent with the Office Design Language, we can concentrate on the real implementation and business logic.

First of all, we need to choose how the application can be activated.

 

In the previous section, “Azure AD application general registration,” we saw that you can pin the application to the Office 365 app launcher. However, often the customers and end users want to be able to activate the application within the context of use and not only from a generic app launcher icon.

 

Let’s say for example that the end users want to be able to activate the application through the ECB (Edit Control Block) menu of any document in a specific document library of SharePoint Online so that the document will be the main and startup element for every team project.

 

Extending the UI of SharePoint Online

To satisfy the business requirement described in the previous paragraph, you can create a SharePoint Online list custom action that extends a target document library, defining a custom ECB menu item. Under the cover, that item will use JavaScript code to activate the custom Office 365 application and to start or access the team project.

 

In the old server-side code development and feature framework development for SharePoint, to create a list custom action you need to create an XML feature that defines all the attributes of the custom action.

 

Now, the server side in SharePoint Online is not available, and the feature framework can be used only with a sandboxed solution. This is a deprecated habit if it contains code, and it is not suggested even if it is just a container for feature framework elements.

 

Within a SharePoint Add-in, you can use the client-side object model (CSOM) to interact with SharePoint and create artifacts and customization. However, the project we created is an Office 365 application, not a SharePoint Add-in. Thus, you may be wondering how you can create a custom action in SharePoint Online using an Office 365 application.

 

When we registered the application in Azure AD, we requested to have the application (app-only) permission to “Have full control of all site collections.” Thus, even if we are in an Office 365 application, we can use the SharePoint CSOM to create a list of custom action onto a target site collection.

 

Moreover, if you are using the OfficeDev PnP Core library and the PnP Provisioning Engine, you can easily provision the custom action and the list to which the custom action applies.

 

Thus, install the SharePointPnPCoreOnline NuGet package in the project. In Listing, you can see a code excerpt that is executed when the custom application starts and authenticates against SharePoint Online with an app-only token.

 

Then, it provisions a custom library with a custom action for the ECB menu of the documents in that library, in case the library does not exist yet.

 

LISTING Sample code excerpt that connects to SharePoint Online with an app-only token and provision some artifacts via CSOM

public static void Provision() {
Create a PnP AuthenticationManager object
AuthenticationManager am = new AuthenticationManager();
Authenticate against SPO with an App-Only access token
using (ClientContext context = am.GetAzureADAppOnlyAuthenticatedContext( O365ProjectsAppContext.CurrentSiteUrl, O365ProjectsAppSettings.ClientId, O365ProjectsAppSettings.TenantId,
O365ProjectsAppSettings.AppOnlyCertificate)) {
Web web = context.Web;
List targetLibrary = null;
If the target library does not exist (PnP extension method) if (!web.ListExists(O365ProjectsAppSettings.LibraryTitle)) {
// Create it using another PnP extension method
targetLibrary = web.CreateList(ListTemplateType.DocumentLibrary, O365ProjectsAppSettings.LibraryTitle, true, true);
}
else {
targetLibrary =
web.GetListByTitle(O365ProjectsAppSettings.LibraryTitle);
}
If the target library exists if (targetLibrary != null) {
Try to get the user's custom action
UserCustomAction customAction =
targetLibrary.GetCustomAction(O365ProjectsAppConstants.ECB_Menu_N
// If it doesn't exist
if (customAction == null) {
Add the user custom action to the list customAction = targetLibrary.UserCustomActions.Add(); http://customAction.Name = O365ProjectsAppConstants.ECB_Menu_Name; customAction.Location = "EditControlBlock"; customAction.Sequence = 100;
customAction.Title = "Manage Business Project"; customAction.Url = $"
{O365ProjectsAppContext.CurrentAppSiteUrl}Project/?Si
teUrl={{SiteUrl}}&ListId={{ListId}}&ItemId={{ItemId}}&ItemUrl={{ItemUrl}}";
}
else {
Update the already existing Custom Action http://customAction.Name = O365ProjectsAppConstants.ECB_Menu_Name; customAction.Location = "EditControlBlock"; customAction.Sequence = 100;
customAction.Title = "Manage Business Project"; customAction.Url = $"
{O365ProjectsAppContext.CurrentAppSiteUrl}Project/?Si
teUrl={{SiteUrl}}&ListId={{ListId}}&ItemId={{ItemId}}&ItemUrl={{ItemUrl}}";
}
customAction.Update();
context.ExecuteQueryRetry();
}
}
}

 

Notice the use of the AuthenticationManager class, which is part of the PnP Core library and provides some helper methods to create a CSOM ClientContext object using any of the available authentication techniques.

 

In Listing the GetAzureADAppOnlyAuthenticatedContext method uses the target URL of the site collection, the ClientId and TenantId defined in Azure AD and the X.509 certificate that we registered in the section “App-only authorization registration” earlier in this blog to create an authenticated context using an app-only access token.

 

The code sample hides the complexity of retrieving the X.509 certificate to authenticate against Azure AD through a global setting called O365ProjectsAppSettings.AppOnlyCertificate. In the full source code of the sample application, you will find the details about how to retrieve the certificate from a certificate store.

 

After creating an authenticated context, the code excerpt checks if the target library exists by using the ListExists extension method, which is available in the PnP Core library. If the list exists, the code gets a reference to it using another extension method called GetListByTitle. Otherwise, it creates the library using the CreateList extension method.

 

Regardless of whether the list already exists, after getting a reference to the library the code checks if the target library already has a user’s custom action for the ECB menu. If the custom action does not exist, the code creates a new one. Otherwise, it updates the existing one.

 

The ECB menu item will drive the user’s browser to the Office 365 application, providing in the query string the URL of the current document together with its ListIdItemId, and the source site URL, leveraging the classic SharePoint URL tokens.

 

As you can see, the user’s custom action creation requires you to invoke the ExecuteQuery method of the CSOM ClientContext object. However, Listing uses another PnP Core library extension method called ExecuteQueryRetry, which is a powerful method that internally handles any retry of the ExecuteQuery standard method of the ClientContext object.

 

When you target SharePoint Online, some requests could fail or be rejected because of connectivity issues or throttling rules on the services side.

 

Provisioning SharePoint artifacts

In the previous section, you saw how to create a library and a user’s custom action in the target site by using CSOM and the PnP Core library. However, within the PnP Core library, you can also find the PnP Remote Provisioning Engine.

 

Through that engine, you can do remote provisioning by code or by applying a template file, which can be applied by using PowerShell or by writing .NET code.

 

For the sake of completeness, in this section, you will see how to leverage an XML-based template file to provision artifacts by writing just a few lines of code. In Listing, you can see a PnP XML provisioning template that provisions the library that we discussed earlier in this blog and its user’s custom action.

 

LISTING Sample PnP XML provisioning template that provisions a library with a user’s custom action

<?xml version="1.0"?>
<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2016/05/ ProvisioningSchema">
<pnp:Preferences Generator="OfficeDevPnP.Core, Version=2.5.1606.2,

Culture=neutral, Publ

icKeyToken=3751622786b357c2">
<pnp:Parameters>
<pnp:Parameter Key="AppSiteUrl" Required="true" />
</pnp:Parameters>
</pnp:Preferences>
<pnp:Templates ID="CONTAINER-TEMPLATE-O365ProjectsApp"> <pnp:ProvisioningTemplate ID="TEMPLATE- O365ProjectsApp" Version="1">
<pnp:Lists>
<pnp:ListInstance Title="BusinessProjects" Description="" DocumentTemplate="{site}/BusinessProjects/Forms/template.dotx" TemplateType="101" Url="BusinessProjects" EnableVersioning="true" EnableMinorVersions="true" MinorVersionLimit="0" MaxVersionLimit="0" DraftVersionVisibility="0" TemplateFeatureID="00bfea71-e717-4e80-aa17-d0c71b360101" EnableAttachments="false">
<pnp:ContentTypeBindings>
<pnp:ContentTypeBinding ContentTypeID="0x0101" Default="true" /> <pnp:ContentTypeBinding ContentTypeID="0x0120" />
</pnp:ContentTypeBindings>
<pnp:Views>
<View Name="{632CEDCA-76C7-4C0E-AEED-4D343DB02B5B}" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" Type="HTML" DisplayName="All

Documents" Url="/

sites/O365ProjectsAppSite/BusinessProjects/Forms/AllItems.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/dlicon.png?rev=43"> <Query>
<OrderBy>
<FieldRef Name="FileLeafRef" />
</OrderBy>
</Query>
<ViewFields>
<FieldRef Name="DocIcon" />
<FieldRef Name="LinkFilename" />
<FieldRef Name="Modified" />
<FieldRef Name="Editor" />
</ViewFields>
<RowLimit Paged="TRUE">30</RowLimit>
<JSLink>clienttemplates.js</JSLink>
</View>
</pnp:Views>
<pnp:FieldRefs>
<pnp:FieldRef ID="ef991a83-108d-4407-8ee5-ccc0c3d836b9" Name="SharedWithUsers" DisplayName="Shared With" />
<pnp:FieldRef ID="d3c9caf7-044c-4c71-ae64-092981e54b33" Name="SharedWithDetails" DisplayName="Shared With Details" />
<pnp:FieldRef ID="3881510a-4e4a-4ee8-b102-8ee8e2d0dd4b" Name="CheckoutUser" DisplayName="Checked Out To" />
</pnp:FieldRefs>
<pnp:UserCustomActions>
<pnp:CustomAction Name="O365ProjectsApp.ManageBusinessProject" Location="EditControlBlock" Sequence="100" Rights="EditListItems,AddListItems,DeleteListItems" Title="Manage Business Project" Url="

{AppSiteUrl}/Project/?

SiteUrl={SiteUrl}&amp;ListId={ListId}&amp;ItemId={ItemId}&amp;ItemUrl=

{ItemUrl}" Enabled="true" />
</pnp:UserCustomActions>
</pnp:ListInstance>
</pnp:Lists>
</pnp:ProvisioningTemplate>
</pnp:Templates>
</pnp:Provisioning>

 

The template file declares to provision a new library by using the ListInstance element highlighted in bold with a user’s custom action, which is also highlighted in bold.

 

Moreover, the template accepts a mandatory parameter named AppSiteUrl, which you can see at the beginning of the template, and which allows you to keep the URL of the site publishing the Office 365 application dynamic.

 

In Listing you can see how to apply that provisioning template to a site, creating the artifacts or updating them if they already exist.

 

LISTING Sample code excerpt that applies a provisioning template to a site

public static void Provision() {
Create a PnP AuthenticationManager object AuthenticationManager am = new AuthenticationManager();
Authenticate against SPO with an App-Only access token
using (ClientContext context = am.GetAzureADAppOnlyAuthenticatedContext( O365ProjectsAppContext.CurrentSiteUrl, O365ProjectsAppSettings.ClientId, O365ProjectsAppSettings.TenantId,
O365ProjectsAppSettings.AppOnlyCertificate)) {
Web web = context.Web;
Load the template from the file system XMLTemplateProvider provider =
new XMLFileSystemTemplateProvider( String.Format(HttpContext.Current.Server.MapPath(".")), "ProvisioningTemplates");
ProvisioningTemplate template = provider.GetTemplate("O365ProjectsAppSite.xml");
Configure the AppSiteUrl parameter template.Parameters["AppSiteUrl"] =
O365ProjectsAppContext.CurrentAppSiteUrl;
Apply the template to the target site template.Connector = provider.Connector; web.ApplyProvisioningTemplate(template);
}
}

 

Notice the statement that loads the PnP provisioning template from the file system and then configures the current site URL, providing a value for the required template parameter. Last, by using the ApplyProvisioningTemplate extension method, the code applies the template to the target site.

 

Because internally the PnP Remote Provisioning Engine does delta handling and compares the target site with the source template, this technique will always keep the target site in sync and aligned with the requirements and capabilities defined in the application within the provisioning XML file.

 

Note

For further details about the XML schema available for defining PnP provisioning templates, you can browse the GitHub repository where the schema is defined and available as a community open source project: https://github.com/OfficeDev/PnP-Provisioning-Schema/

 

Consuming SharePoint Online with delegated permissions

The initial provisioning of artifacts most likely will have to be done using app-only credentials because it requires high permission on the target SharePoint Online.

 

Because the OAuth 2.0 authorization protocol intersects the app permissions with the current user’s permissions, you cannot provision artifacts with every kind of user, and you can’t assign full control permissions to all users, either.

 

Thus, having the capability to act as app-only, only when needed, is powerful. We could say that the app-only technique, in the context of the Office 365 application model and in the SharePoint Add-in model, is like the SPSecurity.RunWithElevatedPrivileges of the old server-side development in SharePoint 201x, but with much more control on the permissions assigned to the app that will act as app-only.

 

However, in common tasks, you probably will need to act with delegated permissions based on the currently logged-in user. In Listing, you can see a code excerpt that shows how to consume SharePoint Online via CSOM using the current user’s identity and an OAuth 2.0 access token with both the user’s token and the app token.

 

LISTING Sample code excerpt that consumes SharePoint Online via CSOM using an OAuth 2.0 access token with user’s token and app token

public static void BrowseLibraryFiles() {
Create a PnP AuthenticationManager object AuthenticationManager am = new AuthenticationManager();
Authenticate against SPO with an App-Only access token using (ClientContext context =
am.GetAzureADWebApplicationAuthenticatedContext( O365ProjectsAppContext.CurrentSiteUrl, (url) => {
return (MicrosoftGraphHelper.GetAccessTokenForCurrentUser(url)); })) {
Web web = context.Web;
var targetLibrary =
web.GetListByTitle(O365ProjectsAppSettings.LibraryTitle);
context.Load(targetLibrary.RootFolder,
fld => fld.ServerRelativeUrl,
fld => fld.Files.Include(f => f.Title, f => f.ServerRelativeUrl)); context.ExecuteQueryRetry();
foreach (var file in targetLibrary.RootFolder.Files) { // Handle each file object
}
}
}

The sample code excerpt creates a ClientContext instance object authenticated against Azure AD and based on the current user’s authorization context by using the

 

AuthenticationManager class of PnP and leveraging its GetAzureADWebApplicationAuthenticatedContext method.

Notice that the above code will access the contents with a delegated permission. Thus, it will have access only to those contents that are accessible to both the application, based on its Azure AD delegated permissions, and the currently logged-in user.

 

Because in Azure AD the application has the delegated permission “Read and write items and lists in all site collections”, the current user’s permissions will determine the effectiveness and resulting permissions.

 

Whether you want to use delegated permissions or app-only access tokens, by using the techniques illustrated in this and the previous section and having proper permissions in Azure AD and in SharePoint Online, you can do almost everything you need against SharePoint Online by using CSOM and the PnP extension methods and helper classes.

 

Using the Microsoft Graph

In the first section of this blog, you saw that creating an Office 365 application enables you to consume not only SharePoint Online but also the entire Office 365 ecosystem. 

 

In this section, you will learn how to apply what you have learned in theory to a real use case. In this section, the key point is how to leverage the OAuth access token to consume the Microsoft Graph API, not the specific actions executed.

 

Nevertheless, discussing the potential and the capabilities through real examples make it easier to understand the topic and to follow the flow.

 

Creating and consuming the project’s Office 365 Group

One requirement of the business use case to which the sample Office 365 application refers is to create a new Office 365 Group for each project that has to be managed. In the code samples, you will find the full implementation of the solution. Here, we will discuss just what really matters from a learning perspective.

 

In Listing, you can see a code excerpt of a helper method that creates a new Office 365 Group, assigns some users as members of the just-created group, and optionally updates the image of the group.

 

LISTING Helper method that uses the Microsoft Graph API to create and configure an Office 365 Group

<summary>
Creates a new Office 365 Group for a target Project
</summary>
<param name="group">The group to create</param>
<param name="membersUPN">An array of members' UPNs</param>
<param name="photo">The photo of the group</param>
<returns>The Office 365 Group created</returns>
public static Group CreateOffice365Group(Group group, String[] membersUPN, Stream photo = null) {
// Create the Office 365 Group
String jsonResponse = MicrosoftGraphHelper.MakePostRequestForString( String.Format("{0}groups",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri), group, "application/json");
var addedGroup = JsonConvert.DeserializeObject<Group>(jsonResponse);
// Set users' membership
foreach (var upn in membersUPN) { MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}groups/{1}/members/$ref", MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, http://addedGroup.Id),
new GroupMemberToAdd {
ObjectId = String.Format("{0}users/{1}/id", MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, upn)
},
"application/json");
}
Update the group's picture, if any if (photo != null) {

 

Retry up to 10 times within 5 seconds, because of the

Office 365 Group sometime takes long to be ready Int32 retryCount = 0;
while (true) { retryCount++; try {
if (retryCount > 10) break; System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(500));
photo.Position = 0;
MemoryStream photoCopy = new MemoryStream(); photo.CopyTo(photoCopy); photoCopy.Position = 0;
MicrosoftGraphHelper.MakePatchRequestForString( String.Format("{0}groups/{1}/photo/$value",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
http://addedGroup.Id),
photoCopy, "image/jpeg");
break;
}
catch {
// Ignore any exception, just wait for a while and retry
}
}
}
return (addedGroup);
}

As you can see, all the tasks are handled by making some HTTPS direct requests against the Microsoft Graph API endpoints.

 

From an authorization perspective, the code leverages the delegated permissions of type “Read and write directory data,” and “Read and write all groups,” which means that the user invoking the function must have at least the same permissions.

 

Notice also that the upload of the custom image for the group implements a retry logic because often the image is available as a target endpoint a few milliseconds after the creation of the group. The group creation method returns the just-created group, which can be useful for further use.

 

In the sample project, whenever a user clicks the ECB menu item Manage Business Project, which has been created in the section “Extending and consuming SharePoint Online” earlier in this blog, the MVC controller that will handle the request will check if the Office 365 Group backing the project exists.

 

If the group exists, the controller will consume it, providing to the end user some high-level information about the group. If the group does not exist, the controller will use the method illustrated in Listing to create the group.

 

In Listing, you can see a code excerpt used to test whether the Office 365 Group exists.

 

LISTING Helper method that uses the Microsoft Graph API to check if an Office 365 Group exists

<summary>
Checks whether an Office 365 Group exists or not
</summary>
<param name="groupName">The name of the group</param>
<returns>Whether the group exists or not</returns>
public static Boolean Office365GroupExists(String groupName) {
String jsonResponse = MicrosoftGraphHelper.MakeGetRequestForString( String.Format("{0}groups?$select=id,displayName" +
"&$filter=groupTypes/any(gt:%20gt%20eq%20'Unified')%20" + "and%20displayName%20eq%20'{1}'",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, HttpUtility.UrlEncode(groupName).Replace("%27", "''")));
var foundGroups = JsonConvert.DeserializeObject<GroupsList>(jsonResponse); return (foundGroups != null && foundGroups.Groups.Count > 0);
}

 

The sampling method makes a REST request against the collection of groups in the current tenant, filtering the items based on the values in the group types collection and searching the target group by display name. If the response is a collection of at least one item, the group does exist; otherwise, it doesn’t.

 

Sending notifications on behalf of users

After creating the Office 365 Group for the project, you may want to send an email message to the group members by leveraging the conversation panel of the just-created group.

 

Using the current user’s context of authentication, you can automate the sending of the message on behalf of the current user. The group will get a message from the current user, but in reality, the application will send the message automatically.

 

To achieve the described result, the application must have proper permissions in Azure AD, as discussed at the beginning of this blog. In Listing, you can see a code excerpt that sends a new thread message to the conversation stream of the group.

 

LISTING Helper method that uses the Microsoft Graph API to send a message to an Office 365 Group

<summary>
Creates a new thread in the conversation flow of a target Office 365 Group
</summary>
<param name="groupId">The ID of the target Office 365 Group</param> public static void SendMessageToGroupConversation(String groupId) {
var conversation = new Conversation {
Topic = "Let's manage this Business Project!", Threads = new List<ConversationThread>(
new ConversationThread[] {
new ConversationThread {
Topic = "I've just created this Business Project", Posts = new List<ConversationThreadPost>(
new ConversationThreadPost[] {
new ConversationThreadPost {
Body = new ItemBody {
Content = "<h1>Welcome to
Project</h1>",
Type = BodyType.Html,
},
}
})
}
})
};
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}groups/{1}/conversations", MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, groupId), conversation, "application/json");
}

 

The key part of the code sample is the creation of the new Conversation object that is made of a new conversation thread. As you can see in Figure the result will be the creation of a new welcome thread in the new Office 365 Group.

 

Creating asynchronous jobs

As you probably experienced while playing with the examples of the previous section, the creation of an Office 365 Group can take a few seconds because under the cover it will create a SharePoint Online site collection and some other objects in Azure AD and Exchange Online.

 

In general, many tasks can take quite a long time to complete or just an unpredictable amount of time to run whenever you interact with external services that are provided by a third party or by a cloud-based platform.

 

Moreover, in Office 365 there are throttling rules that may cause some of your requests to fail, as you saw with SharePoint Online when talking about the benefits of using the ExecuteQueryRetry method of PnP compared with the standard ExecuteQuery.

 

Furthermore, while consuming such API and services within a web-based application, the HTTP requests from the web browser (that is, the client) to the web server that is providing the functionality can timeout while waiting for back-end services.

 

For example, if you host your ASP.NET MVC web application in Microsoft Azure, by default you will have one minute of request timeout. What if you want or need to manage requests that can take longer than one minute? In general, what can you do to have a stronger and more available software architecture, avoiding tight dependencies on unpredictable completion times of third-party services?

 

There are some good patterns that make your software architectures more solid. One of the most powerful patterns is the asynchronous job, which will be discussed in the following paragraphs.

 

Remote timer job architecture

The basic idea of the asynchronous job pattern is to avoid executing actions that are complex, long-running, or have an unpredictable completion time in the foreground and in real time, instead leveraging a queue of actions that will be performed in the background by an engine (a worker job) as soon as there are resources available for processing them.

 

Many enterprise-level platforms and solutions have an out-of-box system to provide such functionalities. For example, think about the TimerJob framework of SharePoint on-premises.

 

However, the TimerJob framework of SharePoint requires a full trust code development approach, which is not suitable for Office 365. In Office 365, there isn’t a scheduler service for executing background actions or tasks.

 

Nevertheless, as you have seen in many other situations throughout this blog, one of the best friends of Office 365 is Microsoft Azure.

 

By using Microsoft Azure, you can create WebJobs that can be scheduled to run at a specific time based on a schedule or can be executed on demand manually by users or triggered by some external event. One of the possible events that can be used to trigger an Azure WebJob is the presence of a message in an Azure Storage Queue.

 

Thus, whenever you are in an Office 365 application and need to perform an action that is business critical and can take an unpredictable amount of time to run, you can enqueue into an Azure Storage Queue a request to perform that action. Later, an Azure WebJob will be triggered and will perform the real action in the background. 

 

The job is often referred to as a “remote timer job” because it mimics a classic SharePoint timer job, but it acts remotely by using the Microsoft Graph and the SharePoint REST API instead of any server-side code. Moreover, it can be a job that interacts with the entire Office 365 ecosystem using the Office 365 application model.

 

From an authentication perspective, the job can access the Office 365 services either by using a set of application credentials (username and password) or—even better—by using a ClientID and SharedSecret by leveraging the OAuth 2.0 protocol and Azure AD, typically running in an app-only authorization context.

 

If you are targeting SharePoint Online only, you can even consider using the PnP remote timer job framework, which is part of the PnP Core library and provides some useful types (base abstract classes and helper types) to make it easier for you to create a remote timer job for SharePoint with this new model.

 

For example, you could have a SharePoint Online remote timer job to check the security settings of sites. In fact, it is a good habit and a best practice to have at least two site collection administrators for each site collection, but SharePoint Online requires only one site collection administrator.

 

Thus, you can create a SharePoint remote timer job that can go through all the existing site collections of a tenant and double-check the number of site collection administrators, adding a predefined second one where there is only one or sending an alert to the single site collection administrator.

 

This is a perfect candidate for using the PnP remote timer job framework and for targeting SharePoint Online only. In the PnP Partner Pack sample solution, there is this kind of sample remote timer job for SharePoint Online.

 

However, there are many scenarios in which you will need to target the entire Office 365 ecosystem. For example, what we did in the previous section—creating an Office 365 Group, setting its image, and enabling members of the group—is a good candidate for such an asynchronous task.

 

For example, you can add a service reference to your .NET web application that provides the Office Add-in implementation, and you can consume the license verification service with a code syntax like.

 

LISTING Code excerpt validating an XML license token through the Office Store license verification service

VerificationServiceClient svc = new VerificationServiceClient();
var result = svc.VerifyEntitlementToken(
new VerifyEntitlementTokenRequest() { EntitlementToken = decodedToken });
if (!result.IsValid) {
// Handle any license issue here
}

 

As you can see, there is a VerifyEntitlementToken operation, which is the only one provided by the service, and which verifies the XML license token. The operation returns an object of type VerifyEntitlementTokenResponse, which provides information about the verified license.

 

The most important property of the response object is the IsValid Boolean property. If the IsValid property has a value of true, the license is valid; otherwise, it is not.

 

Another interesting property of the response object is the IsTest Boolean property, which enables you to check if the current license is for testing purposes only. In general, there are a lot of useful properties that you can use—for example, to enable specific features if the license is a paid one. 

 

You can play with these properties in your code and implement complex behaviors based on the license status. In Listing, you can see a full sample about how to handle the main statuses of a license.

 

LISTING Full code sample about how to handle the license verification response

if (result == null ||
result.ProductId != TARGET_PRODUCTID ||
!result.IsValid) {
// The license is not valid => redirect user to an "UNLICENSED" message/UI
}
else if (result.IsValid) {
switch (result.EntitlementType) {
case "Free":
The license is valid and Free => enable Free features break;
case "Paid":
// The license is valid and Paid => enable Paid features
break;
case "Trial":
// The license is valid but Trial => check Trial period
if (result.EntitlementExpiryDate < DateTime.Now || result.IsExpired)
{
The Trial period is expired => redirect user to
a "TRIAL EXPIRED" message/UI
}
else {
// The Trial period is valid => enable Trial features, only
}
break;
}
}
#if DEBUG
else if (result.IsTest) {
// The license is for testing purposes only => behave accordingly
}
#endif
else {
// The license is not valid => redirect user to an "UNLICENSED" message/UI
}

 

Notice the check against the test license, which is wrapped in the conditional compilation for a DEBUG compilation. Thus, if there is a released version of your code, you will reject any testing license. 

 

In the previous URL, the token query string argument represents the XML license token encoded by using, for example, the Uri.EscapeDataString method of .NET or the encodeURIComponent() method of JavaScript.

 

License check in SharePoint Add-ins

Much of the information shared in the previous section about how to check licenses in an Office Add-in also applies to validate a SharePoint Add-in license. However, in a SharePoint Add-in, the license is not provided through a query string parameter, and you have to query for any available license by using the client-side object model (CSOM).

 

In Listing, you can see a code excerpt about how to retrieve licenses for a currently running SharePoint Add-in. The code excerpt also leverages the OfficeDev PnP Core Library extensions, “Overview of Office 365 development,” to improve the overall availability of the code.

 

LISTING Code excerpt to retrieve an XML license token for a SharePoint Add-in

VerifyEntitlementTokenResponse result = null; VerificationServiceClient svc = new VerificationServiceClient();
AuthenticationManager authManager = new AuthenticationManager();
using (ClientContext context = authManager
.GetSharePointOnlineAuthenticatedContextTenant( targetSiteUrl, userName, password)) {
var licenses = Utility.GetAppLicenseInformation(context, TARGET_PRODUCT_ID);
context.ExecuteQueryRetry();
foreach (AppLicense license in licenses.Value) { var xmlToken = license.RawXMLLicenseToken;
result = svc.VerifyEntitlementToken(
new VerifyEntitlementTokenRequest() { EntitlementToken = xmlToken
});
if (result == null ||
result.ProductId != TARGET_PRODUCTID ||
!result.IsValid) {
// The license is not valid => redirect user to an "UNLICENSED"
message/UI
}
else if (result.IsValid) {
switch (result.EntitlementType) {
case "Free":
The license is valid and Free => enable Free features break;
case "Paid":
The license is valid and Paid => enable Paid features break;
case "Trial":
The license is valid but Trial => check Trial period if (result.EntitlementExpiryDate < DateTime.Now ||
result.IsExpired) {
The Trial period is expired => redirect user
to a "TRIAL EXPIRED" message/UI
}
else {
// The Trial period is valid => enable Trial features,
only
}
break;
}
}
#if DEBUG
else if (result.IsTest) {
// The license is for testing purposes only => behave accordingly
}
#endif
else {
// The license is not valid => redirect user to an "UNLICENSED"
message/UI
}
}
}

The GetAppLicenseInformation method of the Utility class, which is defined in the Microsoft.SharePoint.Client.Utility namespace of CSOM, allows you to retrieve all the currently active licenses for the current user and the current SharePoint Add-in.

 

The result is a collection of licenses that usually is made of a single item but could be a collection of items.

 

Thus, you should walk through all of them to seek any useful information. For example, you may have to merge the information of multiple licenses to determine the real licensed features for the current user related to the current product. The XML license token is inside the property RawXMLLicenseToken of each item of the collection, which is of type app license.

 

In the code sample illustrated in Listing, we iterate through all of the licenses and validate each of them. In a real solution, you probably should do something more sophisticated.

 

Aside from that, the verification process is almost the same as for an Office Add-in license. The Office Store license verification service does the license verification process, and the response is exactly the one you saw in the previous paragraphs.

 

 For testing purposes, you can load up to 10 test licenses into your environment by using the ImportAppLicense method of the Utility class.

 

Best practices for handling licenses in code

In the previous paragraphs, you learned how to handle licenses and how to check XML license tokens in code. However, it is better to understand when and how to apply license checks than to continuously execute verifications for every request.

 

Invoking the Office Store license verification service introduces latency in your application logic, and you should avoid checking a license upon every request. It is better to verify the license once when the user launches your application, and cache the response you get back from the service into a state variable that you can query later if necessary.

 

For example, you could store the needed information—like the license expiration (if any), the entitlement type, and so on—within a persistent state variable and query that state variable whenever you need to enable a feature or capability that requires a proper license to work.

 

The state persistence layer can be a trivial session object if few users use the application and you don’t need to provide high availability for your solution. However, if you need to run the application on multiple servers or service instances, whether for high availability purposes or for scalability goals, you should rely on a scalable service like the Azure Redis Cache.

 

You can also consider using a session provider that relies on the Azure Redis Cache to keep the implementation simple but still highly available and scalable.

 

Note

You can find further details about Azure Redis Cache by reading the document available at the following URL: Azure Redis Cache | Microsoft Azure. 

 

Moreover, you need to consider that the license checks can be done on the server side only. Thus, you cannot plan to perform license checks for an Office Add-in within the JavaScript client code that runs inside the Office client application.

 

If your application is a SharePoint Add-in, you can run the license checks in the main controller of  ASP.NET web application if the add-in is provider-hosted and runs in ASP.NET MVC.

 

Moreover, if the SharePoint Add-in is a SharePoint-hosted add-in, you should rely on an external server to perform the license checks. Any client-side JavaScript license check code could be tampered with by a malicious user.

 

For example, in the main page of the add-in or in any of the app parts, you can embed an image that loads from an external service and that checks the license on the server side. In case of any failed license validation, you can show an alert image instead of the logo of your add-in.

 

Moreover, you can consider providing a custom REST API that you invoke from the SharePoint-hosted add-in instead of using an embedded image, but you need to be careful because a JavaScript request against a REST service can be tampered with.

 

It is all about how critical it is to protect your SharePoint Add-in. In general, if you want to protect your solution with a strong licensing model, having a provider-hosted solution instead of a SharePoint-hosted one is the better option.

 

A provider-hosted solution is also better from an architectural and scalability perspective. Thus, any real enterprise-level solution should be implemented as a provider-hosted add-in, not only for license checks but also from an architectural perspective.

 

Metrics and company profile

Another interesting option you have once you have published an add-in or a web application is to monitor the results in terms of sales and usage of your solution.

 

Metrics

Within the Seller Dashboard, you can click the Metrics tab after selecting the red Continue button under the Office area, and you will be able to monitor some useful numbers about your solutions. First of all, you have to select a published add-in or application for which you want to see the metrics.

 

The metrics available through the Seller Dashboard are related to the latest four weeks and include the following insights:

  1. Browser hits The number of times your solution has been viewed in the Office Store
  2. Downloads The number of times your solution has been downloaded from the Office Store
  3. Trial downloads The number of times your solution has been purchased for free from the Office Store
  4. Trial conversions The number of times a trial has been converted into a paid version of your solution
  5. Purchased seats The overall number of seats that have been purchased for your solution
  6. Purchased site licenses The overall number of site licenses that have been purchased for your solution
  7. For SharePoint Add-ins only, you will also have the following metrics related to the timeframe of analysis:
  8. Installs The overall number of install attempts
  9. Launches The overall number of times the solution has been started
  10. Daily unique users The sum of daily unique users for your solution
  11. Uninstalls The overall number of uninstalling attempts
  12. Failed installs The overall number of failed installs, including any retries
  13. Runtime errors The overall number of errors logged by SharePoint and by the solution within its custom code
  14. Failed upgrades The overall number of failed upgrades, including any retries
  15. You can also see reports in terms of sales by following the link named View Sales And Tax Data, which will open a specific sales report.

 

Office Profile

Another set of information that you can manage during the lifetime of your projects is the Office Profile, which can be managed by clicking the Office Profile tab.

 

Through that page, you will have the capability to update the following data about your company:

  1. Logo of the company The logo that will be shown in the Office Store for your company
  2. Description The description of your business, useful in the Office Store
  3. Website The URL of the website for your business
  4. Marketing Contact Email The email address of marketing contact in your company, if any
  5. Address The physical postal address of your company Phone Number The phone number of your company
  6. The Seller Dashboard will use all of these fields to build your company profile in the Office Store.

 

Summary

In this blog, you learned how to publish your SharePoint Add-ins, Office Add-ins, and Office 365 web applications, whether you want to target the Corporate Catalog or the public Office Store. You saw that all of these solutions share the same publishing tool, which is called the Seller Dashboard if you want to sell them worldwide.

 

You learned how to create, submit for approval, and publish a new solution and how to update or delete a published one. Furthermore, you saw how to monitor your metrics and sales. 

 

Moreover, you understood the pricing models available through the Office Store and how you can control licenses and subscriptions within your custom developed solutions, working with license files and the Office Store license verification service.

 

You are now ready to create your real business solutions and eventually to sell them in the Office Store or publish them in the Corporate Catalog. Have fun

Recommend