Programming Microsoft Office 365
Programming Microsoft Office 365
Paolo Pialorsi
PUBLISHED BY
Microsoft Press
A division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Copyright © 2016 by Paolo Pialorsi
All rights reserved. No part of the contents of this book may be reproduced or
transmitted in any form or by any means without the written permission of the
publisher.
Library of Congress Control Number: 2015938171
ISBN: 978-1-5093-0091-4
Printed and bound in the United States of America.
First Printing
Microsoft Press books are available through booksellers and distributors
worldwide. If you need support related to this book, email Microsoft Press
Support at [email protected]. Please tell us what you think of this book
at http://aka.ms/tellpress.
This book is provided “as-is” and expresses the author’s views and opinions.
The views, opinions and information expressed in this book, including URL and
other Internet website references, may change without notice.
Some examples depicted herein are provided for illustration only and are
fictitious. No real association or connection is intended or should be inferred.
Microsoft and the trademarks listed at http://www.microsoft.com on the
“Trademarks” webpage are trademarks of the Microsoft group of companies. All
other marks are property of their respective owners.
Acquisitions Editor: Devon Musgrave
Developmental Editor: Devon Musgrave
Editorial Production: Cohesion
Technical Reviewer: Steve Caravajal; Technical Review services provided by
Content Master, a member of CM Group, Ltd.
Copyeditor: Ann Weaver
Indexer: Jack Lewis
Cover: Twist Creative • Seattle
This book is dedicated to my family: Paola,
Andrea, and Marta. I love you!
—PAOLO PIALORSI
Contents at a glance
Introduction
PART I GETTING STARTED
CHAPTER 1 Microsoft Office 365: A quick tour
CHAPTER 2 Overview of Office 365 development
PART II OFFICE 365 PROGRAMMING MODEL
CHAPTER 3 Microsoft Graph API reference
CHAPTER 4 Azure Active Directory and security
PART III CONSUMING OFFICE 365
CHAPTER 5 Mail, calendar, and contact services
CHAPTER 6 Users and Groups services
CHAPTER 7 File services
CHAPTER 8 Microsoft Graph SDK for .NET
CHAPTER 9 SharePoint REST API
PART IV SHAREPOINT AND OFFICE APPS
CHAPTER 10 Creating Office 365 applications
CHAPTER 11 Overview of Office Add-ins
CHAPTER 12 Publishing your applications and add-ins
Index
Table of Contents
Introduction
PART I GETTING STARTED
Chapter 1 Microsoft Office 365: A quick tour
What is Microsoft Office 365?
Microsoft Office 365 services
Microsoft Office on PC/Mac and Click-to-Run
Licensing and subscription plans
Administration in Office 365
The new Office 365 Admin Center
The classic Office 365 Admin Center
Summary
Chapter 2 Overview of Office 365 development
Setting up your development environment
Setting up an Office 365 developer tenant
Configuring your development machine
Office 365 Developer Patterns & Practices tools
Preparing for the SharePoint Framework
Office 365 applications development
Web applications
Native applications
Office 365 Connectors
SharePoint online development
SharePoint Add-ins
Remote timer jobs for SharePoint
Remote event receivers
Remote provisioning
Office client development
Summary
PART II OFFICE 365 PROGRAMMING MODEL
Chapter 3 Microsoft Graph API reference
What is the Microsoft Graph API?
Microsoft Graph API metadata
Consuming users and security groups
Yourself and other users
Security groups
Consuming mail, contacts, and calendars
Mail messages
Contacts
Calendars and events
Event invitations
Consuming OneDrive for Business
Querying files and folders
Managing files and folders
Searching within a drive
Sharing files and folders
Working with Office 365 Groups
Summary
Chapter 4 Azure Active Directory and security
Introducing Azure Active Directory
Identities in Azure AD
Managing Office 365 identities
Configuring apps and services in Azure AD
Manual configuration
Multitenancy
Using Microsoft Visual Studio
Understanding OpenID Connect and OAuth 2.0
The OpenID Connect communication flow
Under the cover of OpenID Connect and OAuth 2.0
Active Directory Authentication Library
Using ADAL in an ASP.NET MVC web application
Summary
PART III CONSUMING OFFICE 365
Chapter 5 Mail, calendar, and contact services
Setting up the environment
Mail services
Reading folders, messages, and attachments
Sending an email message
Reply, reply all, and forward email messages
Calendar services
Reading calendars and events
Browsing calendar views
Managing series of events
Creating and updating events
Managing invitations for meeting requests
Contact services
Reading contacts
Managing contacts
Summary
Chapter 6 Users and Groups services
Users services
Reading users
Groups services
Browsing groups
Managing groups
Managing group membership
Office 365 Groups services
Querying Office 365 Groups
Office 365 Groups capabilities
Creating or updating Office 365 Groups
Summary
Chapter 7 File services
Working with drives, files, and folders
Browsing for files and folders
Consuming files
Uploading and updating files
Permissions and sharing
Managing files permissions
Sharing a file
Summary
Chapter 8 Microsoft Graph SDK for .NET
Introduction to the Microsoft Graph SDK
Registering the app and using the SDK
Request model
Querying resources
Basic query operations
Handling paging of collections
Managing resources
Adding a resource to a collection
Updating a resource
Deleting a resource
Handling exceptions and concurrency
Real-life examples
Sending an email
Searching for Office 365 Groups
Handling content of Office 365 Groups
Managing current user’s photo
Managing current user’s manager and direct reports
Uploading a file to OneDrive for Business
Searching for files in OneDrive for Business
Downloading a file from OneDrive for Business
Summary
Chapter 9 SharePoint REST API
Introducing the REST API
API reference
Querying data
Managing data
Cross-domain calls
Security
Common REST API usage
Creating a new list
Creating and updating a list item
Deleting an existing list item
Querying a list of items
Creating a new document library
Uploading or updating a document
Checking in and checking out documents
Deleting a document
Querying a list of documents
Summary
PART IV SHAREPOINT AND OFFICE APPS
Chapter 10 Creating Office 365 applications
Solution overview
Creating and registering the Office 365 application
Azure AD application general registration
App-only authorization registration
Setting Azure AD permissions
Basic UI elements with Office UI Fabric
Office 365 suite bar and top navigation
Responsive grid
Custom components and styles
Extending and consuming SharePoint Online
Extending the UI of SharePoint Online
Provisioning SharePoint artifacts
Consuming SharePoint Online with delegated permissions
Using the Microsoft Graph
Creating and consuming the project’s Office 365 Group
Sending notifications on behalf of users
Creating asynchronous jobs
Remote timer job architecture
Creating a remote timer job in Azure
Publishing the application on Azure
Publishing the Azure App Service
Publishing the WebJob
Office 365 Connectors
Creating and registering a webhook
Writing the project’s connector
Summary
Chapter 11 Overview of Office Add-ins
Introducing Office Add-ins
Tools for creating Office Add-ins
Add-in manifest
Creating Outlook Add-ins
Add-in manifest for Outlook
Your first Outlook Add-in
A more realistic example
Using Yeoman generator
Office JavaScript APIs
Creating Content and Task Pane Add-ins
Summary
Chapter 12 Publishing your applications and add-ins
Options for publishing add-ins and web applications
Private corporate publishing
Office Store
File share publishing
Using the Seller Dashboard
Publishing Office Add-ins
Publishing SharePoint Add-ins
Publishing Office 365 web applications
Updating or deleting add-ins or Office 365 web applications
Licensing model
Types of licenses
Checking license in code
Best practices for handling licenses in code
Metrics and company profile
Metrics
Office Profile
Summary
Index
Assumptions
This book expects that you have at least a minimal understanding of web
development, especially of HTTP, REST, and JSON. Moreover, if you are a
.NET developer and you know about ASP.NET, you will find it easier to follow
and understand some of the code samples shared within the book. Although the
Microsoft Graph API is available to any development platform that supports
HTTP and JSON, most of the samples in this book are based on ASP.NET,C#,
and PowerShell.
With a heavy focus on the Microsoft Graph API, Azure Active Directory, and
the architectural patterns for creating real business-level solutions, this book
the architectural patterns for creating real business-level solutions, this book
assumes that you have a basic understanding of the most common collaboration
needs.
Most of the book’s chapters include code samples that let you try out the
concepts you just learned. No matter which sections you choose to focus on, be
sure to download and install the sample applications on your system.
System requirements
You will need the following hardware and software to complete the practice
exercises in this book:
Any Windows version that can run Microsoft Visual Studio 2015 Update 2
or later or any Mac operating system that can run Visual Studio Code
Microsoft Visual Studio 2015 Update 2 or Visual Studio Code
Computer that has a 1.6 GHz or faster processor (2 GHz recommended)
1 GB (32-bit) or 2 GB (64-bit) RAM
10 GB of available hard disk space
5400 RPM hard disk drive
DirectX 9 capable video card running at 1024 × 768 or higher resolution
display
DVD-ROM drive (if installing Visual Studio from DVD)
Internet connection to download software or chapter examples and to
connect to Microsoft Office 365 and Microsoft Azure
A Microsoft Office 365 subscription and access to the Office 365 admin
portal
A Microsoft Azure subscription and access to the Azure portal
Telerik Fiddler 4 (http://www.telerik.com/fiddler)
Depending on your Windows configuration, you might require local
administrator rights to install or configure Visual Studio 2015.
If you don’t have an Office 365 subscription, you can sign up to join the
Office 365 Developer Program, and you will get a one-year FREE Office 365
Developer subscription at the following URL: http://dev.office.com/.
Downloads
Most of the chapters in this book include exercises that let you interactively try
out new material learned in the main text. All sample projects can be
downloaded from the following page:
https://aka.ms/ProgOffice365/downloads
Follow the instructions on the target page to download the code sample files.
Note
Acknowledgments
This book has been the most complex and challenging manuscript I ever wrote.
Usually, writing a book is a well-defined and time-scoped process, which can be
accomplished following a clear schedule.
accomplished following a clear schedule.
However, this book covers a topic (Microsoft Office 365) that is continuously
changing and growing, almost on a monthly basis, and what you write now
should be slightly different within the next few months. Luckily, and thanks to
Microsoft Press, I had the opportunity to embrace the Current Book Service
model, which allows me to keep the book updated in electronic format and
allows you—the reader—to read a continuously updated book that will follow
the evolution of the target product. In fact, we will ship three updates within the
next 18 months after the first release of the book, and you will be able to stay on
track, refreshing and updating your knowledge according to the growth of
Microsoft Office 365.
First of all, I would like to thank Microsoft Press and all the publishing people
who contributed to this book project. Mainly, I’d like to thank Devon Musgrave,
who trusted me and allowed me to write this book and made it possible for this
book to be part of the Current Book Service model. Devon helped me during the
production process of this book, and without him and his contribution this book
wouldn’t be possible.
In addition, my colleagues in the Core Team of the Office 365 Dev and
SharePoint Patterns & Practices (PnP: http://aka.ms/OfficeDevPnP) deserve
special thanks because they greatly helped me create the content of this book,
helping me find the right ideas and samples and sharing with me their vision,
their time, and their minds. In particular, I would like to thank Vesa Juvonen,
Bert Jansen, Erwin van Hunen, and Patrick Rodgers. PnP really rocks, and you
guys rock even more! “Sharing is caring,” and this book is clear proof of that.
Furthermore, I’d like to thank the people from Microsoft who helped me
during the definition of the outline of this book. In particular, I want to thank
Jeremy Thake, Luca Bandinelli, Yina Arenas, and Vittorio Bertocci.
Last but not least, there are special people who deserve a huge thank you.
They are my wife, Paola, my son, Andrea, and my daughter, Marta. I need to
thank them for their support, patience, and understanding during the last 12
months. We know that whenever daddy writes a book, it will be a very busy
time. However, having you guys counting with me the chapters and the pages
lasting to the end of the book and having your unconditioned support to achieve
my goals helps me a lot. We are a team, and I’m really thankful for your
fundamental and unique contribution.
Stay in touch
Let’s keep the conversation going! We’re on Twitter:
http://twitter.com/MicrosoftPress
Part I: Getting started
CHAPTER 1 Microsoft Office 365: A quick tour
CHAPTER 2 Overview of Office 365 development
This first part of this book introduces the Microsoft Office 365 ecosystem from
both a functional and a developer perspective. The overall goal of this part is to
give you an understanding of the fundamental services that power the Office 365
offering. Moreover, this part offers an overview of the development tools and
techniques for creating software solutions to extend Office 365.
Chapter 1, “Microsoft Office 365: A quick tour,” provides a quick overview of
Office 365, bringing you through the main services and capabilities of
Microsoft’s main Software as a Service (SaaS) offering. It explains the role of
Azure Active Directory and introduces Exchange Online, SharePoint Online,
and Skype for Business. It also discusses Office Delve, Office 365 Video, Office
365 Groups, and other NextGen Portals. If you already know about Office 365 in
general, maybe you can skip this chapter.
Chapter 2, “Overview of Office 365 development,” provides robust
information about what a developer can do to extend the native set of services
Office 365 offers. The chapter also covers the tools and techniques that are
available for developing Office 365 applications, Office Add-ins, and SharePoint
Add-ins. Moreover, the chapter instructs you about how to set up a development
environment to develop this kind of solution.
Chapter 1. Microsoft Office 365: A quick tour
Microsoft Office 365 is one of the most innovative sets of services Microsoft has
offered in the last decade, together with Microsoft Azure. In this chapter, you
will learn what Office 365 is and the fundamental services on which the Office
365 offering is built.
FIGURE 1-1 The main software hosting models and business models available
on the market
There are four main offerings available:
On-premises Everything is based on hardware and software that is
installed within the building (the premises) of the company or person using
the software. All the key aspects of a software solution, like availability,
scalability, and security, are the responsibility of the company or person
who runs that software.
Infrastructure as a Service (IaaS) The software is installed on one or
more virtual machines that are hosted by a third party, abstracting the
subject that uses that software from taking care of all the physical
infrastructural topics like networking, storage, physical servers, and
virtualization.
Platform as a Service (PaaS) The software is hosted and executed in a
platform that allows developers to focus on data and custom application
development. The resources used to host the software solution can be
shared across multiple subjects. In that case, PaaS guarantees isolation and
data partitioning.
Software as a Service (SaaS) The software solution is provided on a
subscription basis and is delivered through a centrally hosted
infrastructure. Typically, the software solution can be improved by
creating customizations and/or embracing an extensibility model that keeps
the SaaS offering safe and isolated from any third-party customization.
Office 365 fits into the SaaS category. Following the guidance in this book,
you—as a developer—will be able to build customizations and extensions
according to the extensibility model of a SaaS offering.
One fundamental concept that you need to understand about the various
hosting models is that developing solutions for SaaS allows you to focus on your
business requirements without having to take care of any infrastructural or
platform-related tasks, as you would if you were hosting a solution running in
any of the other hosting models. Often, developers waste their time architecting
and configuring virtual machines, services, and servers just for the sake of
hosting their custom-developed solutions. With the SaaS model, you will
completely focus on customizing the software provided as a service by realizing
the business requirements that you need to satisfy.
Organization Profile
The section related to the Organization Profile allows admins to manage a
custom theme for the whole tenant and custom tiles for the app launcher or a
custom help desk.
The Organization Profile page provides access to some descriptive
information about your company. This information includes the organization
name, the address, the telephone number, and the main technical reference email
addresses. The most interesting sections of the Organization Profile, from a
developer perspective, are the Custom Theming and the Custom Tiles.
A custom theme for the tenant applies to the Office 365 suite bar and in
particular to the top navigation bar. In Figure 1-11, you can see these elements.
FIGURE 1-11 The main Office 365 suite bar and the top navigation bar
A custom theme is made of the following elements:
Customo logo This is an image with a fixed size of 200 × 30 pixels, not
larger than 10 KB, which can be a JPG, PNG, or GIF. It will be shown in
the middle of the top navigation bar.
URL for a clickable logo If you want to make the custom logo clickable,
here you can provide the target URL that will be loaded by clicking the
logo. Provide full URL, including http:// or https://.
Background image Defines a background image with a fixed size of 1366
× 50 pixels or fewer, not larger than 15 KB, which can be a JPG, PNG, or
GIF. It will be shown as the background for the top navigation bar.
Accent color The color that is used for the app launcher button, for mouse
over, and for other accents.
Nav bar background color Defines the background color for the top
navigation bar.
Text and icons Defines the color used for text and icons in the top
navigation bar.
App launcher icon Allows you to select the color for the app launcher
icon.
Through the Custom Theming page, you have the option to remove any
applied custom theming or custom colors, and you can prevent users from
overriding the custom theming with their own theme.
In Figure 1-12, you can see the UI of the page that allows you to define a
custom theme.
FIGURE 1-12 The page to define a custom theme available in the Office 365
Admin Center
Custom Tiles is another useful section that allows you to define custom items
that will be available to the end users for pinning in the app launcher. Every tile
is made of a Title, a URL that can target a link inside or outside the tenant, a
Description, and an Image URL for the image that will be shown inside the tile.
Description, and an Image URL for the image that will be shown inside the tile.
At the time of this writing, any custom tile will be available to the end users, but
they will have to pin the tile in their app launcher manually. Otherwise, that tile
will be visible only by clicking the My Apps link in the lower area of the app
launcher. Soon it likely will be possible to force the pinning of a tile in the app
launcher for all the users in the tenant, improving the governance experience for
tenant administrators.
You can also extend the app launcher with custom tiles by creating and
registering applications in Azure AD. This topic will be covered in upcoming
chapters, particularly Chapter 4 and Chapter 10.
Summary
In this chapter, you studied the overall architecture of Office 365, and you
learned that it is one of Microsoft’s main SaaS offerings. You examined the
learned that it is one of Microsoft’s main SaaS offerings. You examined the
main services the Office 365 ecosystem offers, and you learned about the new
Office client offering, which is installed through the Click-to-Run setup model.
You saw the main subscriptions available on the market and the services
included in each subscription.
Moreover, you explored the main administrative roles of an Office 365 tenant
and the administrative tools available to manage, monitor, and maintain the
services included. In particular, you saw the administrative tools for customizing
the UI and branding of an Office 365 tenant and for administering the Microsoft
SharePoint Online service.
In Chapter 2, you will learn how to leverage Office 365 from a developer
perspective, and you will explore the main extensibility areas and tools to build
business-level solutions to improve your users’ and customers’ productivity.
Chapter 2. Overview of Office 365 development
In Chapter 1, “Microsoft Office 365: A quick tour,” you got a general overview
of Microsoft Office 365 and of all the services that enrich it. Because this book
targets developers, this chapter will give you an overview of the main areas of
development and customization of Office 365. Moreover, you will see some of
the most useful tools available on the market, most of them made by the
community of Office 365 developers, to realize the potential of the platform.
As you can see in Figure 2-2, there are three flavors available for the library.
SharePointPnPCore2013 Targets SharePoint 2013 on-premises and uses
the CSOM (client-side object model) library for SharePoint 2013 on-
premises
SharePointPnPCore2016 Targets SharePoint 2016 on-premises
SharePointPnPCoreOnline Targets SharePoint Online and leverages the
latest CSOM library for SharePoint Online
Depending on your target platform, you will have to download the proper
package. All the packages provide almost the same set of capabilities except for
the functionalities that are available only on the cloud or only on SharePoint
2016.
Because the PnP Core library is so powerful, you probably will find it useful
to reference it in every Office 365 solution.
Another amazing feature included in the PnP Core library is the PnP Remote
Provisioning Engine, which targets provisioning on SharePoint 2013, SharePoint
2016, or SharePoint Online. If you are a SharePoint developer for on-premises,
and you are used to developing solutions using the old FTC (Full Trust Code)
development model, you probably know that in SharePoint on-premises—since
SharePoint 2007—it is possible to provision artifacts (lists, libraries, content
types, site columns, and so on) by using the feature framework. The feature
framework uses CAML (Collaborative Application Markup Language) and
XML-based files to define the artifacts to provision. However, the FTC
development model and the feature framework are not available in the new
world of SharePoint Online and Office 365.
Nevertheless, you can do remote provisioning. This means using SharePoint
CSOM to provision artifacts instead of using the CAML/XML-based feature
framework. While transforming FTC solutions, WSP (Windows SharePoint
Services) packages, and Sandboxed solutions into the new add-in model, you
should also approach provisioning artifacts and settings in a more maintainable
should also approach provisioning artifacts and settings in a more maintainable
manner. Using pure CSOM enables you to control by code the provisioning and
the versioning of artifacts. This is the option Microsoft engineering officially
recommends because CAML/XML-based provisioning will cause maintenance
challenges with the evolving templates or definitions. Nevertheless, doing all the
provisioning manually and by writing CSOM-based code could be a long and
painful task.
Luckily, the PnP Core team and the entire OfficeDev PnP community have
built an engine that is part of the PnP Core library. It leverages the PnP Core
library extensions, enabling you to provision artifacts easily. Moreover, the PnP
Remote Provisioning Engine enables you to model your artifacts within the web
browser by using a prototype or a model site and to extract the designed artifacts
into a template file, which can then be applied to any target site.
The overall goal of the PnP Remote Provisioning Engine is to make it simple
to accomplish useful and common tasks while provisioning sites and artifacts.
The provisioning template can be created in memory by using a domain model
that is defined within the PnP Core library, or, as already stated, it can be
persisted as a file. In the latter scenario, out of the box the file can be an XML
file based on a community-defined XML Schema
(https://github.com/OfficeDev/PnP-Provisioning-Schema/), or it can be a JSON
file. Since June 2016, there is also support for an OpenXML file format, which
includes all of the information for provisioning artifacts within a unique ZIP file
that adheres to the OPC (Open Packaging Conventions) specification. By
default, a template can be read from or written to a file system folder, a
document library in SharePoint, or a container in Azure Blob storage, which is a
cloud-based repository for binary blobs (that is, files) available on Microsoft
Azure. However, from an architectural perspective, you can implement your
own template formatter and your custom persistence provider to save or load a
template with whatever format and persistence storage you like.1
1 If you want further information about the PnP Remote Provisioning Engine, you can watch the
following video on Channel 9: https://channel9.msdn.com/blogs/OfficeDevPnP/Getting-Started-with-
PnP-Provisioning-Engine.
Built on top of the PnP Core library and the PnP Remote Provisioning Engine
is the PnP Partner Pack, which can be considered a starter kit for customers and
partners. The PnP Partner Pack provides most of the patterns described by PnP
within a unique and articulated solution that can be installed on any Office 365
tenant. The solution enables you to provide a high-level user interface for
managing self-service site collections and site creation based on stored PnP
provisioning templates and gives you the capability to save and manage a
companywide catalog of provisioning templates. Moreover, the PnP Partner
Pack includes other interesting samples and tools in the fields of governance and
maintenance of SharePoint Online site collections.
Another powerful component of the PnP offering is the PnP PowerShell
cmdlets. On GitHub (https://github.com/OfficeDev/PnPPowerShell)—thanks to
the efforts of Erwin van Hunen (https://twitter.com/erwinvanhunen)—you can
find about a hundred open source custom cmdlets that make it possible to
consume SharePoint on-premises (2013/2016) and SharePoint Online from
PowerShell. You can accomplish tasks like creating site collections and sites,
lists, content types, site columns, and so on. As an Office 365 developer, you
will need to have this set of cmdlets on your environment.
To install the PnP PowerShell cmdlets, you can download a .MSI setup
package from GitHub by browsing to the following URL:
https://github.com/OfficeDev/PnPPowerShell/tree/master/Binaries.
There, you will find three flavors, which pair the same options as the PnP
Core library:
SharePointPnPPowerShell2013.msi Targets SharePoint 2013 on-
premises
SharePointPnPPowerShell2016.msi Targets SharePoint 2016 on-
premises
SharePointPnPPowerShellOnline.msi Targets SharePoint Online
Another option you have, if your main operating system is Windows 10 or if
you have at least PowerShell 3.0 and PowerShell Package Management, is to
install the PnP PowerShell package directly within PowerShell by using the
Install-Module cmdlet, using any of the following statements, based on the
version of cmdlets that you want to install:
Click here to view code image
Install-Module SharePointPnPPowerShellOnline
Install-Module SharePointPnPPowerShell2016
Install-Module SharePointPnPPowerShell2013
Once you have installed the PnP PowerShell cmdlets on your machine and the
PnP Core library in your development projects, you will have a rich set of tools
for developing Office 365 solutions.
Note
You will also need to install the latest version of a Node.js runtime, which can
be downloaded from the official Node.js site: https://nodejs.org/en/. With
Node.js you will also use NPM, which is a package manager similar to NuGet
for .NET. It is suggested that you update the NPM package manager to the latest
version, which can be accomplished by using NPM by itself with a command
like the following:
npm install -g npm
The solutions that you create with Node.js can be easily hosted on the cloud—
for example, using Microsoft Azure. This realizes the potential of any Node.js
solution for Office 365 or SharePoint Framework.
Moreover, it is useful to have a console emulator to play with Node.js and
some other tools that you will install later. Thus, it is suggested that you install
Curl for Windows (http://www.confusedbycode.com/curl/) and Cmder for
Windows (http://cmder.net/), which is a console emulator for Windows.
There are two more useful tools for automating scaffolding of solutions and
compilation tasks. The first tool is Yeoman (http://yeoman.io/), which can be
installed through NPM while in the Cmder console, for example. The second
tool is Gulp (http://gulpjs.com/), which automates the compilation and release of
code through a set of customizable workflows. Gulp can also be installed using
NPM from the console emulator.
In Section IV of this book, “SharePoint & Office Add-ins,” you will see some
of these tools in action.
Web applications
The first flavor of applications for Office 365 is applications with a web-based
user interface. You can develop such applications with whatever development
user interface. You can develop such applications with whatever development
environment you like. However, if you usually develop with Microsoft
technologies, you probably will use Microsoft ASP.NET.
Nowdays, one of the most common techniques for developing ASP.NET
applications is to leverage the Model-View-Controller (MVC) pattern and to
create an ASP.NET MVC application. However, from a technological
perspective, you are free to create an ASP.NET WebForm application. That said,
in the field of Office 365 development, you should consider that there are many
more samples on the network for MVC than for WebForm.
Moreover, if you are not a .NET developer and, for example, prefer to develop
web applications using PHP or Java, you can still realize almost the same
potentials that you could by using Microsoft .NET.
Note
FIGURE 2-3 A sample Office 365 application hosted externally from Office 365
This solution can be applied to any kind of application, whatever Office 365
services it consumes or extends. In fact, the application will provide the entire
UI, and the Office 365 services will be consumed through the Microsoft Graph.
You will have to register the application in Microsoft Azure Active Directory
(AD), and in Chapter 4, “Azure Active Directory and security,” you will learn
how to accomplish this task.
The end users will be brought to the custom application by clicking a tile in
the Office 365 app launcher, by following a direct link in a SharePoint site page,
or by activating an add-in in the UI of Office client, among other methods. The
application will communicate with Office 365 by using REST over HTTPS and
the Microsoft Graph or the SharePoint REST API. If the application has to
execute some background and/or long-running processes, you can apply some
decoupling and asynchronous patterns that will be discussed in more detail in
Chapter 10.
A typical use case for this kind of application is a scenario in which you have
to coordinate multiple services, like Exchange Online, SharePoint Online, the
Office 365 Groups, and so on, and you need to provide a unique and common
UI/UX to the end users, working in the background with the back-end services.
From an implementation cost perspective, these solutions guarantee a very
convenient cost of development because they are basically simple web
applications that consume Office 365 through a set of documented APIs.
These kinds of solutions are also cost-effective from a knowledge and
learning perspective. They have a very low total cost of development and
maintenance because the developers can be general ASP.NET developers—you
don’t need to have dedicated developers with deep vertical expertise on every
involved service of the Office 365 ecosystem. Of course, knowing a little bit
about Office 365 development could be useful.
Single-page applications
A third option for developing an Office 365 application is to create a single-page
application (SPA). An SPA is basically a web application that provides the UX
through a unique page with some client-side JavaScript code. These kinds of
solutions typically are based on one of the JavaScript toolkits available on the
solutions typically are based on one of the JavaScript toolkits available on the
market and in the community like KnockoutJS, AngularJS, and many others.
The key point of an SPA is to provide the end users with an immersive UX
based on a single page that mimics the experience of a classic desktop solution,
avoiding the need to reload the whole page or change the current page. An SPA
typically leverages AJAX and WebSockets to communicate with the server,
dynamically updating the UI by leveraging HTML5, CSS3, JavaScript/jQuery,
and/or any other toolkit for creating dynamic pages in JavaScript. A common
scenario in these use cases is to have a set of custom Web APIs in the back end
hosted, for example, on Azure and invoked by the client-side code in the SPA.
Such applications are usually hosted within SharePoint Online in dedicated
pages, even if theorically you can host them wherever you like. Hosting them
within the domain that provides SharePoint Online content makes it simple to
solve any cross-domain or cross-origin resource sharing (CORS) issue—in
particular, if those applications just need to consume resources hosted in
SharePoint Online, which is often the case.
Typical examples of native SPAs are Office 365 Video and most of the
NextGen Portals that Microsoft is releasing. The upcoming SharePoint
Framework can also leverage the same development model.
Native applications
Another common use case is related to native applications, which are custom
applications targeting specific devices and/or client operating systems.
Typically, they are mobile applications or desktop applications. They could be
apps for smartphones, for tablets, or for any other devices. The key point is that
the UI/UX is built using device-specific frameworks and programming
languages, while the back-end information and services could be directly
provided by the Microsoft Graph and Office 365 or could be a set of custom
REST API hosted in a web API application. Regardless of the kind of REST API
the native application consumes, from an architectural perspective the
application will leverage Azure AD and OpenID Connect for users’
authentication and the Open Authorization protocol (OAuth) for users’
authorization.
In the Microsoft technology landscape, you can use toolkits like Visual Studio
2015 and Xamarin to create multidevice applications for iOS, Android, and
Windows Phone and for creating Universal Windows Platform (UWP) apps.
From an architectural perspective, having a common set of APIs in the back end
and a common UI/UX framework that targets all the potential platforms makes
the overall solution promising.
the overall solution promising.
These kinds of applications are completely integrated with the out-of-box
Office 365 and Microsoft Azure offerings, and through them you can realize
great potential.
SharePoint Add-ins
The first and main scope of custom development in SharePoint Online is the
development of SharePoint Add-ins. To be fair, all topics related to developing
SharePoint Add-ins for SharePoint Online also target SharePoint on-premises.
The environments share the same development and extensibility model, so
despite the need to learn a new development model—especially if you come
from the FTC (Full Trust Code) development model—the return on investment
is worth it. This is an excellent feature of the SharePoint Add-in model because
you write once and use twice (online and on-premises).
Note
Nowdays, you can develop a custom SharePoint Add-in whenever you need to
create a custom solution that mainly targets SharePoint Online. As you saw in
the previous section, “Office 365 applications development,” the capability to
develop an Office 365 application that can target SharePoint Online and all the
other services of the Office 365 ecosystem makes this last option more
interesting.
The main difference between a SharePoint Add-in and an Office 365
application is that the SharePoint Add-in is registered in Microsoft Azure ACS
(Access Control Service) through the add-in registration UI provided by
SharePoint Online and can consume SharePoint Online only. In contrast, an
Office 365 application is registered in Azure AD and can consume any service
provided by Office 365 as long as it has proper permissions.
However, there are use cases in which you will need to create a SharePoint
Add-in to achieve your results. Here is a short list of the most common scenarios
in which you probably will create a SharePoint Add-in instead of an Office 365
in which you probably will create a SharePoint Add-in instead of an Office 365
application:
Custom overriding of SharePoint UI through JavaScript embedding
Customize the out-of-box UI and behavior of SharePoint by embedding
custom JavaScript files—for example, through a user’s custom actions—
and by changing the HTML DOM (Document Object Model) of the pages
or the behavior of some of the out-of-box commands. You can achieve the
same result by creating an Office 365 application, but the investment of
having a superset of capabilities just to provision a user’s custom action is
not worth the time required.
Custom SharePoint workflow solutions developed in Visual Studio
2015 Create SharePoint hosted workflow applications that can be executed
in integrated mode to provide custom workflows through the standard UI
of SharePoint and by extending lists and libraries within a host site. This
cannot be achieved by creating an Office 365 application because the
workflow manager component is available only within a SharePoint Add-
in. Moreover, a workflow-integrated application can only be created
through a SharePoint Add-in.
Custom list and library forms Override the out-of-box add/display/edit
forms of lists or libraries by replacing them with custom pages hosted in a
provider-hosted SharePoint Add-in (PHA). Like UI overriding via
JavaScript, this can be done by using an Office 365 application, but it is
easier to create a SharePoint Add-in.
Remote event receivers Create and register remote event receivers based
on a WCF (Windows Communication Foundation) channel to handle
events related to sites, lists, libraries, and so on. This is another typical
SharePoint-oriented development scenario in which an Office 365
application does not fit. You will see more details about this topic in the
upcoming section “Remote event receivers” of this chapter.
Any SharePoint on-premises custom development solution You cannot
customize a SharePoint on-premises farm with an Office 365 application,
and unless you are in a hybrid topology, generally you cannot consume
SharePoint on-premises from Office 365. A custom solution that has to
target both SharePoint Online and SharePoint on-premises falls into this
category. Because this book is about programming Office 365 and not
about SharePoint on-premises, this last bullet can be considered an edge
case.
In this book, you will not dig into development of SharePoint Add-ins because
In this book, you will not dig into development of SharePoint Add-ins because
the focus is the entire Office 365 development. Nevertheless, it is important to
know real use cases that require you to create a customization solution tied to
SharePoint Online.
Note
Remote provisioning
One last scenario in which you probably will create a solution specific for
SharePoint Online is the remote provisioning of artifacts and settings onto a
target SharePoint Online site, site collection, or tenant.
Earlier in this chapter, you saw the concept of remote provisioning in
SharePoint Online as the capability to set up configuration settings and to create
artifacts like lists, libraries, content types, site columns, and so on by using
CSOM. To do that, you will need a custom SharePoint Add-in or a remote timer
job that will execute the CSOM requests against the target SharePoint Online.
If you just need to provision a site based on a template and you want to
leverage the PnP Remote Provisioning Engine, it will suffice to use the PnP
PowerShell cmdlets to get a template from a model site and save it as an
OpenXML, XML, or JSON file. For example, by using the PnP PowerShell
extensions you can export a site as a template into an OpenXML .pnp file,
including all the taxonomy terms and persisting any branding file, by using the
following syntax:
Click here to view code image
Connect-SPOnline "https://[tenant-
name].sharepoint.com/sites/[template-site]"
Connect-SPOnline "https://[tenant-name].sharepoint.com/sites/[target-
site]"
Summary
In this chapter, you had an overview of the most common and useful
development techniques for extending and customizing Office 365. First, you
learned about how to set up your development environment properly, not only
installing the most common tools like Visual Studio Code or Visual Studio 2015,
but also installing and leveraging third-parties’ SDKs, libraries, and community
projects to improve your code and your quality of life.
Then, you discovered Office 365 applications and the various flavors of
projects like web applications, including full-page web applications, web API
applications, single-page applications, and native applications. You also learned
about the new Office 365 Connectors.
From a SharePoint perspective, you were introduced to SharePoint Add-ins,
remote timer jobs, and remote event receivers, and you saw that you can use
them to extend the SharePoint Online and SharePoint on-premises experiences.
You also had an overview of the remote provisioning techniques, which enable
you to provision artifacts and configurations settings on both SharePoint Online
and SharePoint on-premises.
Last, you had a sneak preview of the Office client development model. You
saw the Office Add-in flavors available at the time of this writing and the
support matrix related to the various versions of Office Online, Office 2013 or
2016 for Windows, and Office 2016 for Mac or iPad.
All the concepts you learned in this chapter will be covered in detail in the
upcoming sections, so by reading the remaining chapters of this book you will
learn how to create real solutions leveraging all the potentials of the Office 365
ecosystem as a complete platform for developing custom solutions.
ecosystem as a complete platform for developing custom solutions.
Part II: Office 365 programming
model
CHAPTER 3 Microsoft Graph API reference
CHAPTER 4 Azure Active Directory and security
This part introduces the Microsoft Office 365 programming model from a
developer’s perspective. The overall goal of Part II is to provide you a solid
foundation for understanding the following parts and for mastering the Office
365 programming model.
Chapter 3, “Microsoft Graph API reference,” provides a brief introduction and
a reference about the Microsoft Graph API. The chapter illustrates the Microsoft
Graph API, the main endpoints available, and how to invoke them using bare
HTTP requests from whatever platform you like. The chapter targets any device
or development framework, as long as it supports making HTTP requests.
Chapter 4, “Azure Active Directory and security,” introduces and explains the
security infrastructure that sits under the cover of the Microsoft Graph API. The
chapter covers the architecture of Microsoft Azure Active Directory (Azure AD)
and its main capabilities. Moreover, the chapter shows you how to configure an
app in Azure AD to authenticate users against an online tenant and explains how
to consume the Microsoft Graph API.
Chapter 3. Microsoft Graph API reference
This chapter introduces the Microsoft Graph API and provides a practical
reference about how to consume it from any device and any development
framework. To better understand this chapter, you should have a good
knowledge about the HTTP protocol, the REST (Representational State
Transfer) protocol, and JSON (JavaScript Object Notation).
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users/$entity",
"@odata.type": "#microsoft.graph.user",
"@odata.id": "users/bea7a848-0459-4bee-9034-5513ee7f66e0",
"businessPhones": [
"030-22446688"
],
"displayName": "Paolo Pialorsi",
"givenName": "Paolo",
"jobTitle": null,
"mail": "[email protected]",
"mobilePhone": "+391234567890",
"officeLocation": null,
"preferredLanguage": "en-US",
"surname": "Pialorsi",
"userPrincipalName": "[email protected]",
"id": "bea7a848-0459-4bee-9034-5513ee7f66e0"
}
As you can see, there is information about the JSON object itself, which is an
instance of a Microsoft.Graph.User type. There is information about the current
user such as address, display name, telephone, email, and so on. Another set of
information that deserves attention is the list of properties related to on-premises
directory synchronization, if it is configured. For example, you can see the on-
premises user’s SID (onPremisesImmutableId), when the last synchronization
happened (onPremisesLastSyncDateTime), and so on. Last, you will find the
proxy address and the fundamental UPN (User Principal Name), which will be a
unique identifier for the current user.
You can use the UPN to access any specific user profile as long as you have
the permissions to consume the user’s directory in Azure AD. Let’s say that you
want to access the entire list of users for a specific tenant. You can make an
HTTP GET request for the following URL:
https://graph.microsoft.comv1.0users. In this case, the result will be a JSON
representation of an array of Microsoft.Graph.User objects.
If you want to access the profile properties of a specific user, you can make an
HTTP GET request for the following URL:
https://graph.microsoft.comv1.0users/UPN. For example, when the current user
has a UPN value like [email protected], the following URL defines a direct
entry point to the user’s profile:
https://graph.microsoft.comv1.0users/[email protected].
It is also possible to read the values of single properties instead of getting the
entire JSON object. Moreover, there are complex properties like the user’s photo
that can be accessed only through a direct request. To access a single property,
you can append the property name to the URL path of the user’s profile URL.
You can see such a request in Listing 3-2, in which the HTTP request headers
include the authentication information (the OAuth bearer token), which will be
explained in Chapter 4.
LISTING 3-2The HTTP GET request for the userPrincipalName property of the
current user
Click here to view code image
In Listing 3-3, you can see the response that you should get back.
LISTING 3-3 The HTTP response for the current user’s userPrincipalName
property
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: private
ContentType:
application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatib
le=false;charset=utf-8
Server: Microsoft-IIS/8.5
request-id: 0c2f199c-782e-4918-8bd0-91b0c246a9c8
client-request-id: 0c2f199c-782e-4918-8bd0-91b0c246a9c8
OData-Version: 4.0
OutBoundDuration: 71.2145
Duration: 113.3553
X-Powered-By: ASP.NET
Date: Sat, 05 Sep 2015 08:54:52 GMT
Content-Length: 192
{"@odata.context":"https://graph.microsoft.comv1.0$metadata#users('<UserID>')/us
palName","value":"paolo@<tenant>.onmicrosoft.com"}
Notice that the response contains a bunch of useful information like the ID of
the request, the target server version and engine, and the overall duration of the
request processing. The response is in JSON format, and you should deserialize
it. However, you can also directly access the bare property value as text by
appending the $value path to the property URL. The final URL will look like the
following:
https://graph.microsoft.comv1.0me/userPrincipalName/$value
This technique becomes increasingly useful when you want to retrieve binary
properties like the user’s photo. By providing the $value path at the end of the
user’s photo property, you will get back the binary image file directly, which is
useful for creating great user experiences in your applications. In Listing 3-4,
you can see the user’s photo request.
LISTING 3-4The HTTP GET request for the photo binary property value of the
current user
Click here to view code image
In Listing 3-5, you can see the response from an HTTP perspective.
LISTING 3-5 The HTTP response for the binary value of the current user’s photo
property
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: private
ContentType: image/jpeg
Server: Microsoft-IIS/8.5
request-id: 528841c7-eda8-4eb7-99f7-b241be2b66f9
client-request-id: 528841c7-eda8-4eb7-99f7-b241be2b66f9
OutBoundDuration: 1080.2745
Duration: 1268.9802
X-Powered-By: ASP.NET
Date: Sat, 05 Sep 2015 08:26:09 GMT
Content-Length: 31194
You can read information about yourself or other users, and you can update
that information if you have proper permissions. To change data or execute
operations, you will have to switch from HTTP GET requests to other HTTP
verbs like POST, PATCH, and so on.
For example, to update your current mobile phone number, you can make an
HTTP PATCH request against your profile URL
(https://graph.microsoft.comv1.0me). You will have to provide a JSON object
that defines the profile properties that you want to patch. It is fundamental in this
case to set the Content Type header of the request according to the JSON. For
example, in Listing 3-6, you can see the sample request for updating your mobile
phone number.
LISTING 3-6 The HTTP PATCH request to update the mobile phone number of
the current user
Click here to view code image
{ "mobilePhone":"+39123456789" }
In Listing 3-7, you can see the response that you should get back.
LISTING 3-7 The HTTP response for the current user’s profile update request
Click here to view code image
From a content perspective, the response just confirms that the operation was
successful (HTTP Status 204).
Security groups
Through the Microsoft Graph API, you can also access groups, including both
security groups, synchronized across premises using directory synchronization
tools, and the new Office 365 Groups, which are also called Unified Groups
from an Office 365 perspective. The new Office 365 Groups are covered later in
this chapter in the section “Working with Office 365 Groups.” The security
groups are accessible as objects of type Microsoft.Graph.Group through the
following URL:
https://graph.microsoft.comv1.0groups
The URL returns the entire list of groups, regardless of whether they are
security groups or Office 365 Groups. However, you can play with the
groupTypes property, which has a null value for security groups and a value of
Unified for any Office 365 Group.
You can also access any specific group by providing the group’s ID in the
URL. For example:
https://graph.microsoft.comv1.0groups/<Group_ObjectId>
To access the members of a specific group, you can add the members keyword
at the end of the single group endpoint URL.
Mail messages
As already stated, to access the current user’s mailbox, you can query the
https://graph.microsoft.comv1.0me/Messages URL. The result will be an array
of JSON objects of type Microsoft.Graph.Message or of type
Microsoft.Graph.EventMessage, which are the email messages and the event-
related messages in the current user’s mailbox, regardless of the folder in which
they are stored. In Listing 3-8, you can see an excerpt of the JSON result.
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.
pialorsi%40sharepoint-camp.com')
/messages",
"@odata.nextLink": "https://graph.microsoft.comv1.0me/Messages?
$skip=10",
"value": [
{
"@odata.type": "#Microsoft.Graph.Message",
"@odata.id":
"users/paolo.pialorsi%40sharepoint-
camp.com/Messages/AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwL
WFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENA
VRa8oaXCdnl9HAAIQUEEDAAA%3D",
"Id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tvVRa8oaXCdnl9HAAIQUEEDAAA=",
"ChangeKey": "CQAAABYAAABrFd4C2tvVRa8oaXCdnl9HAAIQVSNq",
"Categories": [],
"DateTimeCreated": "2015-09-03T03:22:23Z",
"DateTimeLastModified": "2015-09-03T03:22:23Z",
"Subject": "Sample message!",
"BodyPreview": "Hello from Office 365!",
"Body": {
"ContentType": "HTML",
"Content": " ... ",
},
"Importance": "Normal",
"HasAttachments": false,
"ParentFolderId":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQAuAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAENAAA=",
"From": {
"EmailAddress": {
"Address": "[email protected]",
"Name": "Contoso Team"
}
},
"Sender": {
"EmailAddress": {
"Address": "[email protected]",
"Name": "Contoso Team"
}
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "[email protected]",
"Name": "Paolo Pialorsi"
}
}
],
"CcRecipients": [],
"BccRecipients": [],
"ReplyTo": [
{
"EmailAddress": {
"Address": "[email protected]",
"Name": "[email protected]"
}
}
],
"ConversationId":
"AAQkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQAQAH2etUG4dspGkfBsjAGHi4M
"DateTimeReceived": "2015-09-03T03:22:23Z",
"DateTimeSent": "2015-09-03T03:22:13Z",
"IsDeliveryReceiptRequested": null,
"IsReadReceiptRequested": false,
"IsDraft": false,
"IsRead": false,
"WebLink": "https://outlook.office365.com/owa/?
ItemID=AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi
04NzAwLWFkZGExY2M
5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tv
l9HAAIQUEEDAAA%3D&exvsurl=1&viewmodel=ReadMessageItem"
},
...
]
}
It is interesting to notice that by default, the mail service will do output paging
and, unless you specify something different in the OData query sent to the
service, the default page size will be 10 items per page. At the beginning of the
JSON answer, you will find a property with name @odata.nexLink that contains
the URL to access the next page of results, and this kind of “next page” link will
be available in any requested page. As a result, developers are obliged to do
paging, which is a good habit but unfortunately is not always a common
practice.
Moreover, you can see that each message provides well-known information
like subject, sender, recipients, content, parent folder Id, and so on. One
fundamental piece of information for each message is the Id property. By
appending a specific message Id at the end of the messages path, you can access
that specific message item directly. The HTTP GET request will look like the
following, where the message Id has been truncated for typographic needs:
https://graph.microsoft.comv1.0me/messages/AAMk...AA=
The response will be a JSON object of type Microsoft.Graph.Message with
the same properties that were available for each message within the list of
messages.
Because we are using the OData protocol to query the Microsoft Graph API,
we can also use the standard protocol’s syntax to project a subset of properties or
to partition the results. For example, imagine that you want to retrieve just the
Id, Subject, From, and ToRecipients properties of the current message. In Listing
3-9, you can see the HTTP GET request to achieve this result, which will target
the following URL:
https://graph.microsoft.comv1.0me/messages/AAMk...AA=?
$select=Id,Subject,From,ToRecipients
LISTING 3-9 The HTTP GET request for a subset of properties for a specific email
message
Click here to view code image
GET v1.0me/messages/AAMk...AA=?
$select=Id,Subject,From,ToRecipients HTTP/1.1
In Listing 3-10, you can see the response that you should get back.
LISTING 3-10 The JSON response for a projection of properties of a specific email
message
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0<tenant>/$metadata#users('paolo.
pialorsi%40sharepoint
-camp.com')/Messages/$entity",
"@odata.type": "#Microsoft.Graph.Message",
"@odata.id": "users/paolo.pialorsi%40sharepoint-
camp.com/Messages/AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwL
WFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENA
VRa8oaXCdnl9HAAIQ3Vk_AAA%3D",
"@odata.etag": "W/\"CQAAABYAAABrFd4C2tvVRa8oaXCdnl9HAAJVc9Lf\"",
"Id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tvVRa8oaXCdnl9HAAIQ3Vk_AAA=",
"Subject": "This is a sample message!",
"From": {
"EmailAddress": {
"Address": "[email protected]",
"Name": "[email protected]"
}
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "[email protected]",
"Name": "Paolo Pialorsi"
}
}
]
}
If you want to filter all the messages in the current user’s inbox based on a
specific subject value, here is the corresponding OData query URL:
https://graph.microsoft.comv1.0me/messages?
$filter=Subject%20eq%20’Office%20365%20Book’
Sending an email message by using the Microsoft Graph API is also a simple
task. You just need to make an HTTP POST request with the JSON object
representing the message to send and targeting the URL of the list of messages
to store the message as a draft. In Listing 3-11, you can see the HTTP POST
request to achieve this result.
LISTING 3-11 The HTTP POST request to save a draft of a new email message
Click here to view code image
{
"Subject": "Sample email from Microsoft Graph API",
"Body": {
"ContentType": "HTML",
"Content": "Hey! This email comes to you from the
<b>Microsoft Graph API</b>!"
},
"Importance": "High",
"ToRecipients": [
{
"EmailAddress": {
"Address": "[email protected]"
}
}
]
}
The HTTP response that you will get back will be the JSON representation of
the just-saved draft message. In the message object, you will see the IsDraft
property with a value of true. To send that message, you will need to invoke the
send action, which can be addressed by appending the send keyword at the end
of the URL of the message and using an HTTP POST method. In Listing 3-12,
you can see a sample request to send a message.
LISTING 3-12 The HTTP POST request to send a draft email message
Click here to view code image
The HTTP response will confirm that the message has been sent by providing
an HTTP Status Code with a value of 202 (Accepted). Note that if you have sent
a message draft and you try to send it again, the REST API call will fail because
the engine will not find that draft (HTTP Status 404 Not Found). If you try to
retrieve the just-sent message, it will no longer be available. The message draft
has been sent and moved to the Sent Items folder, where you will find the
message with the IsDraft property with a value of false, which means that the
message has been sent.
To access a specific mail folder, you can use the mailFolders navigation
property of any Microsoft.Graph.User object for which the current user has the
rights to access the mailbox. For example, the following URL retrieves the list of
available mail folders:
https://graph.microsoft.comv1.0me/mailFolders
As with the mail messages, you can access any specific folder by Id, and you
can browse the messages of that specific folder by appending the messages
keyword to the URL of the folder.
Another option for sending an email message quickly is to directly leverage
the sendMail action, which is available for any object of type
Microsoft.Graph.User. In Listing 3-13, you can see the HTTP POST request to
invoke the action, which is available as microsoft.graph.sendMail or just
sendMail.
{
"Message": {
"Subject": "Sample email from Microsoft Graph API",
"Body": {
"ContentType": "HTML",
"Content": "Hey! This email comes to you from the
<b>Microsoft Graph API"
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "[email protected]"
}
}
]
},
"SaveToSentItems": "true"
}
Again, the HTTP response will confirm that the message has been sent by
providing an HTTP Status Code with a value of 202 (Accepted).
If you want to reply to a received message, you can leverage the reply action
provided by each message instance. You just need to make an HTTP POST
request targeting the message to which you want to reply and appending the
reply path to the URL of the message. Within the HTTP POST request body
message, you provide a JSON response made of a comment property, which will
be the response to the received message. In Listing 3-14, you can see a sample
reply to a message.
LISTING 3-14 The HTTP POST request to reply to a specific email message
Click here to view code image
{
"Comment": "Wow! This message is really amazing!"
}
The response will be empty with an HTTP Status Code with a value of 202
(Accepted).
To reply to all the recipients of a message, there is the replyAll action that has
to be invoked like the reply action. To forward the message to someone, you can
use the forward action, which accepts in the HTTP POST request a list of
recipients who will receive the forwarded message and an optional comment that
will be included in the forwarded message. In Listing 3-15, you can see a
message forwarding example.
{
"Comment": "Please read and give me a feedback...",
"ToRecipients": [
{
"EmailAddress": {
"Address": "[email protected]"
}
},
{
"EmailAddress": {
"Address": "[email protected]"
}
}
]
}
Some other interesting actions are move, which moves an email from one
folder to another, and copy, which copies a message from one folder to another.
Both the move and copy actions accept a JSON object that declares where to
move or copy the message.
One more common use case is the deletion of a message, which can be
accomplished by making an HTTP DELETE request targeting the URL of the
email message. In Listing 3-16, you can see the sample request syntax.
The response will be empty with an HTTP Status Code with a value of 204
(No Content).
The last use case related to messages and emails is the handling of
attachments. To enumerate the attachments of an email, if any, you need to make
an HTTP GET request targeting the email’s direct URL and appending the
attachments keyword at the end of the URL. This will give you access to the
collection of attachments. In Listing 3-17, you can see a sample request.
In Listing 3-18, you can see an excerpt of the JSON response that enumerates
the attachments of an email message.
LISTING 3-18An excerpt of the HTTP response that enumerates the attachments
of an email message
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: private
ContentType:
application/json;odata.metadata=minimal;odata.streaming=true
;IEEE754Compatible
=false;charset=utf-8
Server: Microsoft-IIS/8.5
request-id: a3c1cd57-f2a0-4c0b-8a76-409c7f848fea
client-request-id: a3c1cd57-f2a0-4c0b-8a76-409c7f848fea
OData-Version: 4.0
OutBoundDuration: 165.8664
Duration: 175.4871
X-Powered-By: ASP.NET
Date: Sat, 05 Sep 2015 14:15:39 GMT
Content-Length: 720
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.
pialorsi%40sharepoint-camp.com')
/Messages('AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7
BE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tvVRa8oaXCdnl9HAAIQ3Vk3AAA%3D')/At
,
"value": [
{
"@odata.type": "#Microsoft.Graph.fileAttachment",
"@odata.id": "users/paolo.pialorsi%40sharepoint-
camp.com/Messages/
AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwL
WFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENA
VRa8oaXCdnl9HAAIQ3Vk3AAA%3D/Attachments/AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFk
RlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tvVRa
AAIQ3Vk3AAABEgAQAElsx9CtSZdKkGk_UhYBJDQ%3D",
"Id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAENAABrFd4C2tvVRa8oaXCdnl9HAAIQ3Vk3AAABEgAQAElsx9CtSZdKk
=",
"Name": "CEM41wGUUAEjpHH.jpg",
"ContentType": "image/jpeg",
"Size": 110796,
"IsInline": false,
"DateTimeLastModified": "2015-05-06T03:45:26Z",
"ContentId":
"[email protected]",
"ContentLocation": null,
"IsContactPhoto": false,
"ContentBytes": "LzlqLzRBQVFTa1pKUmdBQkFRQUFBUUFCQUFELzJ..."
}
]
}
Contacts
The current user’s contacts are another useful capability that is available through
the new Microsoft Graph API. To consume the contacts, you have to make an
HTTP GET request against the following URL:
https://graph.microsoft.comv1.0<user>/contacts
In Listing 3-19, you can see an excerpt of the resulting JSON response, which
represents an array of objects of type Microsoft.Graph.Contact.
LISTING 3-19An excerpt of the HTTP response that enumerates the organizational
or personal contacts in a tenant
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.pialorsi%40sharepoint-
camp.com')
/contacts",
"value": [
{
"@odata.etag":
"W/\"EQAAABYAAABrFd4C2tvVRa8oaXCdnl9HAAIQ4ll9\"",
"id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAEPAABrFd4C2tvVRa8oaXCdnl9HAAIQ3XCjAAA=",
"createdDateTime": "2015-09-06T08:41:30Z",
"lastModifiedDateTime": "2015-09-06T08:41:30Z",
"changeKey":
"EQAAABYAAABrFd4C2tvVRa8oaXCdnl9HAAIQ4ll9",
"categories": [],
"parentFolderId":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQAuAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAEPAAA=",
"birthday": null,
"fileAs": "Green, Mike",
"displayName": "Mike Green",
"givenName": "Mike",
"initials": null,
"middleName": null,
"nickName": null,
"surname": "Green",
"title": null,
"yomiGivenName": null,
"yomiSurname": null,
"yomiCompanyName": null,
"generation": null,
"emailAddresses": [
{
"name": "Mike Green",
"address": "[email protected]"
}
],
"imAddresses": [],
"jobTitle": null,
"companyName": null,
"department": null,
"officeLocation": null,
"profession": null,
"businessHomePage": null,
"assistantName": null,
"manager": null,
"homePhones": [],
"businessPhones": [],
"homeAddress": {},
"businessAddress": {},
"otherAddress": {},
"spouseName": null,
"personalNotes": null,
"children": []
},
...
]
}
The structure of a contact is defined in the metadata XML document for the
Microsoft Graph API. You can see there is an Id property, which can be used to
retrieve a specific contact instance directly. Moreover, there are all the common
properties for a contact, like displayName, emailAddresses, companyName, and
so on.
You can also browse users’ various contact folders by querying the
contactFolders navigation property of an object of type Microsoft.Graph.User.
Every contact folder can be accessed by Id, and you can browse its contacts
through the contacts navigation property. You can even add contacts or contact
folders by making an HTTP POST request against the target collection and
providing the JSON representation of the object to create.
TABLE 3-2 These are the main URL entry points for consuming calendars and
events.
All the entry point relative URLs illustrated in Table 3-2 are relative to the
https://graph.microsoft.comv1.0 base URL. For example, in Listing 3-20, you
can see the JSON representation of the default calendar for the current user.
LISTING 3-20 An excerpt of the HTTP response that represents the default
calendar of the current user
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0<tenant>/$metadata#users('paolo.
pialorsi%40sharepoint
"@odata.type": "#Microsoft.Graph.Calendar",
"@odata.id": "users/paolo.pialorsi%40sharepoint-
camp.com/Calendar",
"Id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAEGAABrFd4C2tvVRa8oaXCdnl9HAAAAAAUSAAA=",
"Name": "Calendar",
"ChangeKey": "axXeAtrb1UWvKGlwnZ5fRwACEOJhrw==",
"Color": "LightGreen"
}
LISTING 3-21 The HTTP request to get a calendar view on the current user’s
default calendar
Click here to view code image
GET v1.0mecalendarcalendarView?
startDateTime=2015-09-01T12:00:00Z&endDateTime=2015-09-
30T12:00:00Z HTTP/1.1
In Listing 3-22, you can see an excerpt of the JSON response, which is an
array of objects of type Microsoft.Graph.Event.
LISTING 3-22An excerpt of the JSON response for a calendar view on the current
user’s default calendar
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.pialorsi%40sharepoint-
camp.com')
calendarcalendarView",
"value": [
{
"@odata.type": "#microsoft.graph.event",
"@odata.id":
"users/paolo.pialorsi%40sharepoint-
camp.comcalendarcalendarView/AAMkADU4Zjk3ZTQzLWFjMDct
NDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCd
EOAABrFd4C2tvVRa8oaXCdnl9HAAIQ3XhyAAA%3D",
"@odata.etag":
"W/\"axXeAtrb1UWvKGlwnZ5fRwACEOJdvQ==\"",
"@odata.editLink":
"users/paolo.pialorsi%40sharepoint-
camp.comcalendarcalendarView/AAMkADU4Zjk3ZTQzLWFjMDct
NDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCd
EOAABrFd4C2tvVRa8oaXCdnl9HAAIQ3XhyAAA%3D",
"id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAEOAABrFd4C2tvVRa8oaXCdnl9HAAIQ3XhyAAA=",
"[email protected]": "#DateTimeOffset",
"createdDateTime": "2015-09-06T16:15:13.6997343Z",
"[email protected]": "#DateTimeOffset",
"lastModifiedDateTime": "2015-09-06T16:16:00.310226Z",
"changeKey": "axXeAtrb1UWvKGlwnZ5fRwACEOJdvQ==",
"[email protected]": "#Collection(String)",
"categories": [],
"originalStartTimeZone": "W. Europe Standard Time",
"originalEndTimeZone": "W. Europe Standard Time",
"responseStatus": {
"@odata.type": "#microsoft.graph.responseStatus",
"response": "organizer",
"[email protected]": "#DateTimeOffset",
"time": "0001-01-01T00:00:00Z"
},
"iCalUId":
"040000008200E00074C5B7101A82E008000000009B481236BFE8D00100000000000000001000000
41F7F408C5AFB6669E97439",
"reminderMinutesBeforeStart": 15,
"isReminderOn": false,
"hasAttachments": false,
"subject": "Sample Meeting",
"body": {
"@odata.type": "#microsoft.graph.itemBody",
"contentType": "html",
"content": "<html>...</html>\r\n"
},
"bodyPreview": "This is the description of a sample
meeting!",
"importance": "normal",
"sensitivity": "normal",
"start": {
"@odata.type":
"#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-09-06T16:30:00.0000000",
"timeZone": "UTC"
},
"end": {
"@odata.type":
"#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-09-06T17:00:00.0000000",
"timeZone": "UTC"
},
"location": {
"@odata.type": "#microsoft.graph.location",
"displayName": "Main Office"
},
"isAllDay": false,
"isCancelled": false,
"isOrganizer": true,
"recurrence": null,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "busy",
"type": "singleInstance",
"[email protected]":
"#Collection(microsoft.graph.attendee)",
"attendees": [],
"organizer": {
"@odata.type": "#microsoft.graph.recipient",
"emailAddress": {
"@odata.type":
"#microsoft.graph.emailAddress",
"name": "Paolo Pialorsi",
"address": "paolo.pialorsi@sharepoint-
camp.com"
}
},
"webLink": "https://outlook.office365.com/owa/?
ItemID=AAMkADU4Zjk3ZTQzLWFjMDct
NDM5Mi04NzAwLWFkZGExY2M
5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAAAEOAABrFd4C2tv
l9HAAIQ3XhyAAA%3D&exvsurl=1&viewmodel=ICalendarItemDetailsViewModelFactory",
},
...
]
}
As you can see, the response includes all the typical information for an event,
like Subject, Body, Start and End dates (including their time zone), ShowAs,
Attendees, Responses, Organizer, and so on.
You can also access the entire list of events for the current user by invoking
the events navigation property of the current calendar or of the current user
through an HTTP GET request. The events navigation property will give you
back a JSON array of Microsoft.Graph.Event objects.
As with email messages, discussed in the previous section, you can add,
update, or delete calendar events by leveraging the various HTTP verbs. For
example, in Listing 3-23, you can see an HTTP request to create a new event in
the current user’s default calendar.
LISTING 3-23 The HTTP request to add a new event to the current user’s default
calendar
Click here to view code image
{
"Subject": "Sample meeting create via Microsoft Graph API",
"Body": {
"ContentType": "HTML",
"Content": "The <b>Microsoft Graph API</b> really rock!"
},
"start": {
"@odata.type": "#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-12-22T16:30:00.0000000",
"timeZone": "UTC"
},
"end": {
"@odata.type": "#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-12-22T17:00:00.0000000",
"timeZone": "UTC"
},
"Attendees": [
{
"EmailAddress": {
"Address": "[email protected]",
"Name": "Paolo Pialorsi"
},
"Type": "Required"
}
],
"Location": {
"DisplayName": "Headquarters"
},
"ShowAs": "Busy",
}
The HTTP response will confirm that the event has been created by providing
an HTTP Status Code with a value of 201 (Created). If you want to update an
existing event, you can make an HTTP PATCH request, targeting that event by
Id and sending the updated properties as a JSON object. For example, in Listing
3-24, you can see how to update the Subject property of the just-created event.
LISTING 3-24 The HTTP request to update an existing event in the current user’s
default calendar
Click here to view code image
{
"Subject": "Sample meeting create via Microsoft Graph AP –
Updated!"
}
The HTTP response will confirm the successful update by providing an HTTP
Status Code with a value of 200 (OK) and providing the JSON serialization of
the updated event in the response body. Last, if you want to delete an event from
a calendar, you can use the HTTP DELETE method, targeting the event URL.
The response will be an HTTP Status Code 204 (No Content), meaning that the
event has been deleted. Under the cover, Microsoft Exchange Online will handle
all the email notifications like sending invitations, updates, and event
cancellations.
It is interesting to note that the same REST-based techniques used for
managing single events can be used to manage calendars. For example, you can
managing single events can be used to manage calendars. For example, you can
create, update, or delete a secondary calendar by targeting the collection:
https://graph.microsoft.comv1.0me/calendars
This is a powerful capability that enables you to create custom software
solutions that can completely handle messages, contacts, calendars, and events
via REST.
Event invitations
Another common scenario is to manage invitations for events sent to the current
user by third parties. In Microsoft Exchange Online, any meeting invitation will
automatically be placed in the target user’s default calendar, as happens on-
premises. Thus, to access an invitation, you just need to target the specific
calendar event object that you want to manage by providing the Id of the object.
In Listing 3-25, you can see an excerpt of a meeting request the current user has
received.
LISTING 3-25 An excerpt of the JSON object representing a meeting request the
current user has received
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.pialorsi%40sharepoint-
camp.com')
CalendarEvents/$entity",
"@odata.type": "#Microsoft.Graph.Event",
"@odata.id":
"users/paolo%40PiaSysDev.onmicrosoft.comCalendarEvents/AAMkADU4Zjk3ZTQzLWFjMDctN
zAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZBE6ONBwBrFd4C2tvVRa8oaXCdnl9HAAAAA
C2tvVRa8oaXCdnl9HAAIQ3Xh2AAA%3D",
"Id":
"AAMkADU4Zjk3ZTQzLWFjMDctNDM5Mi04NzAwLWFkZGExY2M5NDRlZQBGAAAAAACIOUtE7VENSpDAypZ
d4C2tvVRa8oaXCdnl9HAAAAAAEOAABrFd4C2tvVRa8oaXCdnl9HAAIQ3Xh2AAA=",
...
"Importance": "Normal",
"HasAttachments": false,
"start": { "@odata.type":
"#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-12-22T16:30:00.0000000",
"timeZone": "UTC"
}, "end": {
"@odata.type": "#microsoft.graph.dateTimeTimeZone",
"dateTime": "2015-12-22T17:00:00.0000000",
"timeZone": "UTC"
},
...
"ResponseStatus": {
"Response": "NotResponded",
"Time": "0001-01-01T00:00:00Z"
},
...
}
As you can see highlighted in bold text, there are Id and ResponseStatus
properties, together with the rich set of properties defining the event. If the
ResponseStatus property has a value of NotResponded for the property with
name Response, it means that the meeting request is pending response.
To accept the meeting request, you can make an HTTP POST request
targeting the event URL and appending the accept operation to the URL. The
ResponseStatus property of the target event will assume a value of Accepted for
the property with name Response, and the Time property will assume the value
of the date and time when you accepted the meeting request. To decline a
meeting request, the operation to append to the URL of the event is decline. To
give a tentative answer, you can append the tentativelyAccept operation to the
URL of the event.
Regardless of whether you accept, decline, or tentatively accept the meeting
request, you will have the option to provide to the meeting organizer a response
message that will be provided to the REST API as a JSON object in the body of
the request. In Listing 3-26, you can see a sample HTTP request to accept a
meeting request.
{
"Comment": "Sure, I'm looking forward to meet you!"
}
LISTING 3-27 The HTTP GET request for the OneDrive for Business root folder
for the current user
Click here to view code image
LISTING 3-28 An excerpt of the JSON response for the OneDrive for Business root
folder for the current user
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.pialorsi%40sharepoint-
camp.com')
driveroot/children",
"value": [
{
"@odata.type": "#microsoft.graph.driveItem",
"@odata.id":
"users/paolo.pialorsi%40sharepoint-
camp.comdriverootchildren01MDKYG3F4IHNFZ6ZQCVCIKPZ4
KVAHJDCH",
"@odata.etag": "\"{5CDA41BC-30FB-4415-853F-
3C5540748C47},2\"",
"@odata.editLink": "users/paolo.pialorsi%40sharepoint-
camp.comdriveroot/chil
dren/01MDKYG3G3MLQJYQ7CUZG3GQRA
7MBBY57D",
"createdBy": {
"@odata.type": "#microsoft.graph.identitySet",
"user": {
"@odata.type": "#microsoft.graph.identity",
"id": "bea7a848-0459-4bee-9034-5513ee7f66e0",
"displayName": "Paolo Pialorsi"
}
},
"[email protected]": "#DateTimeOffset",
"createdDateTime": "2013-06-17T14:52:51Z",
"eTag": "\"{5CDA41BC-30FB-4415-853F-
3C5540748C47},2\"",
"folder": {
"@odata.type": "#microsoft.graph.folder",
"childCount": 5
},
"id": "01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D",
"lastModifiedBy": {
"@odata.type": "#microsoft.graph.identitySet",
"user": {
"@odata.type": "#microsoft.graph.identity",
"id": "bea7a848-0459-4bee-9034-5513ee7f66e0",
"displayName": "Paolo Pialorsi"
}
},
"[email protected]": "#DateTimeOffset",
"lastModifiedDateTime": "2015-11-11T13:50:58Z",
"name": "Sample Share",
"parentReference": {
"@odata.type": "#microsoft.graph.itemReference",
"driveId":
"b!CznMej4oZ0KJYE8EtyeZy0qsx1HxNyJDoP4k_dtjPcx_
OZIUemtpSLqU6V6ZwmfE",
"id": "01MDKYG3F6Y2GOVW7725BZO354PWSELRRZ",
"path": "driveroot:"
},
"[email protected]": "#Int64",
"size": 0,
"webUrl": "https://piasysdev-
my.sharepoint.com/personal/paolo_pialorsi_
sharepoint-camp_com/Documents
/Sample%20Share",
},
...
{
"@odata.type": "#microsoft.graph.driveItem",
"@odata.id":
"users/paolo.pialorsi%40sharepoint-
camp.comdriverootchildren01MDKYG3HEDOLKC33SVREYSJKG
IHLCBRIC",
"@odata.etag": "\"{A1961BE4-726F-49AC-8925-
4641D620C502},1\"",
"@odata.editLink": "users/paolo.pialorsi%40sharepoint-
camp.comdriveroot/chil
dren/01MDKYG3HEDOLKC33SVREYSJKG
IHLCBRIC",
"createdBy": {
"@odata.type": "#microsoft.graph.identitySet",
"user": {
"@odata.type": "#microsoft.graph.identity",
"id": "bea7a848-0459-4bee-9034-5513ee7f66e0",
"displayName": "Paolo Pialorsi"
}
},
"[email protected]": "#DateTimeOffset",
"createdDateTime": "2015-02-15T09:33:04Z",
"cTag": "\"c:{A1961BE4-726F-49AC-8925-
4641D620C502},2\"",
"eTag": "\"{A1961BE4-726F-49AC-8925-
4641D620C502},1\"",
"file": {
"@odata.type": "#microsoft.graph.file"
},
"id": "01MDKYG3HEDOLKC33SVREYSJKGIHLCBRIC",
"lastModifiedBy": {
"@odata.type": "#microsoft.graph.identitySet",
"user": {
"@odata.type": "#microsoft.graph.identity",
"id": "bea7a848-0459-4bee-9034-5513ee7f66e0",
"displayName": "Paolo Pialorsi"
}
},
"[email protected]": "#DateTimeOffset",
"lastModifiedDateTime": "2015-02-15T09:33:04Z",
"name": "contract.docx",
"parentReference": {
"@odata.type": "#microsoft.graph.itemReference",
"driveId":
"b!CznMej4oZ0KJYE8EtyeZy0qsx1HxNyJDoP4k_dtjPcx_
OZIUemtpSLqU6V6ZwmfE",
"id": "01MDKYG3F6Y2GOVW7725BZO354PWSELRRZ",
"path": "driveroot:"
},
"[email protected]": "#Int64",
"size": 18698,
"webUrl":
"https://piasysdev-
my.sharepoint.com/personal/paolo_pialorsi_sharepoint-
camp_com/Documents
/contract.docx",
},
...
}
]
}
LISTING 3-29The HTTP GET request for the children of a folder in OneDrive for
Business for the current user
Click here to view code image
GET v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D/children
HTTP/1.1
GET v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D/children?
$select=name,
dateTimeCreated,dateTimeLastModified&$orderby=name HTTP/1.1
LISTING 3-31 An excerpt of the result for the query described in Listing 3-30
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#users('paolo.pialorsi%40sharepoint-
camp.com')
driveitems('01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D')/children(name,dateTimeCreated,
dateTimeLastModified)",
"value": [
{
"@odata.type": "#microsoft.graph.driveItem",
"@odata.id":
"users/paolo.pialorsi%40sharepoint.camp.comdriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA
children01MDKYG3HAJD7AK4IMLVFIEPWGF4SJCTBX",
"@odata.etag": "\"{05FE48E0-0C71-4A5D-823E-
C62F24914C37},2\"",
"@odata.editLink":
"users/paolo.pialorsi%40sharepoint-
camp.comdriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D
children01MDKYG3HAJD7AK4IMLVFIEPWGF4SJCTBX",
"id": "01MDKYG3HAJD7AK4IMLVFIEPWGF4SJCTBX",
"name": "Child Folder"
},
...
{
"@odata.type": "#microsoft.graph.driveItem",
"@odata.id":
"users/paolo.pialorsi%40sharepoint.camp.comdriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA
children01MDKYG3EIHAILISRHVBDJQVXKTI3LGHVA",
"@odata.etag": "\"{B4103888-274A-46A8-9856-
EA9A36B31EA0},1\"",
"@odata.editLink":
"users/paolo.pialorsi%40sharepoint.camp.comdriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA
children01MDKYG3EIHAILISRHVBDJQVXKTI3LGHVA",
"id": "01MDKYG3EIHAILISRHVBDJQVXKTI3LGHVA",
"name": "Office 365 Sample File.pdf"
},
...
]
}
One last use case to consider is downloading a file. To retrieve the raw
content of a file, you can make a direct HTTP GET request for the URL of the
Microsoft.Graph.driveItem object instance, appending the content function name
just after the file URL. The URL will look like the following:
https://graph.microsoft.comv1.0medriveitems/01MDKYG3EIHAILISRHVBDJQVXKTI3
The result will be an HTTP Status Code with a value of 302 (Redirect), which
will redirect the HTTP request to the real URL of the file in OneDrive for
Business, providing a temporary guest access token that will be valid for a small
amount of time (approximately two hours).
It is interesting to notice that you can also leverage the webUrl property,
which will open the file in the web browser instead of providing the content for
download. This capability is useful to access Microsoft Office files within the
browser to leverage the document rendering and editing capabilities of Office
web applications.
LISTING 3-32 The HTTP POST request to create a new folder in OneDrive for
Business
Click here to view code image
POST v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D/children
HTTP/1.1
{
"folder": {},
"name": "Child Folder - 2"
}
As you can see, the request is straightforward, and the response will be an
HTTP Status Code with a value of 201 (Created). In the body of the response,
you will find a JSON object that represents the just-created folder. If you plan to
use the newly created folder—for example, to upload some files into it—you can
grab the Id property for subsequent requests.
Once you have created a new folder and grabbed its Id property, to create a
file in that folder you can make an HTTP POST request against the collection of
children of the new folder. In Listing 3-33, you can see an example.
The HTTP POST request to upload a new file into a target folder in
LISTING 3-33
OneDrive for Business
Click here to view code image
POST v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D/children
HTTP/1.1
{
"file": {},
"name": "SampleImage.png"
}
After you have created the Microsoft.Graph.driveItem object, you will have to
provide the real content of the file. For example, if the file is an image in JPEG
format, you will have to upload the binary content of the image.
To upload or update the content of a file, you have to make an HTTP PUT
request for the URL of the file, appending the content operation name, setting
the proper content type for the request, and putting the file content in the body of
the request. In Listing 3-34, you can see a sample request to upload the content
of the text file created in Listing 3-33.
PUT v1.0medriveitems/01MDKYG3GK3HVL5QVCGFELA4Z7NECG2PON/content
HTTP/1.1
LISTING 3-35The HTTP POST request to copy a file from the current folder to
another folder in OneDrive for Business
Click here to view code image
POST v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D/copy
HTTP/1.1
{
"parentReference": {
"id": "01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D",
},
"name": "SampleImageCopied.png"
}
Again, the result will be an HTTP Status Code with a value of 202
(Accepted).
If you want to update the properties of the just-created or copied file, you can
use the HTTP PATCH method, targeting the URL of the file for which you want
to update the properties. In Listing 3-36, you can see a sample update request
that renames a file by patching the name property.
{
"name": "SampleImage-Renamed.png"
}
If you want to force your update, regardless any other concurrent update, you
can provide a value of “*” (without quotes) for the IF-MATCH header, or—at
your own risk—you can even skip the IF-MATCH header.
Aside from any concurrency issue, the result of a successful update will be the
JSON serialization of the updated Microsoft.Graph.driveItem object.
Last, to delete a file or a folder, you can leverage the HTTP DELETE method,
targeting the unique Id of the item to delete. Listing 3-37 shows how to make
such a request.
LISTING 3-37 The HTTP DELETE request to delete a file in OneDrive for
Business
Click here to view code image
DELETE v1.0medriveitems/01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D
HTTP/1.1
The response will be an HTTP Status Code with a value of 204 (No Content),
which implies a successful deletion of the target file or folder.
The HTTP GET request to search for files or folders containing the
LISTING 3-38
word “sample”
Click here to view code image
The response will look like the excerpt in Listing 3-39 and will include both
files and folders matching the search criteria.
LISTING 3-39An excerpt of the JSON array returned by invoking the search
operation for the root folder of the OneDrive for Business of the current user
Click here to view code image
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#driveItem",
"value": [
{
"@odata.type": "#microsoft.graph.driveItem",
"createdBy": {
"user": {
"displayName": "Paolo Pialorsi"
}
},
"createdDateTime": "2015-07-10T17:13:04Z",
"folder": {
"childCount": 0
},
"id": "01MDKYG3G3MLQJYQ7CUZG3GQRA7MBBY57D",
"lastModifiedBy": {
"user": {
"displayName": "Paolo Pialorsi"
}
},
"lastModifiedDateTime": "2015-07-10T17:13:04Z",
"name": "Sample Share",
"searchResult": {},
"size": 0,
"webUrl": "https://piasysdev-
my.sharepoint.com/personal/paolo_pialorsi_
sharepoint-camp_com/Documents
/Sample%20Share"
},
{
"@odata.type": "#microsoft.graph.driveItem",
"createdBy": {
"user": {
"displayName": "Paolo Pialorsi"
}
},
"createdDateTime": "2015-09-09T04:25:26Z",
"file": {},
"id": "01MDKYG3EIHAILISRHVBDJQVXKTI3LGHVA",
"lastModifiedBy": {
"user": {
"displayName": "Paolo Pialorsi"
}
},
"lastModifiedDateTime": "2013-07-11T00:23:31Z",
"name": "Office 365 Sample File.pdf",
"searchResult": {},
"size": 426620,
"webUrl": "https://piasysdev-
my.sharepoint.com/personal/paolo_pialorsi_
sharepoint-camp_com/Documents
Sample%20ShareOffice%20365%20Sample%20File.pdf"
},
...
]
}
Note that the search engine will not only search for files and folders with
matching names, but also will search the content inside files, as happens with the
classic Microsoft SharePoint search engine.
LISTING 3-40 The HTTP POST request to share a file for anonymous viewing
Click here to view code image
POST
v1.0medriveitems/01MDKYG3AUTHKROIRYDVHIHLBSZQU7ZNUE/microsoft.graph.createLink
HTTP/1.1
{
"type": "view",
"scope": "anonymous"
}
{
"@odata.context":
"https://graph.microsoft.comv1.0$metadata#permission",
"@odata.type": "#microsoft.graph.permission",
"id": "N0JCQTQ2MzItRTAxQi00RDlBLUFEMkEtNEZCMTZDRkFDODM3",
"roles": [
"read"
],
"link": {
"type": "view",
"webUrl":
"https://piasysdev-my.sharepoint.com/_layouts/15/guestaccess.aspx?
guestaccesstoken=OQ7bQd8
WRvu0OTxu2%2fuKi6KoHB%2bidQgE6tZVGnoC35c%3d&docid=2_0e34e1f4b0e5640388180ae9a6e4
}
}
The sharing link will be available in the webUrl property of the link object.
LISTING 3-42 An excerpt of the JSON array providing the groups of type Office
365 Group
Click here to view code image
{
"value": [
{
"id": "c748625f-ece2-4951-bab7-6e89ad8b6f10",
"description": "Sample Group",
"displayName": "Sample Group",
"groupTypes": [
"Unified"
],
"mail": "[email protected]",
"mailEnabled": true,
"mailNickname": "samplegroup",
"onPremisesLastSyncDateTime": null,
"onPremisesSecurityIdentifier": null,
"onPremisesSyncEnabled": null,
"proxyAddresses": [
"SMTP:[email protected]"
],
"securityEnabled": false,
"visibility": "Public"
}]
}
The result of such a URL query will be a JSON object that represents a
collection of Office 365 Groups. You can see the main properties of the Office
365 Group instance, including the id, displayName, mail address, visibility, and
so on.
As with any group, you can access a specific group by appending the id value
after the groups collection URL. For example, to retrieve a direct reference to
the Sample Group that is illustrated in Listing 3-42, you can make an HTTP
GET request for the following URL:
https://graph.microsoft.comv1.0groups/c748625f-ece2-4951-bab7-
6e89ad8b6f10
Moreover, every Office 365 Group provides a set of navigation properties to
browse the photo for the group, the calendar, the conversations, the files, and the
group’s members. For example, if you want to access the photo of the group,
here is the sample URL to use:
https://graph.microsoft.comv1.0groups/<group-id>/photo/$value
To access the calendar of a group, you just need to make an HTTP GET
request for a URL like the following:
https://graph.microsoft.comv1.0groups/<group-id>/calendar
You will get back an object of type Microsoft.Graph.Calendar, which can be
used exactly like any other calendar in the Microsoft Graph. You can refer to the
section “Consuming mail, contacts, and calendars” earlier in this chapter for
further details about how to manage calendars, events, and meetings.
To access a group’s conversations, there is a straightforward navigation
property called conversations, which can be used to get a list of all the
conversations or to access a specific conversation by id. In Listing 3-43, you can
see the JSON representation of a conversation.
LISTING 3-43An excerpt of the JSON representation of a conversation within an
Office 365 Group
Click here to view code image
{
"id":
"AAQkADdkNjZjMDUwLTA1ZmItNGRiNS04ZWI5LTdjOTQwZTk1MDZiNAAQABzdWzBNd3VKtWsenlfeLcw
"topic": "The new group Sample Group is ready",
"hasAttachments": true,
"lastDeliveredDateTime": "2015-12-03T12:01:30Z",
"uniqueSenders": [
"Sample Group"
],
"preview": "Welcome to the group Sample Group."
}
Summary
In this chapter, you learned about the Microsoft Graph API: its architecture and
the overall goal of having a unified set of API. Moreover, you learned how to
consume services related to users and groups in the Office Graph. You explored
how to consume Exchange Online–related services to browse email messages,
send a new message, and reply to a received message. You also saw how to
query and manage calendars and contacts. You learned how to query, update,
and manage files and folders in OneDrive for Business. Last, you explored how
to browse the new Office 365 Groups and their content.
to browse the new Office 365 Groups and their content.
In Chapter 4, you will learn how to authenticate against Azure AD and how to
leverage the OAuth 2.0 authorization protocol to consume the Microsoft Graph
API securely.
The information provided in this and the following chapter enables you to
consume the Microsoft Graph API from any device and using any development
platform as long as it supports the capability to fire HTTP requests and to
serialize/deserialize JSON objects.
Chapter 4. Azure Active Directory and security
This chapter explains the architecture and the capabilities of Microsoft Azure
Active Directory. You will learn how the authentication and authorization
engine of the Microsoft Graph API works and how to provision custom
applications and services that consume the Microsoft Graph API securely.
Identities in Azure AD
Whenever you deploy an Azure AD tenant, whether manually or automatically
within the creation of a new Office 365 tenant, you can manage users’ identities.
From an architectural perspective, there are three flavors of identities:
Cloud identities These are cloud-only identities that are created, managed,
and handled completely by Azure AD. This is the best option for small
businesses or widely geographically distributed companies that don’t want
to have an on-premises directory system or don’t have an on-premises
infrastructure at all. It is also a good option for applications that want to
support OpenID Connect, OAuth, or SAML through a cloud-based
offering.
Synchronized identities These are identities that get synchronized
between on-premises and the cloud. Microsoft provides tools that enable
you to synchronize objects between an on-premises directory system and
an Azure AD tenant. This is a suitable solution for businesses that have an
on-premises infrastructure, including a directory system—for example,
Microsoft Windows Active Directory—and are willing to share users’
identities across on-premises and the cloud, including the authentication
credentials (user names and hashes of passwords). In this scenario, end
users will consume cloud services—for example, Office 365—using a
completely cloud-based security infrastructure, which under the cover is
synchronized with the on-premises environment. Users will authenticate
against the Azure AD authentication infrastructure, providing credentials
that can be a replica of those defined on-premises and having the Same
Sign-On experience, which allows them to access the cloud services by
using the same credentials they have on-premises. From a management
perspective, some policies or password rules can vary between on-
premises and the copied synchronized identities on the cloud. Moreover, it
is fundamental to highlight that the synchronization process does not copy
users’ passwords to the cloud. Rather, it stores a hash of their passwords in
the cloud.
Federated identities This is a more advanced scenario, in which you can
federate Azure AD with your on-premises infrastructure. In an on-premises
Microsoft Windows Active Directory infrastructure, you will leverage the
Microsoft ADFS feature of Microsoft Windows Server to federate your
local Active Directory with Azure AD. In this scenario, end users will be
able to leverage a Single Sign-On (SSO) logon approach, which will
improve their overall experience. This is often the best option for large
businesses that want to have a centralized identity management system and
provide a consistent end user experience. From a management perspective,
the on-premises directory system is the unique repository of policies and
password rules, providing a more consistent management experience. The
users’ authentication process will involve services and workloads hosted
on-premises instead of using the classic Azure AD authentication form.
These three options can be mixed within the same Azure AD tenant according
to your needs. You can also start with one identity management model and
transition to another one almost transparently from your end users’ perspective.
In Figure 4-2, you can see a schema of the available identity management
options.
FIGURE 4-2 The identity management options available in Azure AD
FIGURE 4-3 The Office 365 Admin UI showing the Active Users management
panel
There, you will be able to add, update, or delete users and map them with
Office 365 licenses. Under the cover, that management UI will store users’
information in the related Azure AD tenant. To see that, you can click the Azure
AD link, which is available in the left command tree of the Admin Center in the
group of menu items called Admin Centers. When you click the Azure AD
menu, your web browser will be redirected to the Azure management portal.
There, you will be able to completely manage the Azure AD tenant related to
your Office 365 tenant.
Manual configuration
Manual configuration
To manually configure an app or service to rely on Azure AD, you have to
access the Azure AD management portal and click the Applications tab. From
there, you will be able to browse the already registered apps, or you can add
other custom apps or services.
Before adding a new app, let’s browse the directory to see what is available
out of the box, as shown in Figure 4-5.
FIGURE 4-6 The wizard for adding a new application to an Azure AD tenant
Select the first option (Add An Application My Organization Is Developing),
and you will have to provide a name for your app and choose between a Web
Application And/Or Web API or a Native Client Application. The former
defines an application that will have a web-based UI. The latter defines any
native platform application—for example, those for desktops, tablets, or
smartphones. For instance, name the app Programming.Office365.SampleApp,
select Web Application And/Or Web API, and make a step forward by clicking
the arrows in the bottom-right corner of the page. You will have to provide a
the arrows in the bottom-right corner of the page. You will have to provide a
Sign-On URL for the app, which is the URL that the end users will use to access
your app. You can also provide an App ID URI, which has to be a valid unique
URI.
Now, you are almost done. The wizard will register the app in Azure AD and
generate a unique Client ID (a GUID) for that app. As you can see in Figure 4-7,
just after the registration phase you will be able to access the configuration
panels of the app.
FIGURE 4-7 The Azure AD management UI just after registering a new app
The following is a brief explanation of the available tabs.
Dashboard Here, you can see a recap of the configuration parameters.
You can see all the available endpoints (Metadata, WS-Federation, SAML-
P, OAuth 2.0, Azure AD Graph) available to access the app through the
current Azure AD tenant. You can upload a custom logo for your app, and
you can manage the app manifest, which will be covered in more detail
later in this chapter. You can delete the app if you don’t want to use it
anymore.
Users Through this tab, you can assign or remove the right to access the
app to the users of the current Azure AD tenant.
Configure This tab includes the most useful options and configuration
parameters, including the Sign-On URL, the ClientID, the shared secret (if
any), the Reply URL, and so on. The Reply URL is the list of URLs to
which the application can redirect users after the authentication phase.
Moreover, you will find the permissions for the app. This tab will be
explored in more detail later in this section.
Owners This tab allows you to define the users within your directory who
will be owners of the current app. It is a functionality that is under preview
at the time of this writing.
Let’s focus on the Configure tab. The first information you can view or edit
within that tab is the name and the Sign-On URL of the app. Moreover, you can
configure whether the app will behave as a multitenant app, which is a concept
that will be explained in much more detail in the next section of this chapter. For
now, configure the sample app as multitenant; you will leverage this capability
later. Then, you have the Client ID, which is fundamental from an OAuth
perspective. It is readonly and can only be copied to the clipboard.
Just after the ClientID, you can see or configure the flag User Assignment
Required To Access App, which if true allows you to assign the application to a
specific set of users in Azure AD instead of targeting every user of the Office
365 tenant. Next, there is the section through which you can create security keys
for the app. You can create as many keys (also known as Client Secret) as you
want, and when you create a new key you can define if it will expire after one or
two years. The key value will be available after saving the configuration tab. Be
careful that the key value of a newly generated key will be visible only one time,
just after its creation. Store it in a safe place just after creation. If you lose the
key value, you will have to generate a new one. If you want to follow the flow of
this chapter, save the Client Secret because you will use it soon.
Following the keys, there is the Single Sign-On section where you can define
the App ID URI and the URLs to which Azure AD will consent to redirect the
end users after authentication. Here, you can configure as many URLs as you
like, and usually you configure the production URL of the app and any
development, testing, or staging URL.
The last section is the Permission To Other Applications, which is one of the
most important sections. From here, you can configure what services and
applications will be accessible by the current app and the related custom
applications will be accessible by the current app and the related custom
permissions for the current app against every other accessible app. To configure
a permission, click the Add Application button and search for the application
that you want to configure. By default, every app will be configured to access
the Azure AD services to sign in users and read their profile.
For example, to configure the just-created app to access Microsoft SharePoint
Online, you can click the Add Application button, select the Office 365
SharePoint Online application, and click the Complete button in the lower-right
corner of the dialog (see Figure 4-8). To access the Microsoft Graph API, click
the Add Application button and select the Microsoft Graph application. You can
also add third-party applications, not only those released by Microsoft.
Moreover, you can add your own applications if you want to consume a custom
set of API that you have developed and provisioned on Azure AD.
FIGURE 4-9 The delegated permissions available for consuming the Microsoft
Graph
As you can see, there are permissions for reading, writing, and managing
As you can see, there are permissions for reading, writing, and managing
resources like files, sites, mail, contacts, groups, and so on.
Multitenancy
Whenever you register an app in Azure AD, you are in a specific tenant. If you
want to provide your app to multiple customers who will have different Azure
AD tenants, you can configure your app as a multitenant app.
As you have seen in the previous section, there is a switch in the Configure
tab of any app that you can use to configure multitenancy support. The result of
this action will be the capability to support multiple tenants with a unique app
registration. From your customers’ perspective, they will have to sign up their
tenant to be able to leverage your app. For further details about how to define the
code for an app sign-up process, see Chapter 10, “Creating Office 365
applications.” To follow the flow of this chapter, you should enable
multitenancy for the test app you are registering in your tenant. This choice will
become useful in the upcoming sections. Whenever you register an app as
multitenant, you have to pay attention on the value of the AppID URI, which has
to be unique and should identify the domain name of the main tenant in which
you are registering the app.
The sign-up process will require your customers to trust your app and to
consent for it to have those rights—against their domains and/or users’ accounts
—that you declared in the permissions setting. Usually, the sign-up process has
to be handled by a user who has administrative rights on the target Azure AD
tenant. This way, the application will be available to all the users of the target
tenant. However, even a single user can sign up for personal use of a specific
application.
By signing up a tenant, your multitenant app will be configured automatically
in that target tenant and, just after that, the tenant administrators will be able to
manage, and even remove, your app from their tenant.
FIGURE 4-10 The wizard to select the authentication technique for a new
application in Microsoft Visual Studio 2015
The available options are:
No Authentication The name should be clear, but it means that your web
app will be accessible to anonymous users and there will not be any
authentication technique configured.
Individual User Accounts Allows you to manage identities that are
related only to the current application. Users can register and sign in with
the app, or they can sign in using their existing Facebook, Twitter, Google,
or Microsoft account or any other provider’s account.
Work And School Accounts Leverages Azure AD or ADFS through
OWIN for ASP.NET.
Windows Authentication Relies on an on-premises Active Directory
domain for users’ authentication via Windows integrated security.
To leverage the Azure AD service for authenticating your users, you will have
to choose the Work And School Accounts option. Moreover, you will have to
properly configure the tenant name and the application unique name for your
app. You can also choose whether to use Azure AD single tenant, which is
presented as Cloud – Single Organization; Azure AD multitenant, which is the
Cloud – Multiple Organizations option; or an on-premises ADFS server, which
is presented as On-Premises. In Figure 4-11, you can see an example of how to
configure such an authentication method.
FIGURE 4-11 The user interface available in Microsoft Visual Studio 2015 to
configure the authentication provider
In this example, select Cloud – Single Organization to use Azure AD with a
single-tenant application. After configuring the Work And School Accounts
option and creating the Visual Studio project, the project creation wizard will
automatically register your project in Azure AD as a new application, which will
have the right to access Azure AD for authentication purposes and read users’
information from Azure AD. The project creation wizard will also add some
configuration items to the .config file of your project. In the following code
excerpt, you can see those configuration items.
Click here to view code image
<appSettings>
<!-- Here there will be the custom configuration sample, as soon
as VS2015 will be RTM ...
-->
<add key="ida:ClientId" value="07974dc3-ae70-4f56-80eb-
1feac570e15d" />
<add key="ida:AADInstance" value="https://login.windows.net/" />
<add key="ida:ClientSecret"
value="***************************************" />
<add key="ida:Domain" value="tenant.onmicrosoft.com" />
<add key="ida:TenantId" value="6c94075a-da0a-4c6a-8411-
badf652e8b53" />
<add key="ida:PostLogoutRedirectUri"
value="https://localhost:44300/" />
</appSettings>
FIGURE 4-14 The OpenID authentication flow from the Office 365 and
Microsoft Graph perspective
First, the application—whether it is a SharePoint Add-in, an Office Add-in, an
Office 365 application, or something else—will contact an Azure AD
authorization endpoint to authenticate the end user and to request an
authorization code. The Azure AD authorization endpoint is the webpage that
authenticates the end user and asks to grant (consent) the application to access a
user’s resources on his own behalf, if any authorization is needed. After a
successful authentication and authorization/consent, the application will get back
an authorization code. At this time, the application will be able to send a token
request to the Azure AD token endpoint, providing a reference to the resource to
access and the just-assigned authorization code. The Azure AD token endpoint
will send back an ID token (it is a JWT token) together with an access token and
will send back an ID token (it is a JWT token) together with an access token and
a refresh token. If the application just needs to authenticate the end user, the
process is completed and the ID token will suffice. If the application also needs
to consume any further API/service/resource, like the Microsoft Graph API, then
it will be able to use the access token to invoke the target service or API.
grant_type=authorization_code&redirect_uri=[redirect_uri]&client_id=
[ClientID]&client_secret=[Cl
ientSecret]&code=[Authorization_Code]&resource=
[target_resource_identifier]
The grant_type argument instructs the target authorization server about the
kind of authorization grant that the client is presenting. In the current example,
you are providing an authorization code, hence the authorization_code value for
the grant_type argument. Moreover, the POST request will include information
about the redirect_uri for the app requesting the token and the client_id and
client_secret of the app (registered in the Configure tab of Azure AD) to
authenticate the app, not only the user. The authorization code will follow the
app credentials included in the code argument. Last, there is a fundamental piece
of information about the target resource/service that the app wants to consume:
the resource argument, which generally is a unique URI representing the target
resource/service. To consume the Microsoft Graph API, you should provide a
value of https://graph.microsoft.com/ to the token endpoint.
After sending the HTTP POST request, if the authorization code and the app
credentials are valid and the request to consume the target resource is authorized,
you will get back a JSON serialized response object,4 which will include the
following properties:
4 For further details about OAuth 2.0 token response, you can read the following document:
https://tools.ietf.org/html/rfc6749#section-4.1.4.
access_token The requested access token, formatted as a Base64 encoded
JWT token, that you can use to consume the target service. Store it in a
safe place.
expires_in The lifetime in seconds of the access token. Usually, the access
token released by Azure AD lasts for one hour.5
5 If you want to learn more about tokens validation, you can read the document “Azure AD Token
Lifetime,” which was written by Vittorio Bertocci and is available at the following URL:
http://www.cloudidentity.com/blog/2015/03/20/azure-ad-token-lifetime/.
expires_on The access token expire date formatted in Unix time format,
which is the number of seconds elapsed since 00:00:00 UTC of Thursday,
January 1, 1970 (1970-01-01T0:0:0Z), not including leap seconds.
id_token A Base64 encoded JWT token that defines the user’s identity.
not_before The access token validity start date, represented in Unix time
format.
refresh_token Optional; provides a token that can be used to refresh/renew
access tokens when they expire.
resource The source for which the access token has been released.
scope The permissions that the access token contains/allows.
token_type The type of the access token. It is always Bearer.
In the following code excerpt, you can see a sample response in JSON format,
with an intentionally omitted id_token, access_token, and refresh_token.
Click here to view code image
{
"expires_in":"3599",
"token_type":"Bearer",
"scope":"AllSites.Read Files.Read MyFiles.Read MyFiles.Write
UserProfile.Read",
"expires_on":"1435318185",
"not_before":"1435314285",
"resource":"https://graph.microsoft.com/",
"access_token":"eyJ0eXAiOiJ...",
"refresh_token":"AAABAAAAi...",
"id_token":"eyJ0eXAiOiJKV1..."
}
If you get the value of the id_token argument and decode it using any JWT
token decoder available on the Internet network, you will see that it includes a
bunch of claims about the current user. Here, you can see a code excerpt
presenting the decoded ID token in JSON format.
Click here to view code image
{
"aud": "aa76451e-c09f-40a2-8714-9d7ea3f617ee",
"iss": "https://sts.windows.net/f1aaea4d-8d94-4a74-9147-
28d8691a325a/",
"iat": 1435314285,
"nbf": 1435314285,
"exp": 1435318185,
"ver": "1.0",
"tid": "f1aaea4d-8d94-4a74-9147-28d8691a325a",
"oid": "214aacc1-44f6-4a3f-8e6e-ed3a10c726c9",
"upn": "[email protected]",
"sub": "h_P28X9nnA4_ph-HdMhqTQZV3S5wRN1Oilx4yZlRs5o",
"given_name": "Paolo",
"family_name": "Pialorsi",
"name": "Paolo Pialorsi",
"amr": [
"pwd"
],
"unique_name": "[email protected]"
}
{
"aud": "https://graph.microsoft.com/",
"iss": "https://sts.windows.net/f1aaea4d-8d94-4a74-9147-
28d8691a325a/",
"iat": 1435314285,
"nbf": 1435314285,
"exp": 1435318185,
"ver": "1.0",
"tid": "f1aaea4d-8d94-4a74-9147-28d8691a325a",
"oid": "214aacc1-44f6-4a3f-8e6e-ed3a10c726c9",
"upn": "[email protected]",
"puid": "10030000828C6116",
"sub": "dcxDje8sxZyw5KTJoZZxvKohnU9g_KjLutGU8eXXUS4",
"given_name": "Paolo",
"family_name": "Pialorsi",
"name": "Paolo Pialorsi",
"amr": [
"pwd"
],
"unique_name": "[email protected]",
"appid": "aa76451e-c09f-40a2-8714-9d7ea3f617ee",
"appidacr": "1",
"scp": "AllSites.Read Files.Read MyFiles.Read MyFiles.Write
UserProfile.Read",
"acr": "1"
}
In Table 4-3, you can see the claims usually defined inside an OAuth 2.0
access token.
TABLE 4-3 Claims usually presented inside JWT-formatted OAuth 2.0 access
token
If you provided a valid access token (one that is not expired) that grants the
If you provided a valid access token (one that is not expired) that grants the
right to retrieve the user’s profile information, you will get back a JSON
response with some useful information like:
Business telephone, office location, mobile phone, and so on
Main email address
Preferred language for UI
Registered devices
Contacts and calendars
User principal name
Photo
Moreover, you can invoke the OneDrive for Business endpoints to retrieve,
for example, the list of files in the user’s personal storage. To do this, you just
need to use the same request format as before (GET with HTTP Authorization
Bearer Header), targeting the URL https://graph.microsoft.com/v1.0/me/drive.
This time, the response will be a JSON object with pointers to the root folder of
the OneDrive for Business drive and to the items within the drive.
These examples illustrate the power of the new Microsoft Graph API. In the
past and without leveraging the Microsoft Graph API, you had to retrieve one
dedicated access token for each service endpoint. Moreover, you had to find the
target service URI through the Office 365 Discovery Service. Now, thanks to the
new unified model, you just need to retrieve one unique access token for the
unified endpoint, and you are ready to consume almost every service with a
unique security handshake.
In the previous HTTP body excerpt, the grant_type argument has a value of
refresh_token, which means that we are granting the consumer’s identity through
a refresh token, which is provided through the refresh_token argument, instead
of by using an authorization code like in the previous examples. If the refresh
token provided to the authorization server is expired or revoked, be ready to
handle an Invalid Grant exception like the following:
AADSTS70002: Error validating credentials. AADSTS70008: The
provided access grant is expired or revoked.
In that case, you will have to discard the refresh token and restart the
handshake process, requesting a new authorization code through the OAuth 2.0
authorization endpoint.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new
OpenIdConnectAuthenticationNotifications() {
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new
ClientCredential(
clientId, appKey);
string signedInUserID =
context.AuthenticationTicket.Identity.FindFirst(
ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new
AuthenticationContext(
Authority, new
ADALTokenCache(signedInUserID));
AuthenticationResult result =
authContext.AcquireTokenByAuthorizationCode
new
Uri(HttpContext.Current.Request.Url
.GetLeftPart(UriPartial.Path)),
credential, graphResourceId);
return Task.FromResult(0);
}
}
});
}
}
In the previous code excerpt, you will find in the accessTokenValue variable
the value of the access token that you can inject as an Authorization Bearer
header within any further REST request against the Microsoft Graph API
endpoints.
Just after that, you have to adapt the Startup.Auth.cs file to support a
multitenancy scenario. In Listing 4-2, you can see a new version of that file.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new
System.IdentityModel.Tokens
.TokenValidationParameters {
// instead of using the default validation
// (validating against a single issuer
value, as we do
// in line of business apps),
// we inject our own multitenant
validation logic
ValidateIssuer = false,
// If the app needs access to the entire
organization,
// then add the logic of validating the
Issuer here.
// IssuerValidator
},
Notifications = new
OpenIdConnectAuthenticationNotifications() {
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new
ClientCredential(
clientId, appKey);
string signedInUserID =
context.AuthenticationTicket.Identity.FindFirst(
ClaimTypes.NameIdentifier).Value;
string tenantID =
context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/tenant
.Value;
AuthenticationContext authContext = new
AuthenticationContext(
aadInstance + tenantID, new
ADALTokenCache(signedInUser
ID));
AuthenticationResult result =
authContext.AcquireTokenByAuthorizationCode(code,
new
Uri(HttpContext.Current.Request.Url
.GetLeftPart(UriPartial.Path)),
credential, graphResourceId);
return Task.FromResult(0);
}
}
});
}
}
The code excerpt of Listing 4-2 is similar to the one of Listing 4-1. The only
difference is that we need to retrieve the Tenant ID from the claims of the
currently logged-in user, if any. Then, the Tenant ID is used to create the
AuthenticationContext instance that will be used to retrieve tokens.
Moreover, in Listing 4-2 there is a custom configuration for the
TokenValidationParameters property that allows you to inject a custom token
issuer validation logic, if needed. In the code excerpt, there is a fake validation
logic, but in your real business-level solutions you should validate token issuers
carefully.
ADAL wrap-up
As you have just seen in practice, the ADAL library makes it easy to
authenticate and to acquire access tokens and refresh tokens. Moreover, the
ADAL library is able to renew access tokens automatically upon expiration by
using the cached refresh token values.
Almost the same capabilities are available in all the other supported
Almost the same capabilities are available in all the other supported
framework/technologies. Thus, feel free to leverage ADAL and all of its flavors
in your real solutions to avoid doing a manual handshake with Azure AD and the
OpenID Connect protocol.
Summary
In this chapter, you learned about the architecture of Azure AD and the key role
Azure AD plays in the architecture of Office 365.
Moreover, you saw how you can leverage OAuth 2.0 and OpenID Connect to
authenticate users and authorize access to the Microsoft Graph API on behalf of
those users. You also inspected how the OAuth 2.0 and OpenID Connect
protocols work under the cover during these authentication and authorization
processes. Last, you saw how to leverage the ADAL to interact with Azure AD
and to manage authentication identities and authorization tokens in Microsoft
.NET.
If you would like to walk through all the configuration and development steps
illustrated in this chapter, you can download the source code of the application
that will be illustrated and built in Chapter 10. That sample application is
available on GitHub at the following URL:
https://github.com/OfficeDev/PnP/tree/master/Samples/BusinessApps.O365ProjectsApp
In the next chapters, you will benefit from the information you learned in this
chapter while consuming the Microsoft Graph API.
Part III: Consuming Office 365
CHAPTER 5 Mail, calendar, and contact services
CHAPTER 6 Users and Groups services
CHAPTER 7 File services
This chapter explains how to leverage the Microsoft Graph API services related
to mail, calendar, and contacts. First, the chapter illustrates how to set up your
development environment to consume the Microsoft Graph API. Then, the
chapter covers the various flavors of available API in the fields of services
related to Microsoft Exchange Online.
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new
CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri
});
}
}
These lines of code define the Open Web Interface for .NET (OWIN) pipeline
and declare that the ASP.NET MVC web application will use cookie-based
authentication, followed by OpenID Connect authentication. The latter is also
configured with a specific Client ID, Authority, and post logout redirect URL.
By default, all these custom configuration parameters are loaded from the
web.config of the web application. In the code samples related to the current
book part, these values are retrieved through a static class that shares all the
general settings across the entire application. In Listing 5-2, you can see the
revised version of the Startup.Auth.cs file, which includes the OAuth access
token handling logic.
LISTING 5-2 The customized Startup.Auth.cs file in the sample ASP.NET project
Click here to view code image
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new
CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = MSGraphAPIDemoSettings.ClientId,
Authority = MSGraphAPIDemoSettings.Authority,
PostLogoutRedirectUri =
MSGraphAPIDemoSettings.PostLogoutRedirectUri,
Notifications = new
OpenIdConnectAuthenticationNotifications() {
SecurityTokenValidated = (context) => {
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new ClientCredential(
MSGraphAPIDemoSettings.ClientId,
MSGraphAPIDemoSettings.ClientSecret);
string signedInUserID =
context.AuthenticationTicket.Identity.FindFirst(
ClaimTypes.NameIdentifier).Value;
AuthenticationResult result =
authContext.AcquireTokenByAuthorizationCode(
code,
new
Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
MSGraphAPIDemoSettings.MicrosoftGraphResourceId);
return Task.FromResult(0);
},
AuthenticationFailed = (context) => {
context.OwinContext.Response.Redirect("HomeError");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
});
}
}
Note
For further details about ADAL, the ADAL cache, and the OpenID
Connect notifications, you can read the book Modern
Authentication with Azure Active Directory for Web Applications,
written by Vittorio Bertocci and published by Microsoft Press in
2015 (ISBN: 978-0-7356-9694-5).
Aside from the initialization code, which is executed whenever the user’s
authentication flow starts, to access the Microsoft Graph API you will have to
provide a valid OAuth access token. In Listing 5-3, you can see a helper
function, which is part of the sample project, to retrieve an access token either
from the ADAL cache or by refreshing a new one through the refresh token
stored in the ADAL cache. If neither the access token nor the refresh token is
valid, the helper method will handle a full refresh of the authentication context
by invoking the Challenge method of the current OWIN Authentication context.
LISTING 5-3A helper method to get an OAuth access token for accessing the
Microsoft Graph API
Click here to view code image
/// <summary>
/// This helper method returns an OAuth Access Token for the
current user
/// </summary>
/// <param name="resourceId">The resourceId for which we are
requesting the token</param>
/// <returns>The OAuth Access Token value</returns>
public static String GetAccessTokenForCurrentUser(String
resourceId = null) {
try {
ClientCredential credential = new ClientCredential(
MSGraphAPIDemoSettings.ClientId,
MSGraphAPIDemoSettings.ClientSecret);
string signedInUserID =
System.Security.Claims.ClaimsPrincipal.Current
.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationResult result =
authContext.AcquireTokenSilent(
MSGraphAPIDemoSettings.MicrosoftGraphResourceId,
credential,
UserIdentifier.AnyUser);
accessToken = result.AccessToken;
}
catch (AdalException ex) {
if (ex.ErrorCode == "failed_to_acquire_token_silently") {
// Refresh the access token from scratch
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties {
RedirectUri =
HttpContext.Current.Request.Url.ToString(),
},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else {
// Rethrow the exception
throw ex;
}
}
return (accessToken);
}
Notice the AcquireTokenSilent method invocation, which will get the access
token from the ADAL cache or refresh it based on the cached refresh token. The
ADAL cache, as described previously, is built based on the current user ID. In
case of an exception with an error code with a value of
failed_to_acquire_token_silently, the helper will invoke the Challenge method
to start a new authentication flow, as discussed previously.
In Listing 5-4, you can see a code excerpt that illustrates how to use the
method GetAccessTokenForCurrentUser described in Listing 5-3.
/// <summary>
/// This helper method makes an HTTP request and eventually
returns a result
/// </summary>
/// <param name="httpMethod">The HTTP method for the
request</param>
/// <param name="requestUrl">The URL of the request</param>
/// <param name="accept">The content type of the accepted
response</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content type of the
request</param>
/// <param name="resultPredicate">The predicate to retrieve the
result, if any</param>
/// <typeparam name="TResult">The type of the result, if
any</typeparam>
/// <returns>The value of the result, if any</returns>
private static TResult MakeHttpRequest<TResult>(
String httpMethod,
String requestUrl,
String accept = null,
Object content = null,
String contentType = null,
Func<HttpResponseMessage, TResult> resultPredicate = null) {
if (!String.IsNullOrEmpty(accessToken)) {
// If we have the token, then handle the HTTP request
HttpClient httpClient = new HttpClient();
if (response.IsSuccessStatusCode) {
// If the response is Success and there is a
// predicate to retrieve the result, invoke it
if (resultPredicate != null) {
result = resultPredicate(response);
}
}
else {
throw new ApplicationException(
String.Format("Exception while invoking endpoint
{0}.", graphRequestUri),
new HttpException(
(Int32)response.StatusCode,
response.Content.ReadAsStringAsync().Result));
}
}
return (result);
}
/// <summary>
/// This helper method makes an HTTP GET request and returns the
result as a String
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <returns>The String value of the result</returns>
public static String MakeGetRequestForString(String
graphRequestUri) {
return (MakeHttpRequest<String>("GET",
graphRequestUri,
resultPredicate: r =>
r.Content.ReadAsStringAsync().Result));
}
/// <summary>
/// This helper method makes an HTTP GET request and returns the
result as a Stream
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <param name="accept">The accept header for the
response</param>
/// <returns>The Stream of the result</returns>
public static System.IO.Stream MakeGetRequestForStream(String
graphRequestUri,
String accept) {
return (MakeHttpRequest<System.IO.Stream>("GET",
graphRequestUri,
resultPredicate: r =>
r.Content.ReadAsStreamAsync().Result));
}
/// <summary>
/// This helper method makes an HTTP POST request without a
response
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content/type of the
request</param>
public static void MakePostRequest(String graphRequestUri,
Object content = null,
String contentType = null) {
MakeHttpRequest<String>("POST",
graphRequestUri,
content: content,
contentType: contentType);
}
/// <summary>
/// This helper method makes an HTTP POST request and returns the
result as a String
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content/type of the
request</param>
/// <returns>The String value of the result</returns>
public static String MakePostRequestForString(String
graphRequestUri,
Object content = null,
String contentType = null) {
return (MakeHttpRequest<String>("POST",
graphRequestUri,
content: content,
contentType: contentType,
resultPredicate: r =>
r.Content.ReadAsStringAsync().Result));
}
/// <summary>
/// This helper method makes an HTTP PATCH request and returns the
result as a String
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content/type of the
request</param>
/// <returns>The String value of the result</returns>
public static String MakePatchRequestForString(String
graphRequestUri,
Object content = null,
String contentType = null) {
return (MakeHttpRequest<String>("PATCH",
graphRequestUri,
content: content,
contentType: contentType,
resultPredicate: r =>
r.Content.ReadAsStringAsync().Result));
}
/// <summary>
/// This helper method makes an HTTP DELETE request
/// </summary>
/// <param name="graphRequestUri">The URL of the request</param>
/// <returns>The String value of the result</returns>
public static void MakeDeleteRequest(String graphRequestUri) {
MakeHttpRequest<String>("DELETE", graphRequestUri);
}
/// <summary>
/// This helper method makes an HTTP PUT request without a
response
/// </summary>
/// <param name="requestUrl">The URL of the request</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content/type of the
request</param>
public static void MakePutRequest(String requestUrl,
Object content = null, String contentType = null) {
MakeHttpRequest<String>("PUT",
requestUrl,
content: content,
contentType: contentType);
}
/// <summary>
/// This helper method makes an HTTP PUT request and returns the
result as a String
/// </summary>
/// <param name="requestUrl">The URL of the request</param>
/// <param name="content">The content of the request</param>
/// <param name="contentType">The content/type of the
request</param>
/// <returns>The String value of the result</returns>
public static String MakePutRequestForString(String requestUrl,
Object content = null, String contentType = null) {
return(MakeHttpRequest<String>("PUT",
requestUrl,
content: content,
contentType: contentType,
resultPredicate: r =>
r.Content.ReadAsStringAsync().Result));
}
You are now ready to consume the Microsoft Graph API within your code by
leveraging these helper methods and the setup environment.
Mail services
As you saw in Chapter 3, “Microsoft Graph API reference,” to consume the mail
services you will need to access the proper REST endpoint and process the
related JSON responses. However, from a .NET perspective, you will have to
deserialize every JSON response into something that can be handled by your
code. To achieve this, you can, for example, use the Newtonsoft.Json package,
which is available through NuGet.
/// <summary>
/// This method retrieves the email folders of the current user
/// </summary>
/// <param name="startIndex">The startIndex (0 based) of the
folders to retrieve</param>
/// <returns>A page of up to 10 email folders</returns>
public static List<MailFolder> ListFolders(Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/mailFolders?$skip={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
startIndex));
LISTING 5-7 Code excerpt that defines the MailFolderList and the MailFolder
model types
Click here to view code image
/// <summary>
/// Defines a list of email message folders
/// </summary>
public class MailFolderList {
/// <summary>
/// The list of email message folders
/// </summary>
[JsonProperty("value")]
public List<MailFolder> Folders { get; set; }
}
/// <summary>
/// Defines an email Folder
/// </summary>
public class MailFolder : BaseModel {
/// <summary>
/// The display name of the email folder
/// </summary>
[JsonProperty("displayName")]
public String Name { get; set; }
/// <summary>
/// Total number of items
/// </summary>
public Int32 TotalItemCount { get; set; }
/// <summary>
/// Number of unread items
/// </summary>
public Int32 UnreadItemCount { get; set; }
}
Notice that the MailFolder type provides just a subset of the properties
defined in a mail folder, but the JsonConvert engine will handle that, including
any property remapping, by leveraging the JsonProperty attribute. Shaping the
MailFolder type and any other domain model type is a task you must perform
based on your real business requirements if you want to consume the Microsoft
Graph API manually and at low level, with pure HTTP, REST, and JSON.
Once you have the list of folders for the current user, you can access the email
messages of a specific folder by making a REST request for a URL like the
messages of a specific folder by making a REST request for a URL like the
following:
https://graph.microsoft.com/v1.0/me/mailFolders/<FolderID>/messages
In Listing 5-8, you can see a code excerpt of a method that retrieves such a list
of email messages.
LISTING 5-8 Code excerpt of a method that retrieves the email messages of a mail
folder
Click here to view code image
/// <summary>
/// This method retrieves the email messages from a folder in the
current user's mailbox
/// </summary>
/// <param name="folderId">The ID of the target folder,
optional</param>
/// <param name="startIndex">The startIndex (0 based) of the
messages to retrieve</param>
/// <param name="includeAttachments">Defines whether to include
attachments</param>
/// <returns>A page of up to 10 email messages in the
folder</returns>
public static List<MailMessage> ListMessages(String folderId =
null, Int32 startIndex = 0,
Boolean includeAttachments = false) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(targetUrl);
var messages = JsonConvert.DeserializeObject<MailMessageList>
(jsonResponse);
if (includeAttachments)
foreach (var message in messages.Messages.Where(m =>
m.HasAttachments)) {
message.LoadAttachments();
}
}
return (messages.Messages);
}
LISTING 5-9 Code excerpt that defines the MailMessageList and the MailMessage
types
Click here to view code image
/// <summary>
/// Defines a list of email messages
/// </summary>
public class MailMessageList {
/// <summary>
/// The list of messages
/// </summary>
[JsonProperty("value")]
public List<MailMessage> Messages { get; set; }
}
/// <summary>
/// Defines an email message
/// </summary>
public class MailMessage : BaseModel {
public MailMessage() {
this.Attachments = new List<MailAttachment>();
}
/// <summary>
/// The importance of the email message
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public ItemImportance Importance { get; set; }
/// <summary>
/// The sender email address
/// </summary>
[JsonProperty("from")
public MailMessageRecipient From { get; set; }
/// <summary>
/// The list of email address TO recipients
/// </summary>
[JsonProperty("toRecipients")]
public List<MailMessageRecipient> To { get; set; }
/// <summary>
/// The list of email address CC recipients
/// </summary>
[JsonProperty("ccRecipients")]
public List<MailMessageRecipient> CC { get; set; }
/// <summary>
/// The list of email address BCC recipients
/// </summary>
[JsonProperty("bccRecipients")]
public List<MailMessageRecipient> BCC { get; set; }
/// <summary>
/// The subject of the email message
/// </summary>
public String Subject { get; set; }
/// <summary>
/// The body of the email message
/// </summary>
public ItemBody Body { get; set; }
/// <summary>
/// The UTC sent date and time of the email message
/// </summary>
public Nullable<DateTime> SentDateTime { get; set; }
/// <summary>
/// The UTC received date and time of the email message
/// </summary>
public Nullable<DateTime> ReceivedDateTime { get; set; }
/// <summary>
/// Defines whether the email message is read on unread
/// </summary>
public Boolean IsRead { get; set; }
/// <summary>
/// Defines whether the email message is a draft
/// </summary>
public Boolean IsDraft { get; set; }
/// <summary>
/// Defines whether the email has attachments
/// </summary>
public Boolean HasAttachments { get; set; }
/// <summary>
/// The list of email message attachments, if any
/// </summary>
public List<MailAttachment> Attachments { get; private set; }
}
/// <summary>
/// Defines the importance of an email message
/// </summary>
public enum ItemImportance {
/// <summary>
/// Low importance
/// </summary>
Low,
/// <summary>
/// Normal importance, default value
/// </summary>
Normal,
/// <summary>
/// High importance
/// </summary>
High,
}
/// <summary>
/// Defines a recipient of an email message/meeting
/// </summary>
public class UserInfoContainer {
/// <summary>
/// The email address of the recipient
/// </summary>
[JsonProperty("emailAddress")]
public EmailAddress Recipient { get; set; }
}
/// <summary>
/// Defines a user info
/// </summary>
public class UserInfo {
/// <summary>
/// The email address
/// </summary>
public String Address { get; set; }
/// <summary>
/// The description of the email address
/// </summary>
public String Name { get; set; }
}
/// <summary>
/// Extension method to load the attachments of an email message
/// </summary>
/// <param name="message">The target email message</param>
public static void LoadAttachments(this MailMessage message) {
if (message.HasAttachments) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/messages/{1}/attachments",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
message.Id));
var attachments =
JsonConvert.DeserializeObject<MailAttachmentList>(jsonResponse);
message.Attachments.AddRange(attachments.Attachments);
The business logic makes an HTTP request for the URL of the Microsoft
Graph API that retrieves the attachments of the current message. Then, it
deserializes the JSON response into a list of .NET complex types and loads each
instance of the MailAttachment type into the collection of attachments of the
target MailMessage instance. The binary content of every attachment is handled
automatically by the Newtonsoft.Json library, and you will find it in the Byte
array property with name Content of the custom type MailAttachment.
Unfortunately, if the email message for which you are downloading the
attachments has one or more big files attached, executing the request illustrated
in Listing 5-10 could become expensive and slow, depending on the available
bandwidth. It is better to leverage the OData querying capabilities and query for
the list of attachments, including their size and excluding their binary content.
Later, you can download just the necessary content of those attachments by
accessing their URL endpoint directly. In Listing 5-11, you can see a revised
sample according to these new requirements.
/// <summary>
/// Extension method to load the attachments of an email message
in a smart way
/// </summary>
/// <param name="message">The target email message</param>
public static void LoadAttachments(this MailMessage message) {
if (message.HasAttachments) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/messages/{1}/attachments?
$select=contentType,id,name,size",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
message.Id));
var attachments =
JsonConvert.DeserializeObject<MailAttachmentList>(jsonResponse);
message.Attachments.AddRange(attachments.Attachments);
/// <summary>
/// Extension method to load the content of a specific attachment
/// </summary>
/// <param name="message">The target email message</param>
public static void EnsureContent(this MailAttachment attachment) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/messages/{1}/attachments/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
attachment.ParentMessageId,
attachment.Id));
MailHelper.SendMessage(new Models.MailMessageToSend {
Message = new Models.MailMessage {
Subject = "Test message",
Body = new Models.ItemBody {
Content = "<html><body><h1>Hello from ASP.NET MVC
calling " +
"the Microsoft Graph API!</h1></body></html>",
Type = Models.BodyType.Html,
},
To = new List<Models.UserInfoContainer>(new
Models.UserInfoContainer[] {
new Models.UserInfoContainer {
Recipient = new Models.UserInfo {
Name = "Paolo Pialorsi",
Address = "[email protected]",
}
}
}),
},
SaveToSentItems = true,
});
/// <summary>
/// This method sends an email message
/// </summary>
/// <param name="message"></param>
public static void SendMessage(MailMessageToSend message) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}me/microsoft.graph.sendMail",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri),
message, "application/json");
}
Note
/// <summary>
/// This method sends a reply to an email message
/// </summary>
/// <param name="messageId">The ID of the message to reply
to</param>
/// <param name="comment">Any comment to include in the reply,
optional</param>
public static void Reply(String messageId, String comment = null)
{
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}me/messages/{1}/reply",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, messageId),
content: !String.IsNullOrEmpty(comment) ? new { Comment =
comment } : null,
contentType: "application/json");
}
/// <summary>
/// This method sends a reply all to an email message
/// </summary>
/// <param name="messageId">The ID of the message to reply all
to</param>
/// <param name="comment">Any comment to include in the reply all,
optional</param>
public static void ReplyAll(String messageId, String comment =
null) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}me/messages/{1}/replyAll",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, messageId),
content: !String.IsNullOrEmpty(comment) ? new { Comment =
comment } : null,
contentType: "application/json");
}
/// <summary>
/// This method forwards an email message to someone else
/// </summary>
/// <param name="messageId">The ID of the message to
forward</param>
/// <param name="recipients">The recipients of the forward</param>
/// <param name="comment">Any comment to include in the reply all,
optional</param>
public static void Forward(String messageId,
List<UserInfoContainer> recipients, String comment = null) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}me/messages/{1}/forward",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri, messageId),
content: new {
Comment = !String.IsNullOrEmpty(comment) ? comment :
null,
ToRecipients = recipients,
}, contentType: "application/json");
}
In the sample, the URL of the actions and the content of the reply/reply all
messages that will be sent is highlighted in bold. Notice also the use of an
anonymous type to hold the value of the response content. The same applies to
the method for forwarding an email message, in which the HTTP request content
is built as an anonymous type made of the Comment, which represents the body
on top of the forward, and the ToRecipients, which are those to whom the
message is forwarded. Using these methods to handle email responses is
straightforward.
Calendar services
When developing custom Office 365 applications, it is often useful to interact
with users’ calendars and events to provide functionalities around the basic
calendar features. As with users’ mailboxes, here you will learn how to
enumerate calendars and events and how to send, accept, and reject meeting
requests by using C# within the ASP.NET MVC sample application.
/// <summary>
/// This method retrieves the calendars of the current user
/// </summary>
/// <param name="startIndex">The startIndex (0 based) of the
folders to retrieve</param>
/// <returns>A page of up to 10 calendars</returns>
public static List<Calendar> ListCalendars(Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/calendars?$skip={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
startIndex));
LISTING 5-15 Code excerpt to show the definition of the CalendarList type
Click here to view code image
/// <summary>
/// Defines a list of calendars
/// </summary>
public class CalendarList {
/// <summary>
/// The list of calendars
/// </summary>
[JsonProperty("value")]
public List<Calendar> Calendars { get; set; }
}
/// <summary>
/// Defines a user's calendar
/// </summary>
public class Calendar : BaseModel {
/// <summary>
/// The color of the calendar
/// </summary>
public String Color { get; set; }
/// <summary>
/// The name of the calendar
/// </summary>
public String Name { get; set; }
}
Once you have the list of calendars, you can retrieve a single calendar object
by ID by using a syntax like the one shown in Listing 5-16, where the
GetCalendar helper method makes an HTTP GET request for a specific calendar
item.
The result of the HTTP request illustrated in Listing 5-16 is a JSON string that
can be deserialized into an object of custom type Calendar. To browse the
events in the calendar, you will need to have a collection of objects of type
Event, which can be retrieved by invoking the events navigation property of a
calendar. The JSON response will be deserialized into the collection. In Listing
5-17, you can see a helper method to retrieve a calendar’s events with paging.
LISTING 5-17 Code excerpt to show the definition of the ListEvents helper method
Click here to view code image
/// <summary>
/// This method retrieves the events of the current user's
calendar
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="startIndex">The startIndex (0 based) of the items
to retrieve</param>
/// <returns>A page of up to 10 events</returns>
public static List<Event> ListEvents(String calendarId, Int32
startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/calendars/{1}/events?$skip={2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId,
startIndex));
Every event is made of a lot of properties. For the sake of simplicity, the
current sample handles just the main properties, which are those defined in the
custom type Event illustrated in Listing 5-18.
LISTING 5-18 Code excerpt to show the definition of the Event and EventList types
Click here to view code image
/// <summary>
/// Defines a list of calendar's events
/// </summary>
public class EventList {
/// <summary>
/// The list of calendar's events
/// </summary>
[JsonProperty("value")]
public List<Event> Events { get; set; }
}
/// <summary>
/// Defines a user's calendar
/// </summary>
public class Event : BaseModel {
/// <summary>
/// The list of email address for the event's attendees
/// </summary>
public List<UserInfoContainer> Attendees { get; set; }
/// <summary>
/// The body of the email message for the event
/// </summary>
public ItemBody Body { get; set; }
/// <summary
/// The subject of the event
/// </summary>
public String Subject { get; set; }
/// <summary>
/// The Type of the event
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public EventType Type { get; set; }
/// <summary>
/// Date and time of creation
/// </summary>
public Nullable<DateTime> CreatedDateTime { get; set; }
/// <summary>
/// Defines whether the event is an all day event
/// </summary>
public Boolean IsAllDay { get; set; }
/// <summary>
/// Defines whether the current user is the organizer of the
event
/// </summary>
public Boolean IsOrganizer { get; set; }
/// <summary>
/// The importance of the email message for the event
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public ItemImportance Importance { get; set; }
/// <summary>
/// The location of the event
/// </summary>
public EventLocation Location { get; set; }
/// <summary>
/// The event organizer
/// </summary>
public UserInfo Organizer { get; set; }
/// <summary>
/// The Original Zone of the end time
/// </summary>
public String OriginalStartTimeZone { get; set; }
/// <summary>
/// The Original Zone of the end time
/// </summary>
public String OriginalEndTimeZone { get; set; }
/// <summary>
/// The start date and time of the event
/// </summary>
public TimeInfo Start { get; set; }
/// <summary>
/// The end date and time of the event
/// </summary>
public TimeInfo End { get; set; }
/// <summary>
/// The status (show as) of the event
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public EventStatus ShowAs { get; set; }
/// <summary>
/// The ID of the Master Event of the Series of events
/// </summary>
public String SeriesMasterId { get; set; }
/// <summary>
/// The Recurrence pattern for the Series of events
/// </summary>
public EventRecurrence Recurrence { get; set; }
}
/// <summary>
/// Defines the type of event
/// </summary>
public enum EventType {
/// <summary>
/// Single instance event
/// </summary>
SingleInstance,
/// <summary>
/// Master of a Series of events
/// </summary>
SeriesMaster,
/// <summary>
/// Recurring event
/// </summary>
Occurrence,
/// <summary>
/// Exception of a Recurring event
/// </summary>
Exception,
}
/// <summary>
/// Defines the status (show as) of an event
/// </summary>
public enum EventStatus {
/// <summary>
/// Free
/// </summary>
Free,
/// <summary>
/// Tentative
/// </summary>
Tentative,
/// <summary>
/// Busy
/// </summary>
Busy,
/// <summary>
/// Out of Office
/// </summary>
Oof,
/// <summary>
/// Working elsewhere
/// </summary>
WorkingElsewhere,
/// <summary>
/// Unknown
/// </summary>
Unknown,
}
An event is similar to an email message; the custom Event type shares many
properties, which are highlighted in bold, with the MailMessage type. If you
like, you could do some refactoring and share an abstract base class between the
MailMessage and Event types. Moreover, every Event type includes information
specific to the event like the location, the start and end date, the event type and
status, and so on. Most of these properties are defined through enumerations, and
maybe you are wondering how to find the possible values for these enum types.
The metadata document of the Microsoft Graph API provides you with all the
needed information. If you browse to the metadata URL
(https://graph.microsoft.com/v1.0/$metadata), at the beginning of the XML
metadata document you will find the definition of all the enumerations and their
possible values.
LISTING 5-19 Code excerpt that shows how to retrieve the events within a specific
date range
Click here to view code image
/// <summary>
/// Retrieves the events of the current user's calendar within a
specific date range
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="startDate">The start date of the range</param>
/// <param name="endDate">The end date of the range</param>
/// <param name="startIndex">The startIndex (0 based) of the items
to retrieve</param>
/// <returns>A page of up to 10 events</returns>
public static List<Event> ListEvents(String calendarId, DateTime
startDate,
DateTime endDate, Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/calendars/{1}/calendarView?" +
"startDateTime={2:o}&endDateTime={3:o}&$skip={4}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId,
startDate.ToUniversalTime(),
endDate.ToUniversalTime(),
startIndex));
The URL of the request targets the calendarView navigation property of the
target calendar object and is built providing the arguments startDateTime and
endDateTime within the query string. Notice that the values of the two date and
time arguments are converted into UTC time zone and formatted according to
the OData protocol format requirements. You will still get back objects of type
Event, divided in pages of no more than 10 items. Thus, if you want to download
all the events or multiple pages of events, you will still have to make multiple
requests, leveraging the $skip query string argument. Keep in mind that you are
not obliged to download the entire set of properties for every event. You can use
the $select query string argument introduced earlier to project a subset of the
properties to download only what you need.
LISTING 5-20 Code excerpt showing the definition of the EventRecurrence type
Click here to view code image
/// <summary>
/// Defines the Recurrence for a series of events
/// </summary>
public class EventRecurrence {
/// <summary>
/// The Recurrence Pattern
/// </summary>
public EventRecurrencePattern Pattern { get; set; }
/// <summary>
/// The Recurrence Range
/// </summary>
public EventRecurrenceRange Range { get; set; }
}
/// <summary>
/// Defines the Recurrence Pattern for a series of events
/// </summary>
public class EventRecurrencePattern {
/// <summary>
/// The day of the month for the recurrence
/// </summary>
public Int32 DayOfMonth { get; set; }
/// <summary>
/// The days of the week for the recurrence
/// </summary>
[JsonProperty(ItemConverterType =
typeof(StringEnumConverter))]
public DayOfWeek[] DaysOfWeek { get; set; }
/// <summary>
/// The first day of the week
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public DayOfWeek FirstDayOfWeek { get; set; }
/// <summary>
/// The week of the month
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public WeekIndex Index { get; set; }
/// <summary>
/// The interval for repeating occurrences
/// </summary>
public Int32 Interval { get; set; }
/// <summary>
/// The month for the recurrence
/// </summary>
public Int32 Month { get; set; }
/// <summary>
/// The type of recurrence
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public RecurrenceType Type { get; set; }
}
/// <summary>
/// Defines the Recurrence Range for a series of events
/// </summary>
public class EventRecurrenceRange
{
/// <summary>
/// The Start Date of the recurrence
/// </summary>
[JsonConverter(typeof(Converters.DateOnlyConverter))]
public Nullable<DateTime> StartDate { get; set; }
/// <summary>
/// The End Date of the recurrence
/// </summary>
[JsonConverter(typeof(Converters.DateOnlyConverter))]
public Nullable<DateTime> EndDate { get; set; }
/// <summary>
/// The number of occurrences
/// </summary>
public Int32 NumberOfOccurrences { get; set; }
/// <summary>
/// The reference TimeZone for the recurrence
/// </summary>
public String RecurrenceTimeZone { get; set; }
/// <summary>
/// The type of recurrence
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public RecurrenceRangeType Type { get; set; }
}
LISTING 5-21 Code excerpt to retrieve the events of a series of events from a target
calendar
Click here to view code image
/// <summary>
/// This method retrieves the events of a series within a specific
date range
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="masterSeriesId">The ID of the master event of the
series</param>
/// <param name="startDate">The start date of the range</param>
/// <param name="endDate">The end date of the range</param>
/// <param name="startIndex">The startIndex (0 based) of the items
to retrieve</param>
/// <returns>A page of up to 10 events</returns>
public static List<Event> ListSeriesInstances(String calendarId,
String masterSeriesId,
DateTime startDate,
DateTime endDate,
Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/calendars/{1}/events/{2}/instances?"
+
"startDateTime={3:o}&endDateTime={4:o}&$skip={5}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId,
masterSeriesId,
startDate.ToUniversalTime(),
endDate.ToUniversalTime(),
startIndex));
The helper method builds the URL of the request, setting the ID of the
calendar and of the master event of the series. Moreover, it configures the
startDateTime, endDateTime, and $skip query string arguments.
LISTING 5-22 Code excerpt to create a new single instance event in a target
calendar
Click here to view code image
As you can see, the excerpt creates an instance of an object of type Event and
submits it to the CreateEvent helper method of the CalendarHelper class. Notice
that the event is a meeting request because the Attendees property is configured
to contain a couple of people. The Microsoft Graph API will take care of
sending all the invitations for you. The result of this HTTP method invocation
will be the just-created instance of the event, so you don’t need to make another
query to retrieve it.
If you want to create an event series, you can submit an Event instance
configured accordingly, providing a recurrence pattern and a range for the
occurrences. In Listing 5-23, you can see an example to create an event series
that recurs until the end of the current month, every Monday from 9.00 A.M. to
10.00 A.M.
It is clear that the only differences between a single instance event and a series
of events are the configuration of the Recurrence property and the value of the
Type property, which has a value of SeriesMaster. Aside from that, the just-
created event is like any other object of type Event. Under the cover, the
Microsoft Graph API will create for you all the series of events in the target
calendar and will send the invitations if the recurring event is a meeting request.
Now, suppose you want to change one event in the series or another event that
you have in a calendar. To accomplish this, you will need to get the event
instance from the calendar, apply any changes, and resubmit the event to the
calendar by using an HTTP PATCH method, as you learned in Chapter 3. In
Listing 5-24, you can see a sample helper method to retrieve a single event
instance from a calendar.
LISTING 5-24 Code excerpt of a helper method to retrieve a single event from a
target calendar
Click here to view code image
/// <summary>
/// This method retrieves an event from a calendar
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="eventId">The ID of the event</param>
/// <returns>The retrieved event</returns>
public static Event GetEvent(String calendarId, String eventId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/calendars/{1}/events/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId, eventId));
In Listing 5-25 you can see the definition of a helper method to update an
event, leveraging the HTTP PATCH method.
LISTING 5-25 Code excerpt of a helper method to update a single event in a target
calendar
Click here to view code image
/// <summary>
/// This method updates an event in a calendar
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="eventId">The event to update</param>
/// <returns>The updated event</returns>
public static Event UpdateEvent(String calendarId, Event
eventToUpdate) {
String jsonResponse =
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}me/calendars/{1}/events/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId, eventToUpdate.Id),
eventToUpdate, "application/json");
Notice that the Microsoft Graph API returns the updated event as the result of
Notice that the Microsoft Graph API returns the updated event as the result of
the HTTP PATCH method. This behavior is useful if you want to refresh the
event view in your business logic because you don’t have to make another
request to refresh your local copy of the updated event.
If you want to delete an event from a calendar, you can make an HTTP
DELETE request against the URL of the event to remove. In Listing 5-26, you
can see a code excerpt of the helper method to do that.
LISTING 5-26 Code excerpt of a helper method to delete a single event from a
target calendar
Click here to view code image
/// <summary>
/// This method deletes an event from a calendar
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="eventId">The ID of the event to delete</param>
public static void DeleteEvent(String calendarId, String eventId)
{
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}me/calendars/{1}/events/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId, eventId));
}
Again, the powerful Microsoft Graph API will send for you a cancellation
email if the event deleted is a meeting request that involves someone else.
/// <summary>
/// This method provides a feedback for a received meeting request
/// </summary>
/// <param name="calendarId">The ID of the calendar</param>
/// <param name="eventId">The ID of the meeting request</param>
/// <param name="feedback">The feedback for the meeting
request</param
/// <param name="comment">Any comment to include in the feedback,
optional</param>
public static void SendFeedbackForMeetingRequest(String
calendarId,
String eventId,
MeetingRequestFeedback feedback,
String comment = null) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}me/calendars/{1}/events/{2}/{3}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
calendarId, eventId, feedback),
content: !String.IsNullOrEmpty(comment) ? new {
Comment = comment } : null,
contentType: "application/json");
}
Contact services
The last group of services provided by the Microsoft Graph API against
Microsoft Exchange Online are those related to handling contacts. In this
section, you will learn how to retrieve, add, update, or delete users’ personal
contacts.
Reading contacts
Like mailboxes and calendars, you can access a user’s default folder of contacts
by making a request for the contacts navigation property of a user, whether it is
the current user (that is, me) or any other user for whom you have the permission
to read contacts. For example, in Listing 5-28 there is a helper method to retrieve
the contacts of the current user.
LISTING 5-28 Code excerpt of a helper method to get the contacts of the current
user
Click here to view code image
/// <summary>
/// This method retrieves the contacts of the current user
/// </summary>
/// <param name="startIndex">The startIndex (0 based) of the
contacts to retrieve</param>
/// <returns>A page of up to 10 contacts</returns>
public static List<Contact> ListContacts(Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/contacts?$skip={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
startIndex));
LISTING 5-29 Code excerpt to define the Contact and ContactList types
Click here to view code image
/// <summary>
/// Defines a list of contacts
/// </summary>
public class ContactList {
/// <summary>
/// The list of contacts
/// </summary>
[JsonProperty("value")]
public List<Contact> Contacts { get; set; }
}
/// <summary>
/// Defines a user's contact
/// </summary>
public class Contact : BaseModel {
/// <summary>
/// The business address of the contact
/// </summary>
public PhysicalAddress BusinessAddress { get; set; }
/// <summary>
/// The business phones of the contact
/// </summary>
public List<String> BusinessPhones { get; set; }
/// <summary>
/// The business home page of the contact
/// </summary>
public String BusinessHomePage { get; set; }
/// <summary>
/// The company name of the contact
/// </summary>
public String CompanyName { get; set; }
/// <summary>
/// The department name of the contact
/// </summary>
public String Department { get; set; }
/// <summary
/// The display name of the contact
/// </summary>
public String DisplayName { get; set; }
/// <summary>
/// The list of email addresses of the contact
/// </summary>
public List<UserInfo> EmailAddresses { get; set; }
/// <summary>
/// The "File As" of the contact
/// </summary>
public String FileAs { get; set; }
/// <summary>
/// The home address of the contact
/// </summary>
public PhysicalAddress HomeAddress { get; set; }
/// <summary>
/// The home phones of the contact
/// </summary>
public List<String> HomePhones { get; set; }
/// <summary>
/// The office location of the contact
/// </summary>
public String OfficeLocation { get; set; }
/// <summary>
/// The other address of the contact
/// </summary>
public PhysicalAddress OtherAddress { get; set; }
/// <summary>
/// Personal notes about the contact
/// </summary>
public String PersonalNotes { get; set; }
/// <summary>
/// The first name of the contac
/// </summary>
public String GivenName { get; set; }
/// <summary>
/// The family name of the contac
/// </summary>
public String Surname { get; set; }
/// <summary>
/// The title of the contact
/// </summary>
public String Title { get; set; }
}
/// <summary>
/// Defines a physical address
/// </summary>
public class PhysicalAddress {
/// <summary>
/// The Street part of the address
/// </summary>
public String Street { get; set; }
/// <summary>
/// The City part of the address
/// </summary>
public String City { get; set; }
/// <summary>
/// The State part of the address
/// </summary>
public String State { get; set; }
/// <summary>
/// The Country or Region part of the address
/// </summary>
public String CountryOrRegion { get; set; }
/// <summary>
/// The Postal Code part of the address
/// </summary>
public String PostalCode { get; set; }
}
You can define these types as you like, as long as they can be used to
deserialize the JSON response returned by the service. When looking at how the
code samples related to this book implement these types, it is interesting to
notice how the properties HomeAddress, BusinessAddress, and OtherAddress are
defined by leveraging the PhysicalAddress type. Moreover, note that the
property EmailAddresses of every contact is a collection of instances of type
UserInfo, which is also used to define the recipients of an email message. Thus,
you can reuse this property to send an email message directly to a specific
contact.
Furthermore, like with email messages and calendars, with contacts you can
browse multiple contact folders. Every user object has a navigation property
called contactFolders that represents a container folder for contacts. By default,
every user’s account has a default contact folder that is the root folder, which
can contain a hierarchy of child folders. Using the navigation properties
contactFolders and childFolders, you can browse all these folders. For example,
in Listing 5-30 you can see a code excerpt of a helper method to retrieve the
contacts of a specific contacts folder.
Every contact can have a picture to better define the contact in the address list.
To access the contact’s picture, as you will do in Chapter 6, you can make an
HTTP GET request for the navigation property photo and extract its $value. In
Listing 5-31, you can see a helper method to get a contact’s picture.
/// <summary>
/// Retrieves the picture of a contact, if any
/// </summary>
/// <param name="contactId">The ID of the contact</param>
/// <returns>The picture as a binary Stream</returns>
public static Stream GetContactPhoto(String contactId) {
Stream result = null;
String contentType = "image/png";
try {
result = MicrosoftGraphHelper.MakeGetRequestForStream(
String.Format("{0}me/contacts/{1}/photo/$value",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
contactId),
contentType);
}
catch (HttpException ex) {
if (ex.ErrorCode == 404) {
// If 404 -> The contact does not have a picture
// Keep NULL value for result
result = null;
}
}
return (result);
}
When you request the photo of a contact that does not have an associated
image, the Microsoft Graph API will return an HTTP Status Code with a value
of 404 (Not Found) and an error message with a value of “The specified object
was not found in the store.” That’s why the code excerpt of Listing 5-31 handles
this kind of HTTP response.
Managing contacts
Another set of common tasks when you work with contacts is updating existing
contacts and adding new contacts. As you can imagine, updating an existing
contact is just a matter of making an HTTP PATCH request, providing the JSON
serialized representation of the contact to update. The Contact type defined in
Listing 5-29 is also suitable for this task. In Listing 5-32, there is a code excerpt
to accomplish the update task.
/// <summary>
/// This method updates a contact
/// </summary>
/// <param name="contact">The contact to update</param>
/// <returns>The updated contact</returns>
public static Contact UpdateContact(Contact contact) {
String jsonResponse =
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}me/contacts/{1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
contact.Id),
contact,
"application/json");
var updatedContact = JsonConvert.DeserializeObject<Contact>
(jsonResponse);
return (updatedContact);
}
/// <summary>
/// This method adds a contact
/// </summary>
/// <param name="contact">The contact to add</param>
/// <returns>The added contact</returns>
public static Contact AddContact(Contact contact) {
String jsonResponse =
MicrosoftGraphHelper.MakePostRequestForString(
String.Format("{0}me/contacts",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri),
contact,
"application/json");
Both the methods return the updated or added contact object to make it easier
for you to handle the refresh of the UI, if needed.
Deleting a contact is also straightforward. In Listing 5-34, you can see the
corresponding helper method, which accepts the ID of the contact to delete.
/// <summary>
/// This method deletes a contact
/// </summary>
/// <param name="contactId">The ID of the contact to
delete</param>
public static void DeleteContact(String contactId) {
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}me/contacts/{1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri
contactId));
}
Summary
In this chapter, you saw how to set up an ASP.NET MVC application to
authenticate with Microsoft Azure Active Directory and how to register the
application in the Azure AD tenant that sits under the cover of a Microsoft
Office 365 tenant. Moreover, you saw how to leverage the Microsoft Active
Directory Authentication Library (ADAL) to acquire an OAuth access token and
how to store that token and a refresh token in a temporary cache.
Next, you saw how to leverage a helper class to manage the OAuth access
token and make all the required HTTP requests to consume the Microsoft Graph
API.
Last, you saw how to build a client application on top of these foundations
that can consume the mail, calendar, and contact services provided by the
Microsoft Graph API. You can use the code you saw in this chapter and the
related code samples you can find on the Internet
((http://aka.ms/OfficeDev365ProgrammingSamples)) as a starting point for
writing you own software solutions that leverage the powerful capabilities of the
Microsoft Graph.
Chapter 6. Users and Groups services
This chapter covers the services related to managing users and groups, which
can be security or dynamic groups or the new Microsoft Office 365 Groups. The
chapter covers the most useful API entities, entity sets, actions, and navigation
properties to handle these resources and services.
You can continue to refer to the sample application that was introduced in
Chapter 5, “Mail, calendar, and contact services” and is available at this URL:
http://aka.ms/OfficeDev365ProgrammingSamples.
Users services
As you saw in Chapter 5, the main step for consuming some resources through
the Microsoft Graph API in Microsoft .NET is to define the domain model types
that can be used to serialize and deserialize the exchanged JSON messages.
From a Users services perspective, the main type to define is the User class. In
Listing 6-1, you can see an excerpt of the User type definition.
LISTING 6-1 Code excerpt to define the User type for consuming the Users
services
Click here to view code image
/// <summary>
/// Defines a single tenant user
/// </summary>
public class User : BaseModel {
/// <summary>
/// Defines whether the user's account is enabled or not
/// </summary>
public Boolean AccountEnabled;
/// <summary>
/// List of licenses assigned to the user
/// </summary>
public List<AssignedLicense> AssignedLicenses;
/// <summary>
/// List of Office 365 plans assigned to the user
/// </summary>
public List<AssignedPlan> AssignedPlans;
/// <summary>
/// List of user's business phones
/// </summary>
public List<String> BusinessPhones;
/// <summary>
/// City of the user
/// </summary>
public String City;
/// <summary>
/// Company of the user
/// </summary>
public String CompanyName;
/// <summary>
/// Display Name of the user
/// </summary>
public String DisplayName;
/// <summary>
/// Mail of the user
/// </summary>
public String Mail;
/// <summary>
/// UPN for the user
/// </summary>
public String UserPrincipalName;
The list of the user’s attributes handled by the service is long, and for the sake
of simplicity in Listing 6-1 you can see just a few of them. Some of the most
important are UserPrincipalName, Mail, and AccountEnabled. In the following
sections, you will see how to handle users instances like this.
Reading users
To browse the list of users registered in an Office 365 tenant, you can make an
HTTP GET request for the users endpoint of the Microsoft Graph, as illustrated
in Listing 6-2.
LISTING 6-2 Code excerpt to enumerate the users registered in the current tenant
Click here to view code image
/// <summary>
/// This method retrieves the list of users registered in Azure AD
/// </summary>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of users in Azure AD</returns>
public static List<User> ListUsers(Int32 numberOfItems = 100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users?$top={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems));
As you can see, the method internally leverages the helper method
MakeGetRequestForString that we used in Chapter 5. Notice the $top query
string parameter to retrieve just a subset of users. By default, if you don’t specify
any value for the $top parameter, the query returns the top 100 items. The values
allowed for the $top parameter are between 1 and 999. Thus, you cannot retrieve
more than 999 users per query, but you can apply some partitioning rules using
the $filter query string parameter to reduce the result set according to the
filtering rules supported by the users collection.
Thus, if you would like to filter the result—for example, extracting only the
users working in a specified department—you can use a code excerpt like the
one illustrated in Listing 6-3.
LISTING 6-3 Code excerpt to enumerate all the users working in a specified
department
Click here to view code image
/// <summary>
/// This method retrieves the list of users working in a specific
department
/// </summary>
/// <param name="department">The department to filter the users
on</param>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of users in Azure AD</returns>
public static List<User> ListUsersByDepartment(String department,
Int32 numberOfItems = 100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users?
$filter=department%20eq%20'{1}'&$top={2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
department,
numberOfItems));
In Listing 6-3, you can see the OData $filter query string parameter, which
selects those user items that have the department field equal to a specific filter
value.
Another interesting filtering option is based on the userType property, which
assumes a value of Guest for the external users. Thus, if you would like to select
all the external users registered in the current tenant, you can use a query like the
one defined in Listing 6-4.
LISTING 6-4 Code excerpt to enumerate all the external users for a target tenant
Click here to view code image
/// <summary>
/// This method retrieves the list of all the external users for a
tenant
/// </summary>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of externa users in Azure AD</returns>
public static List<User> ListExternalUsers(Int32 numberOfItems =
100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users?
$filter=userType%20eq%20'Guest'&$top={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems));
Moreover, because the full user’s profile could be big and rich in attributes,
querying the list of users you will get back the following attributes by default:
businessPhones, displayName, givenName, id, jobTitle, mail, mobilePhone,
officeLocation, preferredLanguage, surname, and userPrincipalName. You can
leverage the OData $select query string parameter to change the default behavior
of the service, selecting a custom set of attributes like the code excerpt in Listing
6-5 does.
LISTING 6-5 Code excerpt that retrieves a list of users with some custom fields
Click here to view code image
/// <summary>
/// This method retrieves the list of users registered in Azure AD
with custom fields
/// </summary>
/// <param name="fields">The list of fields to retrieve</param>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of users in Azure AD</returns>
public static List<User> ListUsers(String[] fields = null, Int32
numberOfItems = 100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users?$top={1}{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems,
selectFilter));
var usersList = JsonConvert.DeserializeObject<UsersList>
(jsonResponse);
return (usersList.Users);
}
In Listing 6-6, you can see a code excerpt that invokes the helper method
illustrated in Listing 6-5.
LISTING 6-6 Code excerpt that invokes the helper method illustrated in Listing 6-5
Click here to view code image
Note
If you want to learn more about the fields that are synchronized by
default by the UPS in Microsoft SharePoint, you can read the article
“Default user profile property mappings in SharePoint Server
2013,” which is available at the following URL:
https://technet.microsoft.com/library/hh147510.aspx. If you want to
import custom properties in the UPS of SPO, you can refer to the
“User Profile Batch Update” sample that is available in the Office
365 Developer Patterns & Practices (PnP) project repository on
GitHub. It can be found at the following friendly URL:
http://aka.ms/PnPUserProfileBatchUpdateAPI.
In Listing 6-7, you can see the definition of a couple of helper methods to
retrieve the manager and the direct reports of a user.
LISTING 6-7 Code excerpt to retrieve the manager and the direct reports of a user
Click here to view code image
/// <summary>
/// This method returns the manager of a user
/// </summary>
/// <param name="upn">The UPN of the user</param>
/// <returns>The user's manager</returns>
public static User GetUserManager(String upn) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users/{1}/manager",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
upn));
/// <summary>
/// This method returns the direct reports of a user
/// </summary>
/// <param name="upn">The UPN of the user</param>
/// <returns>The user's direct reports</returns>
public static List<User> GetUserDirectReports(String upn) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("b",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
upn));
There are many other navigation properties that you can use to go through the
users’ calendars, events, mailboxes and mail folders, drives on OneDrive for
Business, and so on. To access these navigation properties, you must have the
proper account delegations and proper permissions in Azure AD. Aside from the
authorization rules, you can apply to these items the same development
techniques that you saw in Chapter 5 and that you will learn about in Chapter 7,
“File services.”
Just as you can read a list of users, you can get a single user object. You just
need to make an HTTP GET request providing, for example, the User Principal
Name (UPN) just after the users endpoint. In Listing 6-8, you can see a sample
helper method to accomplish this task.
/// <summary>
/// This method retrieves a single user from Azure AD
/// </summary>
/// <param name="upn">The UPN of the user to retrieve</param>
/// <returns>The user retrieved from Azure AD</returns>
public static User GetUser(String upn) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}users/{1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
upn));
/// <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>
public static Stream GetUserPhoto(String upn) {
String contentType = "image/png";
return (result);
}
The resulting stream can be used wherever you like. For example, in the
sample application related to this chapter, the user’s photo is used to reproduce
the current user’s profile picture in the Office 365 suite bar. To achieve this
result, the sample application uses a dedicated action in an ASP.NET MVC
controller where the stream is returned as a binary file together with a specific
image content type (image/png).
Groups services
Whenever you think about users in an enterprise scenario, you also have to think
about groups to better manage permissions and authorizations and perform
general management and governance. In Office 365, groups are managed in
Azure AD, like users’ accounts, and you can also manage them through the
Microsoft Graph API. In this section, you will learn how to do that.
Browsing groups
First, you can query the list of groups by querying the groups endpoint of the
Microsoft Graph API. The result will be a collection of objects that have a
structure like the one illustrated in Listing 6-10.
LISTING 6-10 Code excerpt to define the Group type for consuming the Groups
services
Click here to view code image
/// <summary>
/// Defines a Group
/// </summary>
public class Group : BaseModel {
public String Description;
public String DisplayName;
public List<String> GroupTypes;
public String Mail;
public Boolean MailEnabled;
public String MailNickname;
public Nullable<DateTimeOffset> OnPremisesLastSyncDateTime;
public String OnPremisesSecurityIdentifier;
public Nullable<Boolean> OnPremisesSyncEnabled;
public List<String> ProxyAddresses;
public Boolean SecurityEnabled;
public String Visibility;
public Boolean AllowExternalSenders;
public Boolean AutoSubscribeNewMembers;
public Boolean IsSubscribedByMail;
public Int32 UnseenCount;
}
As you can see, there are some descriptive properties, like the DisplayName
and the Description, and more functional properties, like those related to the
Mail address of the group, the ProxyAddresses, and so on. Moreover, there are
the GroupTypes collection property and the Boolean SecurityEnabled property
that are useful to disambiguate among the classic security groups (which are
mainly used for members’ authorization), the dynamic groups, and the new
Office 365 Unified Groups. These enable you to provide a better collaboration
experience for users, and they will be covered in the upcoming section. In
general, at the time of this writing there are three flavors of groups:
Security Groups Groups used for security authorization. They have the
GroupTypes collection property empty and the SecurityEnabled property
set to true.
Unified Groups The Office 365 Groups. They have the GroupTypes
collection property with a value of Unified and the SecurityEnabled
property set to false.
Dynamic Groups Groups with rule-based membership. This capability
requires Azure AD Premium in the back end. They have the GroupTypes
collection property with a value of DynamicMembership and the
SecurityEnabled property set to false.
By playing with these properties and querying the groups endpoint via OData,
you can start consuming the groups as you see in Listing 6-11.
/// <summary>
/// This method retrieves the list of groups registered in Azure
AD
/// </summary>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of groups in Azure AD</returns>
public static List<Group> ListGroups(Int32 numberOfItems = 100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups?$top={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems));
You can leverage the $top query string parameter to select a subset of group
items. If you want to retrieve a specific set of groups, like the security enabled
groups, you can apply a filtering condition by leveraging the $filter query string
parameter, as illustrated in Listing 6-12.
/// <summary>
/// This method retrieves the list of Security Groups
/// </summary>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of Security Groups</returns>
public static List<Group> ListSecurityGroups(Int32 numberOfItems =
100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups?
$filter=SecurityEnabled%20eq%20true" +
"&$top={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems));
Moreover, if you want to retrieve a specific group, you can use the ID of the
group object to make a direct HTTP GET request. In Listing 6-13, you can see
how to do that.
/// <summary>
/// This method retrieves a specific group registered in Azure AD
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The group instance</returns>
public static Group GetGroup(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
Managing groups
The basic purpose of having groups is to cluster users for authorization and
targeting goals. Thus, it is often necessary to retrieve the members of a group or
to manage groups’ membership. Every group has some useful navigation
properties to accomplish these tasks. For example, you can use the members
navigation property to see the members of a group. In Listing 6-14, you can see
a helper method to do that.
/// <summary>
/// This method retrieves the list of members of a group
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The members of the group</returns>
public static List<User> ListGroupMembers(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/members",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
/// <summary>
/// This method retrieves the list of owners of a group
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The owners of the group</returns>
public static List<User> ListGroupOwners(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/owners",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
/// <summary>
/// This method adds a new member to a group
/// </summary>
/// <param name="user">The user to add as a new group's
member</param>
/// <param name="groupId">The ID of the target group</param>
public static void AddMemberToGroup(User user, String groupId) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}groups/{1}/members/$ref",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId),
new GroupMemberToAdd
{
ObjectId = String.Format("{0}users/{1}/id",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
user.UserPrincipalName)
},
"application/json");
}
{"@odata.id":"https://graph.microsoft.com/v1.0/users/<UPN>/id"}
In Listing 6-17, you can see the definition of the GroupMemberToAdd type.
/// <summary>
/// This type defines a new member to add to a group
/// </summary>
public class GroupMemberToAdd {
[JsonProperty("@odata.id")]
public String ObjectId { get; set; }
}
LISTING 6-18 Code excerpt of a helper method to remove a member from a group
Click here to view code image
/// <summary>
/// This method removes a member from a group
/// </summary>
/// <param name="user">The user to remove from the group</param>
/// <param name="groupId">The ID of the target group</param>
public static void RemoveMemberFromGroup(User user, String
groupId) {
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}groups/{1}/members/{2}/$ref",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId, user.Id));
}
Notice the $ref URL property at the end of the member user’s URL. It is
mandatory to reference the specific membership object to delete it.
LISTING 6-19 Code excerpt of a helper method to query for the list of Office 365
Groups
Click here to view code image
/// <summary>
/// This method retrieves the list of Office 365 Groups
/// </summary>
/// <param name="numberOfItems">Defines the TOP number of items to
retrieve</param>
/// <returns>The list of Office 365 Groups</returns>
public static List<Group> ListUnifiedGroups(Int32 numberOfItems =
100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups?
$filter=groupTypes/any(gt:%20gt%20eq%20'Unified')" +
"&$top={1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
numberOfItems));
Notice the use of the any function of OData applied to the GroupTypes
collection property. Removing the URL encoding, the search query looks like
the following:
Click here to view code image
$filter=groupTypes/any(gt: gt eq 'Unified')
In this example, the arguments of the any function are similar to a lambda
expression (from a C# developer perspective), and the gt keyword represents
every instance of the values contained in the groupTypes collection. When the
instance value equals Unified, the any function will yield the containing group.
Typically, every Office 365 Group also has a picture to better describe the
group and to make it easier for the users to recognize that group within hundreds
or thousands of groups. Through the Microsoft Graph API, you can easily
retrieve the picture of a group by consuming the photo property, like you can for
a user. In Listing 6-20, you can see a helper method to retrieve a group’s picture
as a stream of bytes.
LISTING 6-20 Code excerpt of a helper method to retrieve the picture of a group
Click here to view code image
/// <summary>
/// This method retrieves the photo of a group from Azure AD
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The group's photo retrieved from Azure AD</returns>
public static Stream GetGroupPhoto(String groupId) {
String contentType = "image/png";
return (result);
}
The stream can be used wherever you need it to save or show the
corresponding image file.
/// <summary>
/// This method retrieves the list of threads of an Office 365
Group
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The threads of an Office 365 Group</returns>
public static List<ConversationThread>
ListUnifiedGroupThreads(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/threads",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
var conversationThreadsList =
JsonConvert.DeserializeObject<ConversationThreadsList>
(jsonResponse);
return (conversationThreadsList.Threads)
}
/// <summary>
/// This method retrieves a single post of a conversation thread
for an Office 365 Group
/// </summary>
/// <param name="groupId">The ID of the thread</param>
/// <param name="threadId">The ID of the thread</param>
/// <param name="postId">The ID of the post</param>
/// <returns>The post of the conversation thread for an Office 365
Group</returns>
public static ConversationThreadPost
GetUnifiedGroupThreadPost(String groupId,
String threadId, String postId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/threads/{2}/posts/{3}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId,
threadId,
postId));
var conversationThreadPost =
JsonConvert.DeserializeObject<ConversationThreadPost>
(jsonResponse);
return (conversationThreadPost);
}
In Listing 6-23, you can see how to send a new post into an existing thread.
Just invoke the reply action of the thread object, providing at least the body of
the message and then providing the categories, any new message recipient,
attachment, and whatever else pertains to the post.
/// <summary>
/// This method replies to a thread of an Office 365 Group
/// </summary>
/// <param name="groupId">The ID of the thread</param>
/// <param name="threadId">The ID of the thread</param>
/// <param name="post">The post to send as the reply</param>
public static void ReplyToUnifiedGroupThread(String groupId,
String threadId, ConversationThreadPost post) {
MicrosoftGraphHelper.MakePostRequest(
String.Format("{0}groups/{1}/threads/{2}/reply",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId,
threadId), new { post }, "application/json");
}
Notice the use of the anonymous type as the content argument for the
MakePostRequest method invocation. The reply action signature of a
conversation thread accepts a complex type, serialized in JSON, with a post
property that represents the post with which to reply. In Listing 6-24, you can
see a code excerpt that uses the previous helper method.
LISTING 6-24 Code excerpt using the helper method illustrated in Listing 6-23
Click here to view code image
UnifiedGroupsHelper.ReplyToUnifiedGroupThread(group.Id,
threads[0].Id,
new Models.ConversationThreadPost {
Body = new Models.ItemBody {
Type = Models.BodyType.Html,
Content = "<html><body><div>This is the body of a post
created via" +
"the Microsoft Graph API!</div></body></html>",
},
NewParticipants = new List<Models.UserInfoContainer>(
new Models.UserInfoContainer[] {
new Models.UserInfoContainer {
Recipient = new Models.UserInfo {
Name = "Paolo Pialorsi",
Address = "[email protected]",
}
}
}),
});
As you can see, the sample code excerpt of Listing 6-24 replies to the thread
and includes a new recipient through the NewParticipants collection property of
the ConversationThreadPost type.
Another interesting capability of an Office 365 Group is the group-related
calendar. Using the calendar navigation property of the group endpoint, you can
access both the calendar of a Unified Group and the events stored in that
calendar. In Listing 6-25, you can see the helper method to access a calendar of a
Unified Group.
LISTING 6-25 Definition of the helper method to access the calendar of an Office
365 Group.
Click here to view code image
/// <summary>
/// This method retrieves the calendar of an Office 365 Group
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The calendar of an Office 365 Group</returns>
public static Calendar GetUnifiedGroupCalendar(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/calendar",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
var calendar = JsonConvert.DeserializeObject<Calendar>
(jsonResponse);
return (calendar);
}
As you can see, the type of the result of the calendar navigation property is
identical to that of the services illustrated in Chapter 5. Thus, with the calendars
of Unified Groups, you can leverage all the functionalities and capabilities that
you saw in Chapter 5. As an example, in Listing 6-26 you see how to consume
the list of calendar events for a specific Office 365 Group.
/// <summary>
/// Retrieves the events of an Office 365 Group calendar within a
specific date range
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <param name="startDate">The start date of the range</param>
/// <param name="endDate">The end date of the range</param>
/// <param name="startIndex">The startIndex (0 based) of the items
to retrieve</param>
/// <returns>A page of up to 10 events</returns>
public static List<Event> ListUnifiedGroupEvents(String groupId,
DateTime startDate,
DateTime endDate, Int32 startIndex = 0) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/calendarView?startDateTime=
{2:o}&" +
"endDateTime={3:o}&$skip={4}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId,
startDate.ToUniversalTime(),
endDate.ToUniversalTime(),
startIndex));
LISTING 6-27 The helper method to retrieve the drive of an Office 365 Group
Click here to view code image
/// <summary>
/// This method retrieves the OneDrive for Business of an Office
365 Group
/// </summary>
/// <param name="groupId">The ID of the group</param>
/// <returns>The OneDrive for Business of an Office 365
Group</returns>
public static Drive GetUnifiedGroupDrive(String groupId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}groups/{1}/drive",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
The type with name Drive will be defined and detailed in Chapter 7.
LISTING 6-28 The helper method to create a new Office 365 Group
Click here to view code image
/// <summary>
/// Creates/Adds a new Office 365 Group
/// </summary>
/// <param name="group">The group to add/create</param>
/// <returns>The just added group</returns>
public static Group AddUnifiedGroup(Group group) {
String jsonResponse =
MicrosoftGraphHelper.MakePostRequestForString(
String.Format("{0}groups",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri),
group, "application/json");
LISTING 6-29 A sample code excerpt that uses the i helper method
Click here to view code image
Notice that the value for the GroupTypes collection property is Unified. If you
like, you can configure many more properties while creating a new Office 365
Group. However, those highlighted in the demo will suffice. When you create a
new group, the email address of the group—if any—will be generated
automatically by the Exchange Online service, which sits under the cover of the
mail capabilities of a group, and you cannot force any explicit value because the
mail property of the group is read-only.
Because you could have many Office 365 Groups in your tenant, it is a good
habit to set up every group with a specific and identifying group icon. Setting the
group image/icon is straightforward: you just need to make an HTTP PATCH
request against the photo/$value property of the target group. The approach to
use is similar to the one for setting a single user’s picture. In Listing 6-30, you
can see the corresponding helper method.
LISTING 6-30 The definition of a helper method to set the picture of an Office 365
Group
Click here to view code image
/// <summary>
/// Updates the photo of an Office 365 Group
/// </summary>
/// <param name="groupId">The ID of the target group</param>
/// <param name="photo">The byte array of the photo</param>
public static void UpdateUnifiedGroupPhoto(String groupId, Stream
photo) {
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}groups/{1}/photo/$value",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId),
photo, "image/jpeg");
}
The request will contain the image content as a raw stream of bytes, provided
with a specific content/type for the request body. The response—if successful—
will be an HTTP Status Code with a value of 200 (OK), with no specific text or
will be an HTTP Status Code with a value of 200 (OK), with no specific text or
echo of the request.
If you want to delete an existing Office 365 Group, you can make an HTTP
DELETE request targeting the URL of the group that you want to delete. In
Listing 6-31, you can see a code excerpt to accomplish this task.
LISTING 6-31 The definition of a helper method to delete an Office 365 Group
Click here to view code image
/// <summary>
/// Deletes an Office 365 Group
/// </summary>
/// <param name="groupId">The ID of the group to delete</param>
public static void DeleteUnifiedGroup(String groupId) {
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}groups/{1}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
groupId));
}
Summary
In this chapter, you learned how to read and manage users and groups in an
Office 365 tenant. In particular, you saw how to browse the groups according to
group type (Security, Dynamic, and Unified). Furthermore, you saw how to
query existing Office 365 Groups and how to browse conversations, threads,
calendars, and drives of Unified Groups. Last, you learned how to create, update,
and delete an Office 365 Group by using the Microsoft Graph API.
Chapter 7. File services
OneDrive for Business is one of the most used services of Microsoft Office 365,
together with Microsoft Exchange Online. In fact, almost every Office 365 user
uses a personal drive on OneDrive for Business and has a mailbox.
In this chapter, we will see how to leverage the File services, which enable us
to consume and manage drives and files in OneDrive for Business. As in the
previous chapters of Part III, the code samples illustrated in this chapter are
available at the URL: http://aka.ms/OfficeDev365ProgrammingSamples.
LISTING 7-1Code excerpt of the helper method to access the current user’s
personal drive
Click here to view code image
/// <summary>
/// This method returns the personal drive of the current user
/// </summary>
/// <returns>The current user's personal drive</returns>
public static Drive GetUserPersonalDrive() {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}me/drive",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri));
The result will be an instance of a custom Drive type that defines the drive
object and can be serialized into and deserialized from the JSON representation
of a drive provided by the Microsoft Graph API. The custom Drive type is
defined in Listing 7-2.
LISTING 7-2The definition of the custom Drive type for representing a OneDrive
for Business drive
Click here to view code image
/// <summary>
/// Defines a drive of OneDrive for Business
/// </summary>
public class Drive : BaseModel {
/// <summary>
/// The type of the current Drive
/// </summary>
public String DriveType { get; set; }
/// <summary>
/// The drive's owner
/// </summary>
public IdentitySet Owner { get; set; }
/// <summary>
/// The storage quota of the drive
/// </summary>
public Quota Quota { get; set; }
}
As you can see, there is a DriveType property of type String, which assumes a
value of business for a OneDrive for Business drive.
Note
Through the Drive instance, you can also access the Owner of the drive and
the Quota numbers of the current drive. For example, by leveraging the Owner
property of the drive object, you can access the user who owns that drive
directly. This could be useful when you are browsing third parties’ drives (as
long as you have proper permissions to do that) and want to traverse the graph to
gain a user from her drive object.
LISTING 7-3Code excerpt of the helper method to access the root folder of the
current user’s personal drive
Click here to view code image
/// <summary>
/// This method returns the root folder of the personal drive of
the current user
/// </summary>
/// <returns>The root folder of the current user's personal
drive</returns>
public static DriveItem GetUserPersonalDriveRoot() {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}medriveroot",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri));
/// <summary>
/// Defines any generic item of a drive
/// </summary>
public class DriveItem : BaseModel {
public System.IO.Stream Content;
public IdentitySet CreatedBy;
public Nullable<DateTimeOffset> CreatedDateTime;
public String CTag;
public String Description;
public String ETag;
public IdentitySet LastModifiedBy;
public Nullable<DateTimeOffset> LastModifiedDateTime;
public String Name;
public ItemReference ParentReference;
public Nullable<Int64> Size;
public String WebDavUrl;
public String WebUrl;
public Audio Audio;
public Deleted Deleted;
public File File;
public FileSystemInfo FileSystemInfo;
public Folder Folder;
public Image Image;
public GeoCoordinates Location;
public Photo Photo;
public SearchResult SearchResult;
public Shared Shared;
public SpecialFolder SpecialFolder;
public Video Video;
}
For example, the root folder of a user’s drive will hold just a few of the
available properties. For the root folder and for folders in general, you will have
at least the Name, the WebUrl, the CreatedDateTime, and the
LastModifiedDateTime.
However, when you access a specific drive or folder, you usually are
interested in consuming the files contained in that drive or folder. Let’s say that
you want to retrieve all the files stored in the root folder of the current user’s
drive. As you learned in Chapter 3, “Microsoft Graph API reference,” you can
make an HTTP GET request for the children navigation property of the folder
you want to browse. For the root folder of the current user, the relative URL path
could be like the following:
medrive/root/children
But to target any folder and any drive in general—not only the root folder of
the current user’s drive—you can use the ID of the drive and the ID of the folder
to build a direct path to the collection of children items. So, you will have
something like the following relative URL path:
Click here to view code image
drives<DriveID>/items/<FolderId>/children
In Listing 7-5, you can see a helper method that retrieves the children items of
any folder for any target drive.
LISTING 7-5 The helper method to retrieve the children items of a target folder in
a specific drive
Click here to view code image
/// <summary>
/// This method returns the children items of a specific folder
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="folderId">The ID of the target folder</param>
/// <param name="numberOfItems">The number of items to
retrieve</param>
/// <returns>The children items</returns>
public static List<DriveItem> ListFolderChildren(String driveId,
String folderId,
Int32 numberOfItems = 100) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}drives/{1}/items/{2}/children?$top={3}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
folderId,
numberOfItems));
By leveraging code recursion, you can then browse all the folders of a drive
by using the ID of the folders and by accessing their children items. However,
you should keep in mind that by default the Microsoft Graph API will return no
more than 200 items for each request. Thus, you will have to leverage the
@odata.nextLink property of the result with the $top OData query string
parameter if you want to do paging of results and/or if you want to consume all
the children items, page by page.
Consuming files
When you have identified a file that you want to consume, you can access it
directly by ID, using the same syntax that you use for consuming a folder. Thus,
the relative URL will look like the following:
Click here to view code image
drives<DriveID>/items/<FileId>
LISTING 7-6The helper method to consume the content of a file from OneDrive
for Business
Click here to view code image
/// <summary>
/// This method returns the content of a specific file by ID
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <returns>The content of the file as a Stream</returns>
public static Stream GetFileContent(String driveId, String fileId,
String contentType) {
Stream fileContent =
MicrosoftGraphHelper.MakeGetRequestForStream(
String.Format("{0}drives/{1}/items/{2}/content",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId),
contentType);
return (fileContent);
}
As you saw in Chapter 3, the response will be an HTTP Redirect (Status Code
302) to the URL of the file. Thus, you will have to enable HTTP redirection in
the HttpClient instance that you used to make the request.
However, often you don’t know where a file is, and you want to search for it
and then consume its content or properties. In that case, you can use the
microsoft.graph.search (or search) action, which can be applied to any folder of
OneDrive for Business. In Listing 7-7, you can see a function that leverages this
search capability.
LISTING 7-7 The helper method to search for files in OneDrive for Business
Click here to view code image
/// <summary>
/// This method searches for a file in the target drive and
optional target folder
/// </summary>
/// <param name="searchText">The text to search for</param>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="folderId">The ID of the target folder,
optional</param>
/// <returns>The list of resulting DriveItem objects, if
any</returns>
public static List<DriveItem> Search(String searchText, String
driveId,
String folderId = null) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(requestUri);
var driveItems = JsonConvert.DeserializeObject<DriveItemList>
(jsonResponse);
return (driveItems.DriveItems);
}
Notice that the function applies the action to a specific folder URL if the input
argument folderId is provided. Otherwise, it applies the action to the root folder
of the drive, which will search the content in the entire drive. Aside from that, it
is just a common REST query that will return a set of objects of type DriveItem
if there is any result corresponding to the provided query text. Nevertheless, you
should consider that there could be some delay in finding newly added items due
to the indexing engine that works in the background. Thus, don’t be surprised if
you do not instantly find any new file that you just uploaded.
When consuming files, sometimes it is also useful to present a list of files—
for example, the list of search results—including a thumbnail of each file item.
Luckily, OneDrive for Business and the Microsoft Graph API provide an out-of-
box capability to generate and provide thumbnails for known file types. For
every known file type—Office files, video, audio, images, and many others—
you can have three different thumbnails: small, medium, and large. Depending
on the user interface or the user experience that you want to provide, you can use
any of these autogenerated thumbnails. You will need to make an HTTP GET
request for the thumbnails navigation property of the DriveItem that you target,
and the result will be the set of the three available thumbnails. You can also
access a specific thumbnail size directly. In Listing 7-8, you can see a helper
method to get these thumbnails for a specific file item.
/// <summary>
/// This method returns the thumbnails of a specific file by ID
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <returns>The file thumbnails for the specific file</returns>
public static ThumbnailSet GetFileThumbnails(String driveId,
String fileId) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}drives/{1}/items/{2}/thumbnails",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId));
var thumbnails =
JsonConvert.DeserializeObject<ThumbnailSetResponse>(jsonResponse);
return (thumbnails.Value.Count > 0 ? thumbnails.Value[0] :
null);
}
/// <summary>
/// This method returns a thumbnail by size of a specific file by
ID
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <param name="size">The size of the target thumbnail</param>
/// <returns>The file thumbnails for the specific file</returns>
public static Thumbnail GetFileThumbnail(String driveId, String
fileId,
ThumbnailSize size) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}drives/{1}/items/{2}/thumbnails/0/{3}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId,
size.ToString().ToLower()));
Every thumbnail item will provide information about the height and width of
the image file and the URL of the physical image file. If you want, you can even
download the image file directly. For example, the URL of a thumbnail could be
something like this:
https://<tenant>-
my.sharepoint.com/personal/<user>/_api/v2.0driveitems/<fileId>/thumbnails/0/large/th
The URL of the thumbnail file content comes from the base URL of OneDrive
for Business, which is related to the URL https://<tenant>-my.sharepoint.com.
Thus, you cannot request this URL by providing the classic OAuth 2.0 access
token that we used so far because that one targets the Microsoft Graph API
resource identifier (https://graph.microsoft.com). As you learned in Chapter 4,
“Azure Active Directory and security,” you have to request an access token
specific to every different resource by using the refresh token to request or
refresh an access token. So to consume the image file of a thumbnail, you have
to switch from the Microsoft Graph API to the OneDrive API, and you have to
create a new access token. Aside from that, it will be a common HTTP request
for a file stream, as you can see in Listing 7-9.
LISTING 7-9 The helper method to retrieve the image file of a thumbnail for
specific files in OneDrive for Business
Click here to view code image
/// <summary>
/// This method returns the thumbnails of a specific file by ID
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <returns>The file thumbnails for the specific file</returns>
public static Stream GetFileThumbnailImage(String driveId, String
fileId,
ThumbnailSize size) {
String jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}drives/{1}/items/{2}/thumbnails/0/{3}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId,
size.ToString().ToLower()));
return (thumbnailImageStream);
}
Under the cover of the MakeGetRequestForStream method, the
MicrosoftGraphHelper class will disambiguate among the various resource
identifiers and request the proper access token to the Microsoft Azure AD
OAuth 2.0 endpoint.
/// <summary>
/// This method creates a new folder in OneDrive for Business
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="parentFolderId">The ID of the parent
folder</param>
/// <param name="folder">The new folder object to create</param>
/// <returns>The just created folder</returns>
public static DriveItem CreateFolder(String driveId, String
parentFolderId,
DriveItem folder) {
var jsonResponse =
MicrosoftGraphHelper.MakePostRequestForString(
String.Format("{0}drives/{1}/items/{2}/children",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
parentFolderId),
folder,
"application/json");
var newFolder = JsonConvert.DeserializeObject<DriveItem>
(jsonResponse);
return (newFolder);
}
LISTING 7-11 Code excerpt to invoke the helper method illustrated in Listing 7-10
Click here to view code image
Once you have a target folder, you can upload a new file into it. A file is a
DriveItem like a folder, and you need to make an HTTP POST request against
the children entity set of the parent folder where you want to create the file.
However, a DriveItem that represents a file must have the File property assigned
instead of the Folder property. Moreover, you will also have to upload the real
file content, making an HTTP PUT request against the content property of the
file item. In Listing 7-12, you can see a helper method that accepts the drive and
parent folder in which you want to create a file and the file object of type
DriveItem and a System.IO.Stream that will represent the real content for the file.
LISTING 7-12 Helper method to create and upload a file into a target parent folder
Click here to view code image
/// <summary>
/// This method creates and uploads a file into a parent folder
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="parentFolderId">The ID of the parent
folder</param>
/// <param name="file">The file object</param>
/// <param name="content">The binary stream of the file
content</param>
/// <param name="contentType">The content type of the file</param>
/// <returns>The just created and uploaded file object</returns>
public static DriveItem UploadFile(String driveId, String
parentFolderId,
DriveItem file, Stream content, String contentType) {
var jsonResponse =
MicrosoftGraphHelper.MakePostRequestForString(
String.Format("{0}drives/{1}/items/{2}/children",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
parentFolderId),
file,
"application/json");
try {
MicrosoftGraphHelper.MakePutRequest(
String.Format("{0}drives/{1}/items/{2}/content",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
uploadedFile.Id),
content,
contentType);
}
catch (ApplicationException ex) {
// For whatever reason we come here ... the upload failed
// and we need to delete the just created file
FilesHelper.DeleteFile(driveId, uploadedFile.Id);
Notice that the helper method makes two requests against the Microsoft Graph
API. In a real enterprise-level solution, you should provide some kind of logical
transaction and a compensating transaction that will remove the file from the
parent folder if the content upload fails for any reason. That’s why there is a try
catch statement wrapping the content upload stage. So, in case of any failure
during content upload, the DriveItem will be deleted.
The previous helper method, in case of any failure, uses another helper
method to delete a file, which makes an HTTP DELETE request against the
target file item. In Listing 7-13, you can see the helper method to delete a file in
OneDrive for Business.
/// <summary>
/// This method deletes a file in OneDrive for Business
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
public static void DeleteFile(String driveId, String fileId) {
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}drives/{1}/items/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId));
}
In Listing 7-14, you can see another option that you have to upload a new file,
including its content, by making a unique HTTP request. If you make an HTTP
PUT request—for example, providing the name of the target file instead of the
ID in the target URL of the PUT request—the Graph API will create the file on
the fly if it does not exist.
LISTING 7-14Revised helper method to create and upload a file into a target
parent folder
Click here to view code image
/// <summary>
/// This method creates and uploads a file into a parent folder
with a unique request
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="parentFolderId">The ID of the parent
folder</param>
/// <param name="file">The file object</param>
/// <param name="content">The binary stream of the file
content</param>
/// <param name="contentType">The content type of the file</param>
/// <returns>The just created and uploaded file object</returns>
public static DriveItem UploadFileDirect(String driveId, String
parentFolderId,
DriveItem file, Stream content, String contentType) {
var jsonResponse =
MicrosoftGraphHelper.MakePutRequestForString(
String.Format("
{0}drives/{1}/items/{2}/children/{3}/content",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
parentFolderId,
file.Name),
content,
contentType);
In the previous sample, notice the target relative URL, which has a format like
the following:
Click here to view code image
drives<DriveID>/items/<FolderId>/children/<TargetFileName>/content
LISTING 7-15 Helper methods to update name and/or content of an existing file
Click here to view code image
/// <summary>
/// This method renames an already existing file in OneDrive for
Business
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <param name="newFileName">The new file name</param>
/// <returns>The updated DriveItem corresponding to the renamed
file</returns>
public static DriveItem RenameFile(String driveId, String fileId,
String newFileName) {
var jsonResponse =
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}drives/{1}/items/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId),
new DriveItem {
Name = newFileName,
},
"application/json");
/// <summary>
/// Uploads a new file content on top of an already existing file
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="fileId">The ID of the target file</param>
/// <param name="content">The binary stream of the file
content</param>
/// <param name="contentType">The content type of the file</param>
public static void UpdateFileContent(String driveId, String
fileId,
Stream content, String contentType) {
MicrosoftGraphHelper.MakePutRequest(
String.Format("{0}drives/{1}/items/{2}/content",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
fileId),
content,
contentType);
}
LISTING 7-16 Helper method to move a DriveItem within a OneDrive for Business
drive
Click here to view code image
/// <summary>
/// This method moves one item from one parent folder to another
/// </summary>
/// <param name="driveId">The ID of the target drive</param>
/// <param name="driveItemId">The ID of the target file</param>
/// <param name="newItemName">The new name for the item in the
target folder</param>
/// <param name="newParent">The name of the new target
folder</param>
/// <returns>The moved DriveItem instance</returns>
public static DriveItem MoveDriveItem(String driveId, String
driveItemId,
String newItemName, String newParent) {
var jsonResponse =
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}drives/{1}/items/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveId,
driveItemId),
new DriveItem {
Name = newItemName,
ParentReference = new ItemReference {
Path = $"driveroot:/{newParent}"
}
},
"application/json");
var movedItem = JsonConvert.DeserializeObject<DriveItem>
(jsonResponse);
return (movedItem);
}
/// <summary>
/// This method returns a list of permissions for a specific
DriveItem in OneDrive for
Business
/// </summary>
/// <param name="driveItemId">The ID of the DriveItem</param>
/// <returns>The list of permission for the target
object</returns>
public static List<Permission> GetDriveItemPermissions(String
driveItemId, String permissionId) {
var jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}medriveitems/{1}/permissions",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveItemId));
If you want to retrieve a single object of type Permission, you can do that by
directly querying the object by ID. In Listing 7-19, there is a helper method that
retrieves by ID a single permission for a target DriveItem.
/// <summary>
/// This method returns a permission of a specific DriveItem in
OneDrive for Business
/// </summary>
/// <param name="driveItemId">The ID of the DriveItem</param>
/// <param name="permissionId">The ID of the permission</param>
/// <returns>The permission object</returns>
public static Permission GetDriveItemPermission(String
driveItemId, String permissionId) {
var jsonResponse =
MicrosoftGraphHelper.MakeGetRequestForString(
String.Format("{0}medriveitems/{1}/permissions/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveItemId,
permissionId));
/// <summary>
/// This method removes a permission from a target DriveItem
/// </summary>
/// <param name="driveItemId">The ID of the DriveItem</param>
/// <param name="permissionId">The ID of the permission</param>
public static void RemoveDriveItemPermission(String driveItemId,
String permissionId) {
MicrosoftGraphHelper.MakeDeleteRequest(
String.Format("{0}medriveitems/{1}/permissions/{2}",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri
driveItemId,
permissionId));
}
Because this is the fourth chapter in which you see this kind of behavior while
consuming entity sets through the Microsoft Graph API, you shouldn’t be
surprised by the syntax used in Listing 7-20.
Sharing a file
Sharing a file, which means creating a new permission for a target object of type
DriveItem, is another interesting use case that can be helpful in real business and
enterprise-level solutions. To share a file, you need to invoke with an HTTP
POST request the microsoft.graph.createLink (or createLink) action, targeting
the URL of the DriveItem that you want to share. Within the body of the HTTP
request, you will have to provide a JSON object that defines the features of the
sharing link that you want to create. As you saw in Chapter 3, every sharing link
has a type, which can be view, edit, or embed, and a scope, which can be
organization or anonymous.
In Listing 7-21, you can see another helper method that makes it easy to create
a sharing link for a target DriveItem.
/// <summary>
/// This method creates a sharing link for a target DriveItem
/// </summary>
/// <param name="driveItemId">The ID of the DriveItem</param>
/// <param name="type">The type of the sharing link</param>
/// <param name="scope">The scope of the sharing link</param>
/// <returns>The just added permission for the sharing
link</returns>
public static Permission CreateSharingLink(String driveItemId,
SharingLinkType type, SharingLinkScope scope) {
var jsonResponse =
MicrosoftGraphHelper.MakePostRequestForString(
String.Format("{0}medriveitems/{1}/createLink",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
driveItemId),
new {
@type = type.ToString().ToLower(),
@scope = scope.ToString().ToLower(),
},
"application/json"
);
var addedPermission =
JsonConvert.DeserializeObject<Permission>(jsonResponse);
return (addedPermission);
}
Notice the use of the anonymous type for defining the type and scope of the
sharing link. The result of the helper method will be a new object of custom type
Permission that defines the newly added sharing permission for the target item.
Summary
In this chapter, you saw how to browse drives, folders, and files stored within
OneDrive for Business. You learned how to consume files’ content and files’
thumbnails. You saw how to upload new files and update existing files.
Moreover, you learned how to move a file or a folder around a drive and how to
delete an item, whether it is a file or a folder. You also learned how to search for
contents in OneDrive for Business. Last, you realized how to manage item-level
permissions and how to share a file or a folder by creating a sharing link or by
reading a list of permissions.
Now you have the knowledge and the power to manage the contents of
OneDrive for Business programmatically through the Microsoft Graph API.
Chapter 8. Microsoft Graph SDK for .NET
In the previous chapters, you played with the Microsoft Graph API by using
low-level HTTP requests, which are a good option to keep control of what is
travelling on the wire but can sometimes be painful to develop and maintain.
Luckily, Microsoft released a NuGet package called Microsoft Graph SDK for
.NET, which enables you to accomplish some of the most common and useful
tasks by using a helper library, without having to dig into the details of the
HTTP and REST protocols.
In this chapter, you will learn about the architecture, the functionalities, and
the capabilities of the Microsoft Graph SDK for .NET and about the Microsoft
Graph SDKs for all the other platforms. All the code samples and snippets
illustrated in this chapter are available as a sample application on GitHub in the
OfficeDev PnP repository at the following URL:
https://github.com/OfficeDev/PnP/tree/master/Samples/MicrosoftGraph.Office365.DotNetSDK
More Information
In the following paragraphs, you will learn how to leverage each of these two
registration models, which imply a corresponding authentication and
authorization model. Regardless of the registration model, you will have to
provide the authentication logic to the Microsoft Graph SDK to be able to
consume the Graph API. Out of the box, the Microsoft Graph SDK does not
have any authentication logic. Instead, there is an abstract interface called
IAuthenticationProvider that has to be implemented by a class that implements
the real authentication. In Listing 8-1, you can see the definition of the
IAuthenticationProvider interface.
namespace Microsoft.Graph {
using System.Net.Http;
using System.Threading.Tasks;
/// <summary>
/// Interface for authenticating requests.
/// </summary>
public interface IAuthenticationProvider {
/// <summary>
/// Authenticates the specified request message.
/// </summary>
/// <param name="request">The <see
cref="HttpRequestMessage"/>
/// to authenticate.</param>
/// <returns>The task to await.</returns>
Task AuthenticateRequestAsync(HttpRequestMessage request);
}
}
LISTING 8-2 Creation of the GraphServiceClient class using ADAL and Azure
AD for authentication
Click here to view code image
if (!String.IsNullOrEmpty(accessToken)) {
// Configure the HTTP bearer Authorization Header
requestMessage.Headers.Authorization = new
AuthenticationHeaderValue("bearer",
accessToken);
}
else {
throw new Exception("Invalid authorization
context");
}
return (Task.FromResult(0));
}));
LISTING 8-3 Creation of the GraphServiceClient class by using MSAL and the v2
authentication endpoint
Click here to view code image
Notice that aside from the custom permissions that you provide to MSAL
through the scope, internally it will always add permission requests for signing
in the user (openid), reading the user’s email and profile, and accessing the
user’s data any time (offline_access).
Moreover, the tenant scope is also implied by the provided user’s credentials,
and you don’t need to register any specific tenant in the application registration
portal or provide a target tenant to the PublicClientApplication instance. The v2
authentication endpoint and MSAL are multitenant by default.
For the sake of brevity, and because at the time of this writing MSAL is still
under preview and in alpha release, we will not go deeper into this topic.
However, we will use it in the code samples related to this chapter to keep the
samples up to date.
Regardless of how you created the GraphServiceClient instance, once you
have it you can just browse the properties of the object, which provide a fluent
API of objects and a common and useful query model. The objects provided by
the Graph SDK domain model map to the resources published by the Microsoft
Graph REST API and are provided on the .NET side as property bag classes. For
example, in Listing 8-4 you can see a code excerpt to query the DisplayName
property of the current user (me).
LISTING 8-4 Code excerpt to query the current user’s DisplayName property
Click here to view code image
var me = await
graphClient.Me.Request().Select("DisplayName").GetAsync();
var displayName = me.DisplayName;
Notice that the Graph SDK object model is completely asynchronous and the
syntax for querying objects looks similar to that of LINQ (Language Integrated
Query), but it is not identical and you cannot use LINQ and IQueryable<T> to
query the Graph SDK domain model.
Under the cover of every request, there will be an HTTP request handled by
the Graph SDK engine through the implementation of the HTTP provider and a
the Graph SDK engine through the implementation of the HTTP provider and a
process of serialization/deserialization of JSON objects into domain model
objects.
Request model
To better understand how the Microsoft Graph SDK works, it’s worth digging a
little deeper into the request model that sits under the cover of the library.
Whenever you want to access one or more resources, you have to ask the
Graph SDK to make a REST request for you. Internally, the SDK has an engine
made of request builders that are invoked whenever you call the Request()
method of a target object. Because the Microsoft Graph API is continuously
growing and evolving, the request builders are generated together with the
domain model types that map the resources by using the $metadata endpoint of
the target Graph API. The VIPR project, referenced earlier in this chapter, is
responsible for the autogeneration of all these types. Moreover, to allow easy
unit testing of custom developed solutions, all the request handlers implement a
specific interface that is autogenerated by VIPR.
Listing 8-5 shows how the GraphClientService type is defined from an
interface-level perspective at the time of this writing.
public GraphServiceClient(IAuthenticationProvider
authenticationProvider,
IHttpProvider httpProvider = null);
public IGraphServiceDirectoryObjectsCollectionRequestBuilder
DirectoryObjects { get; }
public IGraphServiceDirectoryRolesCollectionRequestBuilder
DirectoryRoles { get; }
public
IGraphServiceDirectoryRoleTemplatesCollectionRequestBuilder
DirectoryRoleTemplates { get; }
public IGraphServiceOrganizationCollectionRequestBuilder
Organization { get; }
public IGraphServiceSubscribedSkusCollectionRequestBuilder
SubscribedSkus { get; }
public IGraphServiceSubscriptionsCollectionRequestBuilder
Subscriptions { get; }
As you can see, every complex property like the collection of Users, the
collection of Subscriptions, the Drive, the current user (Me), and all the other
properties are of type I*RequestBuilder, where the asterisk corresponds to the
underling type name. For example, the Me property will return the current user
resource object. Thus, it will be of type IUserRequestBuilder.
In the set of generated request builder types, there will be the
UserRequestBuilder class, which implements the IUserRequestBuilder interface
and internally prepares all the REST requests for a single user resource by
leveraging the UserRequest type. For the sake of completeness, if you open the
source code of the UserRequest type in the Microsoft Graph SDK, either by
browsing it on GitHub (https://github.com/microsoftgraph/msgraph-sdk-
dotnet/blob/master/src/Microsoft.Graph/Requests/Generated/UserRequest.cs) or
by forking the GitHub repository locally on your development environment, you
will see that internally it handles the HTTP REST requests, including the proper
HTTP methods, all the HTTP headers, and the query string parameters.
In the section “Reading users” in Chapter 6, “Users and Groups services,” you
saw how to select one or more properties of a user by querying the endpoint for
the collection of users and providing the $select query string parameter. For
example, a REST query to get the DisplayName, UserPrincipalName, and Mail
address of all the users can be done by making a GET request for the following
URL:
https://graph.microsoft.com/v1.0/users?
$select=displayName,userPrincipalName,mail
Here, you can see the corresponding syntax if you want to make the same
request by using the Microsoft Graph SDK:
Click here to view code image
var users = await
graphClient.Users.Request().Select("DisplayName,UserPrincipalName,Mail").GetA
As you can see, the request model is straightforward and clearly maps to the
corresponding low-level REST approach.
For the request model, it is important to underline that to execute the HTTP
request effectively against the target, you have to invoke the GetAsync method,
which is an asynchronous method, as the name implies. In fact, the Graph SDK
is completely asynchronous and ready for the asynchronous development model
of .NET.
Querying resources
The situation becomes more challenging if you also want to filter, sort, or
partition data. As you learned in the previous chapters, the Graph REST API is
compliant with the OData v4 protocol specification. Thus, it supports query
string parameters like $filter, $orderBy, $skip, $top, and $expand.
The Microsoft Graph SDK supports the same statements. In this section, you
will see each of them in action.
LISTING 8-6 Code excerpt to query the users filtered by Department property
value
Click here to view code image
var filteredUsers = await graphClient.Users.Request()
.Select("DisplayName,UserPrincipalName,Mail")
.Filter("department eq 'IT'")
.GetAsync();
Notice that you can create a chain of methods so that, for example, in Listing
8-6 you apply the Filter method on top of the results of the Select method. The
corresponding REST request will be something like the following:
Click here to view code image
https://graph.microsoft.com/v1.0/users?
$select=displayName,userPrincipalName,mail,department&$fi
lter=department%20eq%20'IT'
As you can see, the string argument of the Filter method is what you can
provide to the REST endpoint directly, without the URL encoding. Thus, using
the new Microsoft Graph SDK you are free to make whatever query you like,
but you will have to know the OData querying syntax because there are no high-
level tools for building queries.
Another useful method you can use is OrderBy, which sorts the results of a
query based on one or more property values. However, at the time of this writing
you cannot combine the OrderBy method with the Filter method. This is
highlighted in the REST API online documentation (see the “$orderby” section
here: http://graph.microsoft.io/en-us/docs/overview/query_parameters).
Moreover, depending on the target resource type, you could have to provide the
properties’ names with the same case that is used in the $metada document. For
example, if you want to sort the users by DisplayName, you cannot use the
DisplayName clause—you will have to use the displayName clause, with a
lowercase trailing letter, according to its definition in the $metadata document.
This is a requirement of the REST API, depending on the target type of
resources, and not of the SDK because internally the SDK will build the REST
request for the API, and the API refuses the query that has the wrong casing of
properties, providing an exception message like the following:
Click here to view code image
Given expression for $orderby Microsoft.Data.OData.Query.SemanticAst.
SingleValueOpenPropertyAccessNode is not supported
In Listing 8-7, you can see a query with sorting based on DisplayName
ascending.
LISTING 8-7 Code excerpt to query the users sorted by DisplayName ascending
Click here to view code image
If the target collection supports the capability, you can sort the resources in
ascending (default) or descending order by providing the desc or asc keywords
after the name of the sorting fields. You can even combine multiple fields to
achieve multilevel hierarchical sorting.
Furthermore, you can partition results of queries by leveraging the Top and
Skip methods. These are the counterparts of the $top and $skip query string
parameters of the OData protocol. In Listing 8-8, you can see a query that
retrieves the top 5 email messages in the inbox folder of the current user’s
mailbox, skipping the first 10 messages.
Code excerpt to query the top 5 messages in inbox, skipping the first
LISTING 8-8
10 messages
Click here to view code image
Again, depending on the target type of resource, some of these methods could
be unsupported. For example, at the time of this writing, if you invoke the Skip
method against the collection of users, you will get back an exception like the
following:
Click here to view code image
The last method related to the querying capabilities of the Microsoft Graph
SDK is the Expand method, which allows you to include in the results resources
related to the main resource that is subject to query. For example, in Listing 8-9
you can see a code excerpt to retrieve a folder in a drive, together with (Expand)
the children files and folders of that folder, within a unique request.
LISTING 8-9 Code excerpt to retrieve a folder and its children items
Click here to view code image
@odata.nextLink=https://graph.microsoft.com/v1.0/users?
$skiptoken=X%2744537074020000203A666E3138
372E6C6E313837407368617265706F696E742D63616D702E636F6D29557365725F643932663131636
3136662D386363302D61323639646265396632313600203A666E3237372E6C6E32373740736861726
3136662D386363302D61323639646265396632313600203A666E3237372E6C6E32373740736861726
616D702E636F6D29557365725F66386162643338662D653836332D343639302D616637382D6439306
34B900000000000000000000%27
The $skiptoken query string argument instructs the REST service to provide
the next page of the current page. Internally, the $skiptoken query string is an
opaque token that identifies a starting point in the collection of resources
identified by the current REST query. Basically, it defines the first record of the
next page to query. If the query orders the resulting resources by using the
$orderby query string argument, the results will be ordered according to the
sorting rule. Otherwise—and by default—the results will be ordered by entity
key (the ID).
When you query collections of resources by using the Microsoft Graph SDK,
all the internals of paging and of the $skiptoken argument are hidden from you
under the cover of the ICollectionPage<T> interface and a set of collection
interfaces autogenerated by VIPR. For example, in Listing 8-10 you can see the
definition of the ICollectionPage<T> interface.
/// <summary>
/// Interface for collection pages.
/// </summary>
/// <typeparam name="T">The type of the collection.</typeparam>
public interface ICollectionPage<T> : IList<T> {
/// <summary>
/// The current page of the collection.
/// </summary>
IList<T> CurrentPage { get; }
/// <summary>
/// Gets the next page <see
cref="IGraphServiceUsersCollectionRequest"/> instance.
/// </summary>
IGraphServiceUsersCollectionRequest NextPageRequest { get; }
/// <summary>
/// Initializes the NextPageRequest property.
/// </summary>
void InitializeNextPageRequest(IBaseClient client, string
nextPageLinkString);
}
LISTING 8-12 Code sample of how to do paging of users by using the Microsoft
Graph SDK
Click here to view code image
Int32 pageCount = 0;
while (true) {
pageCount++;
Console.WriteLine("Page: {0}", pageCount);
if (pagedUsers.NextPageRequest != null) {
pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
}
else {
break;
}
}
You can implement the paging logic however you like; the key point is to
request pages of resources as long as the NextPageRequest property is not
NULL.
Managing resources
In the previous chapters, you saw that you can add, update, and delete the
resources provided by the Microsoft Graph API. In this section, you will learn
how to accomplish these tasks by using the Microsoft Graph SDK.
Code excerpt that creates a new Office 365 Group by using the
LISTING 8-13
Microsoft Graph SDK
Click here to view code image
Notice that you have to configure the group completely in code. For example,
to create an Office 365 Group, you have to specify the value for the GroupTypes
property, selecting the Unified value.
Furthermore, when you create an Office 365 Group, usually you also would
like to set up a photo for the group and to assign owners and members to the
group. The group’s photo can be configured by providing a System.IO.Stream
object to the PutAsync method of the Photo.Content.Request of the target group.
In Listing 8-14, you can see the corresponding code.
Code excerpt that uploads a photo for an Office 365 Group by using
LISTING 8-14
the Microsoft Graph SDK
Click here to view code image
If you are going to add a photo to a new group, you probably will need to
provide a retry logic because usually it will take a few seconds after the creation
for the group to be ready and available for uploading the photo.
The owners and members are more interesting. If you think about the Graph
API from a REST perspective, the collections of members and owners of a group
are accessible through the $ref keyword, which represents a reference to the
underlying collection objects. Because the Graph SDK domain model is
autogenerated from the $metadata definition of resources, the $ref endpoints are
also made available through an autogenerated References property. That
property gives us a reference to the underlying collection of items.
For example, if you reference a group item by using the Groups property of an
IGraphServiceClient implementation, you will see that the Owners property will
be of type IGroupOwnersCollectionReferencesRequest, where the interface
name implies that there are also references included in the interface definition
and that you can invoke the Request method against them. In general, in the
autogenerated domain model there are a bunch of interfaces with name
I*CollectionReferencesRequest, where the asterisk refers to the collection name.
If you get a reference to the collections of owners or members of a group, you
can then invoke the AddAsync method of those collections and add new user
objects of type DirectoryObject to them. In Listing 8-15, you can see a code
excerpt that adds one owner and a couple of members to a just-created Office
365 Group.
LISTING 8-15Code excerpt adding an owner and members to an Office 365 Group
by using the Microsoft Graph SDK
Click here to view code image
// Add owners to the group
var ownerQuery = await graphClient.Users
.Request()
.Filter("userPrincipalName eq 'paolo.pialorsi@sharepoint-
camp.com'")
.GetAsync();
if (owner != null) {
try {
await graphClient.Groups[addedGroup.Id].Owners
.References.Request().AddAsync(owner);
}
catch (ServiceException ex) {
Console.WriteLine(ex.Message);
}
}
if (memberOne != null) {
try {
await graphClient.Groups[addedGroup.Id].Members
.References.Request().AddAsync(memberOne);
}
catch (ServiceException ex) {
Console.WriteLine(ex.Message);
}
}
if (memberTwo != null) {
try {
await graphClient.Groups[addedGroup.Id].Members
.References.Request().AddAsync(memberTwo);
}
catch (ServiceException ex) {
Console.WriteLine(ex.Message);
}
}
Consider that if you try to add the same user object to the same target role
(member or owner) more than once, you will get back an exception with an error
message like the following, which is provided in the case of a duplicate owner:
Click here to view code image
One or more added object references already exist for the following
modified properties:
'owners'.
Notice that if you access the Owners property of a Group instance retrieved
using the GetAsync method, you will not have access to the References property.
This is because the single instance of type Group does not expose the Owners as
an implementation of IGroupOwnersCollectionReferencesRequest, but just as an
implementation of the IGroupOwnersCollectionWithReferencesPage interface.
This same is true for the Members property of a Group instance.
Last, consider that to add a new Office 365 Group, you will need to have the
permission scope of type Group.ReadWrite.All, but if you also want to manage
owners and members, you most likely will also need permissions to access the
Azure AD tenant. For example, the permission scope
Directory.AccessAsUser.All could be enough as long as you don’t want to act
with elevated privileges—for example, using an app-only access token.
Nevertheless, keep in mind that at the time of this writing, you cannot create an
Office 365 Group with an app-only access token.
Updating a resource
Now that you have seen how to add resources to collections, it is time to learn
how to update an existing resource. The Microsoft Graph SDK provides the
UpdateAsync method for this purpose.
The UpdateAsync method is available through the I*Request interface for
objects that are updatable. For example, in Listing 8-16 you can see a code
excerpt to update the DisplayName and Description properties of an Office 365
group.
LISTING 8-16 Code excerpt to update an Office 365 Group by using the Microsoft
Graph SDK
Click here to view code image
When you update a resource, particularly an Office 365 Group, you have to
consider that there could be some delay due to data caching on the service side
and because of asynchronous update operations that are working in the
background. Thus, there is no guarantee that you will see your updates in real
time.
Deleting a resource
Deleting a resource is another straightforward use case. You just need to get a
reference to the resource object you want to delete and then invoke the
DeleteAsync method on it.
In Listing 8-17, you can see a code excerpt to delete an existing Office 365
Group.
LISTING 8-17 Code excerpt to delete an Office 365 Group by using the Microsoft
Graph SDK
Click here to view code image
await graphClient.Groups[groupId]
.Request()
.DeleteAsync();
As you can see, the DeleteAsync method is applied directly to the target
resource. In case of success, it does not provide any kind of result. In case of
failure, you will have to catch the proper exception type.
LISTING 8-18 The definition of the ServiceException type of the Microsoft Graph
SDK
Click here to view code image
currentError = currentError.InnerError;
}
return false;
}
return null;
}
}
The main information provided by the ServiceException is the Error property,
which provides detailed information about the service-side exception. The main
information that you can find within the Error property, which is of type
Microsoft.Graph.Error, is illustrated in Table 8.1:
try {
}
catch (ServiceException ex) {
Console.WriteLine(ex.Error);
if (ex.IsMatch(GraphErrorCode.AccessDenied.ToString())) {
Console.WriteLine("Access Denied! Fix permission scopes
...");
}
else if
(ex.IsMatch(GraphErrorCode.ThrottledRequest.ToString())) {
Console.WriteLine("Please retry ...");
}
}
As you can see, the exception handling is clean and clear. It is also interesting
to see that there is a specific error code for throttled requests, which is useful
when you are consuming a cloud service like the Microsoft Graph API and you
need to implement a retry logic.
Real-life examples
It is now time to play a little bit with the Microsoft Graph SDK, looking at how
to accomplish some of the most common and useful tasks in real business
scenarios.
Sending an email
Sending an email on behalf of the current user is one of the most common use
cases. As you saw in Chapter 3, “Microsoft Graph API reference,” and Chapter
5, “Mail, calendar, and contact services,” there is an operation called
microsoft.graph.sendMail available under the current user resource, which is
called Me.
Using the Microsoft Graph SDK, you can invoke the SendMail method, which
is available for the Me property of the GraphServiceClient object. In Listing 8-
20, you can see a code excerpt that uses the SendMail method.
LISTING 8-20 Sample of how to send an email by using the Microsoft Graph SDK
Click here to view code image
try {
await graphClient.Me.SendMail(new Message {
Subject = "Sent from Graph SDK",
Body = new ItemBody {
Content = "<h1>Hello from Graph SDK!</h1>",
ContentType = BodyType.Html,
},
ToRecipients = new Recipient[] {
new Recipient {
EmailAddress = new EmailAddress {
Address = recipientMail,
Name = recipientName,
}
}
},
Importance = Importance.High,
},
true).Request().PostAsync();
}
catch (ServiceException ex) {
Console.WriteLine(ex.Error);
}
Notice that the SendMail method does not send the email message directly,
but it returns a request builder of type IUserSendMailRequestBuilder. Thus, you
have to invoke the Request method to retrieve the request object, and after that
you have to POST over HTTP the request invoking the PostAsync method.
The main input of the SendMail method is an object of type
Microsoft.Graph.Message, which is a domain model object autogenerated by
VIPR and corresponding to the resource defined in the $metadata document.
LISTING 8-21Code excerpt to retrieve all the Office 365 Groups page by page by
using the Microsoft Graph SDK
Click here to view code image
Int32 pageCount = 0;
while (true) {
pageCount++;
Console.WriteLine("Page: {0}", pageCount);
foreach (var group in pagedGroups) {
Console.WriteLine("{0} - {1} - {2}", group.Id,
group.DisplayName, group.Description);
}
if (pagedGroups.NextPageRequest != null) {
pagedGroups = await
pagedGroups.NextPageRequest.GetAsync();
}
else {
break;
}
}
The key point of the sample in Listing 8-21 is the argument provided to the
Filter method. You can recognize the filtering criteria that we used in Chapter 3,
in the section “Working with Office 365 Groups,” within Listing 3-42.
LISTING 8-22 Code excerpt to retrieve all the files in the root folder of an Office
365 Group
Click here to view code image
As usual, you have to build the request and then execute it asynchronously. If
you are looking for a specific file by name or content, you can use the search
capabilities of OneDrive for Business and SharePoint. In Listing 8-23, you can
see how to search for a file.
LISTING 8-23 Code excerpt to search for files in the root folder of an Office 365
Group
Click here to view code image
LISTING 8-24 Code excerpt to retrieve all the conversations of an Office 365
Group
Click here to view code image
One last common use case is the retrieval of one or more events from the
group’s calendar. There is an Events collection property for the group, and you
can make a request for it, like Listing 8-25 does.
LISTING 8-25 Code excerpt to retrieve all the events of an Office 365 Group
Click here to view code image
All of these collections are also available for management of their resources.
For example, if you want to add a new topic to a group conversation, you can
use a syntax like the one illustrated in Listing 8-26.
LISTING 8-26 Code excerpt to add a new conversation thread in an Office 365
Group
Click here to view code image
As you can see from reading the code samples, you need to create an instance
of type ConversationThread, providing the mandatory Topic property and one or
more instances of Post resources.
In Listing 8-27, you can see how to add a new event in the group’s calendar.
LISTING 8-27 Code excerpt to add a new event in the calendar of an Office 365
Group
Click here to view code image
As you can see, any user resource—including the current user (Me)—has a
Photo property that provides a Content property. You just need to invoke the
GetAsync method against the Request method of the Content. The result of the
GetAsync method is an object of type System.IO.Stream that you can consume
freely. The code sample in Listing 8-28 saves the image on the local file system.
Updating the current user’s photo is even more interesting, as illustrated in
Listing 8-29.
if (manager != null) {
Console.WriteLine("Your manager is: {0}",
manager.DisplayName);
}
Notice that the Manager property of the user resource gives you back only the
ID of the target manager, not the whole directory object. Thus, if you want to
retrieve any property of the manager, you will have to make one more request,
targeting the collection of Users of the GraphServiceClient object and providing
the ID as the key to access the proper user resource. Moreover, the Manager
property of the user object provides a Reference property, which can be retrieved
through the Request method. The Manager.Reference resource provides a
DeleteAsync method that can be used to unlink the current user from his
manager.
Furthermore, the direct reports can be retrieved by querying the DirectReports
collection property of the target user resource. Because there can be many direct
reports, the collection supports paging. In Listing 8-31, you can see a sample of
how to retrieve a list of direct reports for the current user, without paging.
Code excerpt to retrieve the current user’s direct reports by using the
LISTING 8-31
Microsoft Graph SDK
Click here to view code image
if (reports.Count > 0) {
Console.WriteLine("Here are your direct reports:");
foreach (var r in reports) {
var report = await graphClient.Users[r.Id].Request()
.Select("DisplayName").GetAsync();
Console.WriteLine(report.DisplayName);
}
}
else {
Console.WriteLine("You don't have direct reports!");
}
The sample is straightforward and just browses the direct report resources.
Like the manager resource, the direct reports provide only the ID of the target
directory object. Thus, you will have to query explicitly for every direct report
resource that you want to use, if any.
To add a direct report to a user, you can invoke the AddAsync method against
the Request provided by the References property of the DirectReports user’s
property.
Based on what you just saw, there are methods to add direct reports to a user
and to delete the manager reference from a user. However, there are no direct
methods to add or change the manager of a user or to delete the direct reports
from a user. Nevertheless, you can achieve all of the possible results by
leveraging the available methods in the right sequence.
If you want to add or change a manager of a user, you can just add that user as
a direct report for her manager. If you want to remove a direct report of a user,
you can delete his manager’s reference. Last, if you want to change the direct
reports of a user, you can assign them as direct reports of a new manager or
delete their manager’s reference.
delete their manager’s reference.
LISTING 8-32Code excerpt to create a new file in current user’s OneDrive for
Business root folder by using the Microsoft Graph SDK
Click here to view code image
newFile = await
graphClient.Me.Drive.Root.Children.Request().AddAsync(newFile);
Notice that the file has to be provided as a DriveItem instance and that at a
minimum you have to configure the File property and the Name property. As
you saw in Chapter 7, creating a folder or a specific file type like a picture or a
video will use the same approach, setting the proper properties on the DriveItem
instance.
Once you have created the DriveItem instance, you can add the item to the
collection of children of the target folder, which is the root folder in Listing 8-
32.
So far, you have only created a new item in the target folder. Now you need to
upload the real content of the file, which is also useful if you need to update the
content of an existing file. Every drive item resource provides the Content
property, which can be used to update the file content by requesting the content
resource and invoking the PutAsync method. The PutAsync method accepts an
argument of type System.IO.Stream, which represents the binary content of the
file. In Listing 8-33, you can see how to upload or update the content of a target
file.
LISTING 8-33Code excerpt to upload or update the content of a file in OneDrive
for Business by using the Microsoft Graph SDK
Click here to view code image
Notice that the result of the PutAsync method will be the just-uploaded or
updated DriveItem.
Int32 pageCount = 0;
while (true) {
pageCount++;
Console.WriteLine("Page: {0}", pageCount);
foreach (var result in searchResults) {
Console.WriteLine("{0} - {1}\n{2}\n", result.Id,
result.Name, result.WebUrl);
}
if (searchResults.NextPageRequest != null) {
searchResults = await
searchResults.NextPageRequest.GetAsync()
}
else {
break;
}
}
Because the number of results is unpredictable and could be very large, the
code sample leverages the paging capabilities of the Microsoft Graph SDK to
browse all the results.
Moreover, consider that the search query results are provided through an
instance type that implements the IDriveItemSearchCollectionPage interface.
Thus, every result will be an object of type Microsoft.Graph.DriveItem, and you
can apply querying methods like Select, Filter, OrderBy, and so on to the result
so that you can partition, order, and project the results based on your needs.
Because by requesting the Content property you will get back an object of
type System.IO.Stream, if you also want to retrieve metadata information like the
file name or its content type you will have to make a separate request selecting
what you need against the target resource.
In Listing 8-35, we just download the file and save it onto the file system,
retrieving the name of the file from the Graph API.
Summary
In this chapter, you learned about the new Microsoft Graph SDK for .NET,
which is an object model Microsoft provides to make it easy to consume the
Microsoft Graph API without having to dig into the HTTP and REST protocol
details.
Throughout the chapter, you examined the architecture of the SDK and saw
how to authenticate by using either ADAL for Azure AD or MSAL for the new
v2 authentication endpoint. Moreover, you learned about the request model and
the querying model of the Graph SDK.
You saw how to do paging of collections, how to manage items of collections,
and how to do exceptions handling properly.
Last, you had a preview of some of the most common use cases, which can be
useful whenever you need to create real business-level solutions using the
Microsoft Graph API and the Microsoft Graph SDK for .NET.
Chapter 9. SharePoint REST API
Since Microsoft SharePoint 2013, SharePoint has included a rich set of REST
(Representational State Transfer) API, which is useful for creating SharePoint
Add-ins, SharePoint workflows, and other software solutions. The SharePoint
REST API gives any platform access to many key objects, properties, and
methods that are also available via the client-side object model (CSOM). The
new API provides a rich set of REST URIs that you can access via HTTP and
XML/JSON (JavaScript Object Notation) for consuming nearly every capability
of the CSOM. All you need is a third-party technology capable of consuming
REST services. In this chapter, you will learn about the architecture of this
REST API and how to manage the most common tasks for everyday
programming needs.
More Information
stands for Open Data Protocol, and you can read more about it at
http://www.odata.org/.
You can access the REST API at the relative URL _api/ of any SharePoint
site. For example, to access the API targeting the root site collection of a target
web application, you can open your browser and navigate to a URL such as the
following:
https://<your-tenant>.sharepoint.com_apisite
where <your-tenant>.sharepoint.com is the host name of a sample web
application hosted in Microsoft Office 365. However, the SharePoint REST API
is also available on-premises. The previous URL is just an alias of the real URL
of the WCF service under the cover of the REST API, which is:
https://<your-tenant>.sharepoint.com/_vti_bin/client.svc/site
This is just an additional RESTful endpoint that publishes the capabilities of
the classic CSOM through the OData protocol. By browsing to such a URL, you
will see that the result is an XML representation—based on the ATOM protocol
—of information about the current site collection. (When using Internet
Explorer, be sure to disable the feed-reading view in the browser’s content
properties.) At the beginning of the ATOM response, there is a list of links
targeting many additional URLs for accessing information and API related to the
current site collection. At the end of the response, there are some properties
specific to the current site collection.
Here are some other commonly used URLs of API, which are useful while
developing on SharePoint:
https://<your-tenant>.sharepoint.com_apiweb Use to access the
information about the target website.
https://<your-tenant>.sharepoint.com_apiweb/lists Use to access the
collection of lists in the target website.
https://<your-tenant>.sharepoint.com_apiweb/lists/GetByTitle(‘Title of
the List’) Use to access the information of a specific list instance, selected
by title.
https://<your-tenant>.sharepoint.com_apisearch Use to access the search
query engine.
As you can see, the root of any relative endpoint is the _api/ trailer, which can
be followed by many API targets (as the following section will illustrate) and
corresponds to the most common artifacts of SharePoint. As with many REST
services, you can communicate with this REST API not only by using the
browser, invoking URLs with the HTTP GET method, but also by using a client
capable of communicating over HTTP and parsing ATOM or JSON responses.
Depending on the HTTP Accept header provided within the request, the REST
service will provide ATOM (Accept: application/atom+xml) or JSON (Accept:
application/json;odata=verbose) answers. By default, REST service responses
are presented by using the ATOM protocol according to the OData specification.
Depending on the HTTP method and headers (X-Http-Method) you use, you
can take advantage of various capabilities of the API, such as a complete
CRUDQ (create, read, update, delete, and query) set of methods. The available
HTTP methods and headers are as follows:
GET These requests typically represent read operations that apply to
objects, properties, or methods and return information.
POST Without any additional X-Http-Method header, this method is used
for creation operations. For example, you can use POST to post a file to a
library, post an item to a list, or post a new list definition for creation in a
target website. While invoking POST operations against a target object,
any property that is not required and is not specified in the HTTP
invocation will be set to its default value. If you provide a value for a read-
only property, you will get an exception.
PUT, PATCH, and MERGE These requests are used for update
operations. You can use PUT to update an object. While invoking PUT
operations, you should specify all writable properties. If any property is
missing, the operation could fail or could set the missing properties back to
their default values. The PATCH and MERGE operations are based on the
POST method, with the addition of an X-Http-Method header with a value
of PATCH or MERGE. They are equivalent, and you should always use the
former because the latter is provided for backward compatibility only. Like
PUT, PATCH and MERGE handle update operations. The big difference is
that with PATCH and MERGE, any writeable property that is not specified
will retain its current value.
DELETE These requests are for deleting an item and can be implemented
with POST plus the additional X-Http-Method header with a value of
DELETE. If you invoke this operation against recyclable objects,
SharePoint will move them to the Recycle Bin.
Listing 9-1 demonstrates how to use the new REST API from within
PowerShell. The sample is intentionally written using a PowerShell script to
demonstrate that the REST API is available to any platform and any technology
landscape. The code reads the title of a list instance in a target website.
Moreover, this sample leverages a useful and powerful set of PowerShell
extensions for Microsoft SharePoint Online and Office 365, which are available
through the Office 365 Developer Patterns & Practices (PnP) community
project.1
1 For further details about the Office 365 Developer Patterns & Practices community project, you can
browse to the following URL: http://aka.ms/OfficeDevPnP. The PowerShell extensions, which have
been created by Erwin van Hunen (https://twitter.com/erwinvanhunen), can be installed from the
following URL: https://github.com/OfficeDev/PnPPowerShell/tree/master/Binaries. They are available
in three flavors: SharePointPnPPowerShell2013.msi targets SharePoint 2013 on-premises,
SharePointPnPPowerShell2016.msi targets SharePoint 2016 on-premises, and
SharePointPnPPowerShellOnline.msi targets SharePoint Online.
LISTING 9-1A sample PowerShell script for reading the title of a list instance in a
target website using the REST API
Click here to view code image
# Connect to SharePoint Online
$targetSite = "https://<your-tenant>.sharepoint.comsites<Site-
Name>/"
$targetSiteUri = [System.Uri]$targetSite
Connect-SPOnline $targetSite
FIGURE 9-1 The schema of the URL of any REST API published by SharePoint
The protocol moniker can be http or https, depending on the web application
configuration. If you target Microsoft SharePoint Online, it will be https. The
{hostname} argument is the host name—which will include the fully qualified
domain name—of the target web application. For Microsoft SharePoint Online,
it will be <your-tenant>.sharepoint.com. The subsequent {site} is the target site
collection and is optional because you could target the root site collection.
Following the _api trailer is a {namespace} argument that corresponds to one of
the target families of API. Table 9-1 lists some of the main available
namespaces. The URL ends with a reference to an {object}, a specific
{property}, an {indexer}, or a {method} call. Indexers will be followed by a
numeric {index} argument, while method calls could be followed by {parameter}
arguments. For some operations, the arguments can be provided as a JSON
object in the HTTP POST request body.
TABLE 9-1 The main namespaces available in URLs of the REST API
The REST API offers about 2,000 classes and more than 6,000 members,
which are available throughout the hierarchy of objects of the CSOM using the
preceding namespaces as root objects. The first three namespaces are easy to
manage and understand because you just need to reference the corresponding
CSOM types and compose the request URLs. For example, the Site class of the
Microsoft.SharePoint.Client namespace offers a property with name Owner and
type User. By using the REST API, you can invoke the GET verb to retrieve the
following URL:
https://<your-
tenant>.sharepoint.com_apisite/owner
To invoke the GetWebTemplates method, which accepts the culture
parameter, you can invoke the following URL:
https://<your-
tenant>.sharepoint.com_apisite/GetWebTemplates(1033)
The value 1033 provided is the en-US culture. Consult the CSOM online
reference (http://msdn.microsoft.com/en-us/library/ee544361.aspx) to see all the
available properties, methods, and members.
Notice that for security reasons, all the operations that modify data will
require a security form digest with a name of X-RequestDigest in the HTTP
request headers. To retrieve the value needed for this header, you have a couple
of options:
Working in JavaScript, inside a webpage directly hosted in SharePoint or a
SharePoint-hosted add-in, you can retrieve the value of the digest from a
hidden INPUT field with an ID value of __REQUESTDIGEST. For
example, using jQuery, you can reference the field with the following
syntax: $(“# __REQUESTDIGEST”).val().
Working in any other context, you can invoke (using the POST method)
the ContextInfo namespace and retrieve the form digest value from the
ATOM or JSON response. By default, the form digest retrieved through
this method will expire in 1,800 seconds.
Listing 9-2 shows the JSON output of the ContextInfo method invocation. The
form digest value is highlighted in bold.
Listing 9-3 provides a code excerpt of a PowerShell script that invokes the
EnsureUser method of a target website, providing a value for the form digest
HTTP header, which is called X-RequestDigest, after extracting that value from
the ContextInfo method.
LISTING 9-3A PowerShell code excerpt for invoking the EnsureUser method of a
target website via the REST API
Click here to view code image
$global:webSession = New-Object
Microsoft.PowerShell.Commands.WebRequestSession
function Initialize-SPOSecuritySession {
param ($targetSite)
function Initialize-SPODigestValue {
param ($targetSite)
$digestValue =
$jsonContextInfo.d.GetContextWebInformation.FormDigestValue
$global:webSession.Headers.Add("X-RequestDigest",
$digestValue)
}
$targetSite = "https://<your-tenant>.sharepoint.comsites<Site-
Name>/"
Initialize-SPOSecuritySession -targetSite $targetSite
Initialize-SPODigestValue -targetSite $targetSite
So far, you have seen that all the REST requests were decorated with the
Accept HTTP header and configured with a value of
application/json;odata=verbose. This header instructs the OData engine to give
back a JSON (application/json) response with a rich set of information
(metadata) about the type of the result. This capability could be useful if you
query unknown data structures and want to retrieve not only the data but also the
data type because you want to update the data back to SharePoint. However,
often you consume data sets by themselves, in read-only mode and with a fixed
set of fields. Having all the metadata information of what is coming back from
the server in every response could be noisy and expensive. That is why the
international community defined the so-called JSON Light protocol and why
Office 365 supports the JSON Light format since August 2014. JSON Light is an
open standard that allows developers to provide in the HTTP headers of the
REST requests how much metadata has to be returned. The supported values for
the Accept HTTP header are:
application/json;odata=verbose
application/json;odata=minimalmetadata
application/json;odata=nometadata
The first option is the most common and well known, and it retrieves all the
metadata available from the HTTP server. In the following excerpt, you can see
a JSON object retrieved with the verbose option by using a customized OData
query that retrieves the ID and the Title of a single document from a library with
the EMail field of the document’s Author. The metadata information is
highlighted in bold.
Click here to view code image
{"d":
{"__metadata":
{"id":"00af8a37-6fbd-454b-bd02-8fe1b74f8c41",
"uri":"https://<your-tenant>.sharepoint.comsites<Site-
Name>_apiWeb/...",
"etag":"\"2\"",
"type":"SP.Data.Sample_x0020_LibraryItem"},
"Author":
{"__metadata":
{"id":"8d8bbb4d-aa28-4a9b-be38-30cb8667fb2e",
"type":"SP.Data.UserInfoItem"},
"EMail":"<author>@<your-tenant>.onmicrosoft.com"},
"Id":1,
"Title":"SampleFile-03",
"ID":1}
}
The second option—which is now the default in Office 365 if you don’t
specify the odata attribute in the Accept request header—instructs the engine to
release minimal metadata information. In the following excerpt, you can see the
JSON result of the same query as before, but with the minimalmetadata option.
Click here to view code image
{"odata.metadata":"https://<your-tenant>.sharepoint.comsites<Site-
Name>_api$metadata#...",
"odata.type":"SP.Data.Sample_x0020_LibraryItem",
"odata.id":"50be68eb-be36-46b0-a3ef-6a8452a134cd",
"odata.etag":"\"2\"",
"odata.editLink":"Web/Lists(guid'70473747-fda3-4a81-a16a-
3231f2876aa7')/Items(1)",
"[email protected]":"Web/Lists(guid'70473747-fda3-4a81-
a16a-3231f2876aa7')/
Items(1)/Author",
"Author":
{"odata.type":"SP.Data.UserInfoItem",
"odata.id":"4cdc633c-8f3a-41e5-87ec-8117256b409b",
"EMail":"<author>@<your-tenant>.onmicrosoft.com"},
"Id":1,
"Title":"SampleFile-03",
"ID":1
}
{"Author":
{"EMail":"<author>@<your-tenant>.onmicrosoft.com"},
"Id":1,
"Title":"SampleFile-03",
"ID":1}
Querying data
Another useful capability of the REST API is support for OData querying. Every
Another useful capability of the REST API is support for OData querying. Every
time you invoke an operation that returns a collection of entities, you can also
provide an OData-compliant set of query string parameters for sorting, filtering,
paging, and projecting that collection. For example, imagine querying the list of
items available in a document library. The URL would be:
https://<your-
tenant>.sharepoint.com_apiweb/lists/GetByTitle(‘Documents’)/Items
If you are interested in the list of files in the root folder of the library, the
corresponding URL is:
https://<your-
tenant>.sharepoint.com_apiweb/lists/GetByTitle(‘Documents’)/RootFolder/Files
According to the OData specification, you can append the following querying
parameters to the URL:
$filter Defines partitioning criteria on the current entity set. For example,
you can provide the query string argument
$filter=substringof(‘Budget’,Name)%20eq%20true to retrieve documents
with Budget in their file name.
$select Projects only a subset of properties (fields) of the entities in the
current entity set. For example, you can provide a value of
$select=Name,Author to retrieve only the file name and the author of every
file in the entity set.
$orderby Sorts data returned by the query. You can provide query string
arguments with a syntax like
$sort=TimeLastModified%20desc,Name%20asc to sort files descending by
TimeLastModified and ascending by Name.
$top Selects the first N items of the current entity set. Use the syntax
$top=5 to retrieve only the first five entities from the entity set.
$skip Skips the first N items of the current entity set. Use the syntax
$skip=10 to skip the first 10 entities of the entity set.
$expand Automatically and implicitly resolves and expands a relationship
between an entity in the current entity set and another related entity. For
example, you can use the syntax $expand=Author to retrieve the author of
a file.
The arguments provided to an OData query must be URL encoded because
they are passed to the query engine via REST, through the URL of the service.
Space characters must be converted into %20, for example, and any other non-
alphanumeric characters must be converted into their corresponding encoded
values.
In the previous examples, you saw a quick preview of the available functions
and operators for filtering entities with OData. Table 9-2 provides the full list of
the available logical operations defined in the OData core specification. You can
read the official core documentation of OData at http://docs.oasis-
open.org/odata/odata/v4.0/odata-v4.0-part1-protocol.html. The operators in
bold are supported by the SharePoint REST API.
TABLE 9-2 The logical operations available in the OData core specification
There are also some arithmetic operators, which are listed in Table 9-3.
TABLE 9-3 The arithmetic operators available in the OData core specification
None of the arithmetic operators defined in the OData core specification are
supported by the SharePoint REST API. While defining a query, you can
compose operators using parentheses ( ) to group elements and define
precedence. For example, you can write the following:
Click here to view code image
/Products?$filter=(Price sub 5) gt 10
Last, in queries for partitioning data, you can also use functions for strings,
dates, math, and types. Table 9-4 provides the full list of functions available in
the OData specification. Again, the operators highlighted in bold are those
supported by the SharePoint REST API.
TABLE 9-4 The functions available in the OData core specification for querying
entities
Based on all the information provided in previous paragraphs, you should now
be able to understand the following query:
Click here to view code image
https://<your-
tenant>.sharepoint.com_apiweb/lists/GetByTitle('Documents')/
RootFolder/Files?
$expoand=Author&$select=Name,Author/EMail,TimeLastModified&$sort=TimeLastModifi
ed%20desc,Name&$skip=20&$top=10&$filter=substringof('Chapter',Name)%20eq%20true
You can disassemble and decode the query string parameters with the
information provided in Table 9-5.
TABLE 9-5 The sample query string parameters explained
More Information
For quick testing and definition of OData queries, you can use
LINQPad, which is a smart tool available at the following URL:
http://www.linqpad.net.
If you are working with the .NET Framework, the OData client library creates
such queries for you, allowing you to write LINQ queries on the consumer side.
If you are working with any other development technology, you need to
understand and write this kind of query.
Managing data
Creating, updating, deleting, and otherwise managing entities by using OData
and the REST API is simple if you remember a few rules. First, as you have
seen, you must provide the X-RequestDigest HTTP header whenever you want to
change some data. Second, when managing lists and list items, you need to avoid
concurrency conflicts by specifying an additional HTTP header with the name
IF-MATCH, which assumes a value of ETag. To avoid concurrency conflicts,
read the ETag value by retrieving the target entity (list or list item) with a GET
method. The ETag value will be included in the response HTTP headers and in
the response content, regardless of whether it is formatted in ATOM or JSON.
Listing 9-4 includes a sample set of HTTP response headers returned by
SharePoint Online while selecting a list item via the REST API. The ETag
header is highlighted in bold, together with the OData version supported (3.0)
and the SharePoint build version (16.0.0.4208).
Note
The IF-MATCH header applies only to lists and list items and can
assume a value of * for situations in which you do not care about
concurrency and want to force your action.
LISTING 9-4 A sample set of HTTP response headers returned while querying a
list item via the REST API
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
ContentType: application/json;odata=verbose;charset=utf-8
Expires: Sun, 21 Jun 2015 22:48:18 GMT
LastModified: Mon, 06 Jul 2015 22:48:18 GMT
ETag: "2"
Server: Microsoft-IIS/8.5
X-SharePointHealthScore: 0
X-SP-SERVERSTATE: ReadOnly=0
DATASERVICEVERSION: 3.0
SPClientServiceRequestDuration: 109
X-AspNet-Version: 4.0.30319
SPRequestGuid: b17b179d-f0b2-2000-3929-0362939a61f8
request-id: b17b179d-f0b2-2000-3929-0362939a61f8
X-FRAME-OPTIONS: SAMEORIGIN
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 16.0.0.4208
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI
TELo OUR SAMo CNT COM INT
NAV ONL PHY PRE PUR UNI"
Date: Mon, 06 Jul 2015 22:48:19 GMT
Content-Length: 2713
To better understand how to manage data via the REST API, switch to some
JavaScript code samples, which likely are similar to what you will need while
writing SharePoint Add-ins and SharePoint Framework solutions. For example,
the code excerpt of the JavaScript function in Listing 9-5 updates the title of an
item by using the REST API and provides a value for the ETag parameter.
LISTING 9-5 A sample code excerpt to update the title of a list item by using
JavaScript and the REST API
Click here to view code image
var hostweburl;
var appweburl;
var eTag;
// This code runs when the DOM is ready and creates a context
object
// which is needed to use the SharePoint object model
$(document).ready(function () {
//Get the URI decoded URLs.
hostweburl =
decodeURIComponent(getQueryStringParameter("SPHostUrl"));
appweburl =
decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
function execCrossDomainRequest() {
var contextInfoUri = appweburl + "_apicontextinfo";
var itemUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/GetByTitle('Documents')/Items(1)?
@target='" +
hostweburl + "'";
Executing as soon as the DOM document is ready, Listing 9-5 first configures
both the app web URL and the host web URL. Then, it configures a scripting file
(SP.RequestExecutor.js), which will be discussed in the section “Cross-domain
calls” that follows. After startup, the sample code requests the ContextInfo via a
POST request to extract a valid value for the form digest. If your code runs
inside a SharePoint-hosted app, you can read the form digest value from the
current page (a hidden field with name __REQUESTDIGEST). After retrieving
the form digest, the sample gets the item to update to access its ETag value. Last,
the code runs a POST request against the target item URI, providing the JSON
serialization of the changes to apply, the form digest, and the ETag.
In the “Common REST API usage” section later in this chapter, you will see
many samples based on the concepts demonstrated here. For now, notice that the
JavaScript code for invoking the REST API uses an object of type
SP.RequestExecutor to invoke the service endpoints instead of a classic
jQuery.Ajax method. In the next section, “Cross-domain calls,” you will learn
how it works.
One last thing to understand about data management is how the REST API
behaves in case of a concurrency conflict. Remember, providing the ETag value
enables you to identify and manage conflicts; it does not prevent you from
experiencing them unless you provide a value of * for the IF-MATCH header.
For example, imagine that while you’re executing the code of Listing 9-5,
someone else changes the same target item, confirming the updates before the
execution of your code. In a real-world scenario, you should retrieve the ETag
value as soon as the user starts editing the target item, and you should provide it
back to the server while saving your changes. Thus, you could have a short-term
concurrency conflict. Every time someone changes an item and saves it, the
ETag value will change. It is a numeric value, and it will increment by 1 unit
whenever a change happens. If a conflict does occur, the update or delete action
will fail, and your HTTP request will get back a 412 HTTP status code, which is
the Precondition Failed status. Moreover, in the response body, you will find an
XML or JSON representation of the error. For example, the JSON response error
message will look like the following excerpt:
Click here to view code image
{"error":{"code":"-1,
Microsoft.SharePoint.Client.ClientServiceException","message":
{"lang":"en-
US","value":"The request ETag value '\"4\"' does not match the
object's ETag value '\"5\"'."}}}
You can find this object serialized inside the data argument of the function
invoked if the HTTP request fails due to a concurrency conflict, and the
errorCode variable will assume a value of –1002. In your custom code, you
should catch this kind of exception, prompt the user with a concurrency conflict
error, and download the updated item from SharePoint to let the user compare
data and make a choice.
Cross-domain calls
When developing SharePoint Add-ins, you typically need to make cross-domain
JavaScript calls between the app web, which is the website in which your add-in
executes, and the host web, which is the website that is extended through your
SharePoint Add-in. Because the domain of the app web is always different from
the domain of the host web, this can cause complications. Specifically, browsers
prohibit this kind of behavior by default in an effort to avoid cross-domain
attacks and related security issues. Luckily, SharePoint provides a JavaScript
library to help you satisfy the browsers and keep the calls flowing: the
SP.RequestExecutor.js library.
Found in the _layouts/15 folder of every SharePoint site, the
SP.RequestExecutor.js library provides out-of-box capabilities to make cross-
domain calls against trusted and registered domains. When you instantiate the
library’s SP.RequestExecutor type in your client-side code, it uses a hidden
IFRAME element and some POST messages and a proxy page
(AppWebProxy.aspx) to enable you to make highly secure calls—even cross-
domain calls.
In Listing 9-5, the startup code adds a reference to the library for making
cross-domain calls. Then, it creates an instance of the SP.RequestExecutor type,
providing the URL of the app web in the object constructor. Behind the scenes,
the object injects an IFrame rendering the AppWebProxy.aspx page, which calls
the host web. When the call to the host web completes, the client instance of the
SP.RequestExecutor retrieves the result from the IFrame and provides it to the
calling add-in. Figure 9-2 diagrams this process.
Security
By default, the REST API requires that the consumers act in an authenticated
session for security purposes. From a SharePoint on-premises perspective, the
authenticated session can be gained through Windows integrated security,
browser-based direct authentication (in the case of a SharePoint-hosted app), or
by using OAuth (in any other situation). From a SharePoint Online perspective,
the session can be authenticated only by using the Azure AD user’s credentials
or by using the OAuth protocol.
In the case of integrated security, you need to enable the automatic flow of
integrated security credentials in the HTTP client library you will use. For
example, if you’re working in JavaScript within a web browser and using
SharePoint-hosted add-in or application pages, the flow of integrated security
credentials will be automatic. In contrast, when working in a Universal Windows
Platform app for Windows 10, for example, you must request permission for the
enterprise authentication capability in the AppManifest.xml file of the app.
If you want to use OAuth—for example, if you’re executing JavaScript code
within a provider-hosted SharePoint Add-in on a third-party site—you first need
to retrieve and store the access token provided during the OAuth handshake. For
example, you can use the ADAL.JS library that was introduced in Chapter 4,
“Azure Active Directory and security.” Then, you must provide that access
token to every request to the REST API, embedded in a dedicated Authorization
HTTP header, as you did in the PowerShell sample in Listing 9-1. The
JavaScript code excerpt in Listing 9-6 configures that HTTP header using an
access token stored in a hypothetical accessToken variable.
LISTING 9-6 A code excerpt for invoking the REST API with OAuth
authentication
Click here to view code image
jQuery.ajax({
url: "http://hostname_apicontextinfo",
type: "POST",
headers: {
"Authorization": "Bearer " + accessToken,
"accept": "application/json;odata=verbose",
"contentType": "text/xml"
},
})
Within Office 365, you can also leverage Azure AD to get an access token to
access SharePoint Online as long as you register your application in the directory
and assign proper permissions to it, as you saw in Chapter 4.
If you target an on-premises farm, you can enable anonymous access to read-
only operations of the REST API if you want to publish your contents to the
public Internet. To configure this capability, you will need to edit the
Anonymous permission of the target website. Figure 9-3 shows the configuration
panel for setting this option. You can find the panel by choosing Site Settings |
Site Permissions | Anonymous Access. However, you should be really careful
and disable this flag only in specific scenarios and after having considered all
pros and cons.
FIGURE 9-3 The UI for configuring anonymous access to the REST API
If you turn off Require Use Remote Interfaces Permission, all anonymous
users will be able to invoke the read-only operations of the REST API. Only
authorized users can change this option, but they may do so from the web
interface, by working within PowerShell, or by using the CSOM. For security
reasons, this option is not available in Microsoft SharePoint Online.
Important
The code samples are available through the PnP repository on
GitHub. To access them, follow the instructions provided in the
introduction of this book. The code samples illustrated here come
from a SharePoint-hosted add-in, so they do not need to provide an
OAuth access token. Please refer to the “Security” section earlier in
this chapter if you need to use the code sample from a provider-
hosted app.
For the sake of simplicity, all the code samples assume that you have the set
of global and predefined variables illustrated in Listing 9-7 and some common
startup code.
LISTING 9-7 A code excerpt for the startup phase of the code samples illustrated in
the current section
Click here to view code image
var hostweburl;
var appweburl;
var eTag;
var formDigestValue;
$(document).ready(function () {
// Get the URI-decoded URLs.
hostweburl =
decodeURIComponent(getQueryStringParameter("SPHostUrl"));
appweburl =
decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
function retrieveFormDigest() {
var contextInfoUri = appweburl + "_apicontextinfo";
var executor = new SP.RequestExecutor(appweburl)
executor.executeAsync({
url: contextInfoUri,
method: "POST",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
var jsonObject = JSON.parse(data.body);
formDigestValue =
jsonObject.d.GetContextWebInformation.FormDigestValue;
},
error: function (data, errorCode, errorMessage) {
var errMsg = "Error retrieving the form digest value:
"
+ errorMessage;
$("#error").text(errMsg);
}
});
}
All the code samples illustrated in the following sections will behave as event
handlers for HTML Button input elements.
LISTING 9-8 A JavaScript function for creating a list instance by using the REST
API
Click here to view code image
function createNewList() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists?@target='" +
hostweburl + "'";
var bodyContent = JSON.stringify({
'__metadata': { 'type': 'SP.List' },
'AllowContentTypes': true,
'BaseTemplate': 100,
'ContentTypesEnabled': true,
'Description': 'Custom List created via REST API',
'Title': 'RESTCreatedList'
});
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"content-type": "application/json;odata=verbose",
"content-length": bodyContent.length,
"X-RequestDigest": formDigestValue,
},
body: bodyContent,
success: function (data) {
var jsonObject = JSON.parse(data.body);
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
Notice that Listing 9-8 creates the list in the host web; your app will need
specific permissions to accomplish this task.
LISTING 9-9A JavaScript function for creating a list item in a list instance by
using the REST API
Click here to view code image
function createNewListItem() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/GetByTitle('RESTCreatedList')/Items?
@target='"
+ hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"content-type": "application/json;odata=verbose",
"content-length": bodyContent.length,
"X-RequestDigest": formDigestValue,
},
body: bodyContent,
success: function (data) {
var jsonObject = JSON.parse(data.body);
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
Notice the value assigned to the type property of the __metadata of the target
item. It defines the data type name corresponding to a list item of the current list.
Listing 9-9 assumes a value of SP.Data.RESTCreatedListListItem.
Updating an existing item is almost the same as creating a new one, except
that you need to provide the ETag value in the request headers and synchronize
the execution of parallel operations. Listing 9-10 shows an example that changes
the title property of an existing list item.
LISTING 9-10A JavaScript function for updating a list item in a list instance by
using the REST API
Click here to view code image
function updateListItem() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/GetByTitle('RESTCreatedList')/Items(1)
@
target='" + hostweburl + "'";
var bodyContent = JSON.stringify({
'__metadata': { 'type':
'SP.Data.RESTCreatedListListItem' },
'Title': 'Item changed via REST API'
});
Note that Listing 9-10 uses a nested SP.RequestExecutor instance, which will
run just after successful completion of the external operation invocation.
LISTING 9-11A JavaScript function for deleting a list item in a list instance by
using the REST API
Click here to view code image
function deleteListItem() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/GetByTitle('RESTCreatedList')/Items(1)
@
target='" + hostweburl + "'";
}
});
}
LISTING 9-12 A JavaScript function for querying a list of contacts by using the
REST API
Click here to view code image
function queryListItems() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/GetByTitle('Sample%20Contacts')/"
+
"Items?@target='" + hostweburl +
"'&$filter=Company%20eq%20'DevLeap'";
executor.executeAsync({
url: operationUri,
method: "GET",
headers: { "Accept": "application/json;odata=verbose" },
success: function (data) {
var jsonObject = JSON.parse(data.body);
$("#result").empty();
The HTTP request for querying items is a GET; it does not require a form
digest, and it will suffice that the app and the current user both have permissions
to read the target list. The response is a JSON serialized array of items that is
browsed by code.
LISTING 9-13 A JavaScript function for creating a document library via the REST
API
Click here to view code image
function createNewLibrary() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"content-type": "application/json;odata=verbose",
"content-length": bodyContent.length,
"X-RequestDigest": formDigestValue,
},
body: bodyContent,
success: function (data) {
var jsonObject = JSON.parse(data.body);
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
The procedure is almost identical to that for creating a custom list. The only
difference is that here you provide a BaseTemplate value compliant with a
document library. The example provides a value of 101, which corresponds to a
generic document library. When you successfully create the library, you will get
back a JSON serialization of its definition in the success event.
function uploadFile() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/" +
"GetByTitle('Documents')/RootFolder/Files/Add" +
"(url='SampleFile.xml',overwrite=true)?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"content-type": "text/xml",
"content-length": xmlDocument.length,
"X-RequestDigest": formDigestValue,
},
body: xmlDocument,
success: function (data) {
var jsonObject = JSON.parse(data.body);
$("#message").text("Operation completed!");
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
If you want to update a published file, you can use a procedure like the one
illustrated in Listing 9-15.
function updateFile() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/" +
"GetFileByServerRelativeUrl('sitesAppsDevelopmentSite/" +
"Shared%20Documents/SampleFile.xml')/$value?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"content-type": "text/xml",
"content-length": xmlDocument.length,
"X-HTTP-Method": "PUT",
"X-RequestDigest": formDigestValue,
},
body: xmlDocument,
success: function (data) {
$("#message").text("Operation completed!");
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
s}
As you can see, the REST endpoint for the operation is the $value of the file,
and the file will be overwritten by the body content submitted through the HTTP
request.
LISTING 9-16 A JavaScript function for checking out a document from a document
library via the REST API
Click here to view code image
function checkOutFile() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/" +
"GetFileByServerRelativeUrl('sitesAppsDevelopmentSite/" +
"Shared%20Documents/SampleFile.xml')/CheckOut()?@target='"
+
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": formDigestValue,
},
success: function (data) {
var jsonObject = JSON.parse(data.body);
$("#message").text("Operation completed!");
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
function checkInFile() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/" +
"GetFileByServerRelativeUrl('sitesAppsDevelopmentSite/" +
"Shared%20Documents/SampleFile.xml')/CheckIn?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"Content-type": "application/json;odata=verbose",
"Content-length": bodyContent.length,
"X-RequestDigest": formDigestValue,
},
body: bodyContent,
success: function (data) {
var jsonObject = JSON.parse(data.body);
$("#message").text("Operation completed!");
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
The check-out phase just requires an operation URI to be invoked via HTTP
POST. In contrast, the checkin phase requires posting some arguments, which in
the current example are presented as a JSON object. This posted JSON object
represents the arguments for the standard CheckIn method of the CSOM.
Deleting a document
The last action related to managing single files is deleting a document. As shown
at the beginning of this chapter, to delete a document you need to make an HTTP
POST request to the service, providing an ETag for security validation rules and
an HTTP header of type X-HTTP-Method with a value of DELETE. Listing 9-18
demonstrates the process.
LISTING 9-18 A JavaScript function for deleting a document from a document
library via the REST API
Click here to view code image
function deleteFile() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/" +
"GetFileByServerRelativeUrl('sitesAppsDevelopmentSite/" +
"Shared%20Documents/SampleFile.xml')?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"X-HTTP-Method": "DELETE",
"X-RequestDigest": formDigestValue,
"IF-MATCH": "*", // Discard concurrency checks
},
success: function (data) {
$("#message").text("Operation completed!");
},
error: function (data, errorCode, errorMessage) {
var jsonObject = JSON.parse(data.body);
var errMsg = "Error: " +
jsonObject.error.message.value;
$("#error").text(errMsg);
}
});
}
Notice that Listing 9-18 retrieves the file itself as an SP.File object instead of
the bare content ($value) of the file. The code then deletes that file without
performing a concurrency check, thanks to the IF-MATCH header with a value
of *.
function queryDocuments() {
var executor = new SP.RequestExecutor(appweburl);
var operationUri = appweburl +
"_apiSP.AppContextSite(@target)/web/lists/" +
"GetByTitle('Documents')/RootFolder/Files?@target='" +
hostweburl + "'";
executor.executeAsync({
url: operationUri,
method: "GET",
headers: { "Accept": "application/json;odata=verbose" },
success: function (data) {
var jsonObject = JSON.parse(data.body);
$("#message").empty();
Notice that the HTTP query uses an HTTP GET method and provides only the
Accept HTTP header. It does not require any other extended header or
information.
Summary
In this chapter, you learned about the REST API introduced in SharePoint 2013
and available in Microsoft SharePoint Online. You examined the architecture
and the capabilities of this tool, which can be consumed by any platform capable
of making HTTP requests. In addition, you learned how to implement the REST
API in real projects with JavaScript, addressing a set of common scenarios.
API in real projects with JavaScript, addressing a set of common scenarios.
Part IV: SharePoint and Office apps
CHAPTER 10 Creating Office 365 applications
CHAPTER 11 Overview of Office Add-ins
CHAPTER 12 Publishing your applications and add-ins
Chapter 10. Creating Office 365 applications
So far, you have seen how to consume Microsoft Office 365 from a general
viewpoint. However, as a developer you need to create real business solutions
that leverage the Office 365 ecosystem. In this chapter, you will walk through a
set of real-life examples that can give you inspiration about how to empower
your solutions with what you learned in the previous chapters.
Solution overview
Throughout this chapter, you will see how to create a solution that leverages
some of the most interesting capabilities of Office 365 and the Microsoft Graph.
The goal is to create a custom ASP.NET MVC solution that extends Office 365
to create custom digital workplaces to manage business projects.
The resulting application will be accessible through a tile in the app launcher
and by extending the out-of-box UI of SharePoint Online with some custom
actions. The sample solution provides some collaboration-oriented capabilities,
enabling you to start a new business project with a focus group of people
working on it. Under the cover of every project, there will be an Office 365
Group that will hold the documents, a shared calendar, and the communications
related to the project. The custom application will provide some customized UI
elements to interact with each project and to monitor the overall process by
sending automated email messages and feeding the Office 365 Group custom
notifications and events by leveraging the Office 365 Connectors.
Overall, the solution will give you some guidance about how to accomplish
the following tasks:
Creating an Office 365 application by using Microsoft Visual Studio 2015
Configuring the application to act on behalf of the current user via OAuth
2.0 or with an app-only access token
Leveraging the Office UI Fabric to provide a common and well-known
user experience to the application users
Using the Microsoft Graph to interact with Office 365 Groups and
Microsoft Exchange Online
Using the Microsoft SharePoint REST API to consume SharePoint Online
Leveraging controls and libraries provided by the Office 365 Developer
Patterns & Practices (PnP) community project
Leveraging an asynchronous development pattern and a Microsoft Azure
WebJob to create more scalable and reliable solutions
Creating an Office 365 Connector to asynchronously interact with an
Office 365 Group
In Figure 10-1, you can see the overall architecture of the solution, and in the
following sections you will dig into each of its key points.
Note
You can download the full sample solution from GitHub at the
following URL:
https://github.com/OfficeDev/PnP/tree/master/Samples/BusinessApps.O365ProjectsApp
Within the same GitHub repository, you will find other useful
samples and solutions, ready to be used for creating your own
solutions.
You can select to create a new ASP.NET 4.5.x MVC web application, which
will provide both MVC and Web API capabilities. Moreover, as you learned in
Chapter 4, “Azure Active Directory and security,” you will have to select an
authentication model. To target Office 365, you have to select the Work And
School Accounts authentication model, providing the target tenant name. 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. For further details about these dynamics, see Chapter 4.
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 any ASP.NET MVC
application, which is unlike the user interface and experience of Office 365. In
the section “Basic UI elements with Office UI Fabric” later in this chapter, you
will learn how to customize the UI to adhere to the common and well-known
user experience of Office 365. Nevertheless, even with the out-of-box ASP.NET
MVC UI, you will see the currently logged-in user name in the upper-right
corner of the screen.
Note
The statement instructs the tool to create a self-signed certificate (option -r),
with an exportable private key (option -pe), with a common name value of
“CN=ApplicationName” (option -n), valid from May 1 2016 (-b) to May 1 2018
(-e). Moreover, the certificate will be stored in the personal certificates secure
store of the current user (-ss my), and the generated key length will be 2,048 bits
(-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.
Note
You can find further details about the makecert tool at the following
URL: https://msdn.microsoft.com/en-
us/library/bfsktky3(v=vs.100).aspx. You can find more information
about the New-SelfSignedCertificate cmdlet at the following URL:
https://technet.microsoft.com/en-us/library/hh848633.
There is a sample solution called PnP Partner Pack that is available in the
OfficeDev PnP offering at the following URL:
http://aka.ms/OfficeDevPnPPartnerPack. That sample solution uses the app-
only authorization, like the sample application about which you are learning in
this chapter. One key benefit of having a look at the PnP Partner Pack is that the
setup guide of the project provides you with a PowerShell script
(https://github.com/OfficeDev/PnPPartner-Pack/blob/master/scripts/Create-
SelfSignedCertificate.ps1) that gives you a ready-to-go solution for creating a
self-signed certificate, which will be automatically installed in the certificate
store and also saved in the local file system.
More info
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. In Figure 10-3, you can see a
screenshot of the Manage Manifest menu item.
LISTING 10-1 The .JSON manifest file of an Office 365 application registered in
Azure AD
Click here to view code image
{
"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 keyCredentials 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.
Click here to view code image
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:
Click here to view code image
"keyCredentials": [
{
"customKeyIdentifier": "<Base64CertHash>",
"keyId": "<KeyId>",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": "<Base64Cert>"
}
],
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 10-2.
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 10-2, 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
10-3 in the next section.
<!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"
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 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
& 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">
</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. In Figure 10-4, you can see the overall
result, rendered in a browser.
FIGURE 10-4 The home page of the custom Office 365 application with the
Office 365 suite bar and top navigation bar
For the sake of brevity, this section will not show the entire content of the
_Office365SuiteBar.cshtml partial view, which is available in the sample code
related to this book. However, in Listing 10-4 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 10-4
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.
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 10-5, you can see the full implementation of that
PersonaController.
LISTING 10-5 The source code of the PersonaController, which renders the
current user’s profile picture by using the Microsoft Graph
Click here to view code image
using BusinessApps.O365ProjectsApp.WebApp.Components;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using 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";
if (result != null) {
return base.File(result, contentType);
}
else {
return new
HttpStatusCodeResult(System.Net.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);
}
return newImage;
}
}
}
<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">
Notice the search text box, which leverages a hidden text box 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 --hasMenu 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. You can see the
syntax to invoke the NavBar function at the end of Listing 10-3.
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:
by using an HTML syntax like the following:
Click here to view code image
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 glyphicon.
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, in Listing 10-7 you can see a sample grid
divided into the following three rows:
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, in Listing 10-7 the columns have the same
width regardless of the device size.
LISTING 10-7 Sample HTML excerpt that renders a responsive grid of Office UI
Fabric
Click here to view code image
<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.
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.
Note
UserCustomAction customAction =
targetLibrary.GetCustomAction(O365ProjectsAppConstants.ECB_Menu_
// If it doesn't exist
if (customAction == null) {
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 10-8, 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 chapter 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 ListId, ItemId, 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 10-
8 uses another PnP Core library extension method called ExecuteQueryRetry,
which is a powerfull 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. The
ExecuteQueryRetry method provided by PnP automatically handles retries and
hides from your code the need to take care of any connectivity or throttling
issues. You can even customize the ExecuteQueryRetry method, providing
arguments for retryCount and delay between retries. By default, the
ExecuteQueryRetry method will retry 10 times, with a 500 milliseconds delay
between each retry.
LISTING 10-9 Sample PnP XML provisioning template that provisions a library
with a user’s custom action
Click here to view code image
<?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}&ListId={ListId}&ItemId=
{ItemId}&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 10-10, you can see how to apply that provisioning template to a site,
creating the artifacts or updating them if they already exist.
LISTING 10-10 Sample code excerpt that applies a provisioning template to a site
Click here to view code image
ProvisioningTemplate template =
provider.GetTemplate("O365ProjectsAppSite.xml");
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/. If you are interested in understanding details about the
PnP Provisioning Engine, you can watch the following training
video on Channel 9:
https://channel9.msdn.com/blogs/OfficeDevPnP/PnPCore-
Component-Site-Provisioning-Framework.
context.Load(targetLibrary.RootFolder,
fld => fld.ServerRelativeUrl,
fld => fld.Files.Include(f => f.Title, f =>
f.ServerRelativeUrl));
context.ExecuteQueryRetry();
LISTING 10-12Helper method that uses the Microsoft Graph API to create and
configure an Office 365 Group
Click here to view code image
/// <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) {
photo.Position = 0;
MemoryStream photoCopy = new MemoryStream();
photo.CopyTo(photoCopy);
photoCopy.Position = 0;
MicrosoftGraphHelper.MakePatchRequestForString(
String.Format("{0}groups/{1}/photo/$value",
MicrosoftGraphHelper.MicrosoftGraphV1BaseUri,
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 chapter, 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 10-12 to create the group.
In Listing 10-13, you can see a code excerpt used to test whether the 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",
"''")));
The sample method makes a REST request against the collection of groups in
the current tenant, filtering the items based on the values in the groupTypes
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.
/// <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 ConversationThread. As you can see in Figure 10-5, the
result will be the creation of a new welcome thread in the new Office 365 Group.
FIGURE 10-5 The conversation view of the just-created Office 365 Group, with
the new thread created by code
log.WriteLine("Completed Job");
}
}
The method signature is simple, but notice the QueueTrigger attribute applied
to the first argument. The purpose of the attribute is to instruct the job engine to
trigger this method whenever there is a new message in a queue, whose name is
provided as an argument to the attribute. To make the sample solution easier to
maintain, we provide the name of the queue to poll via a string constant stored in
a helper type, which is defined in the infrastructural class library. Moreover, the
first argument of the method is a POCO (Plain Old CLR Object) instance of type
GroupCreationInformation, which you can define freely and which will be used
to hold the deserialized content of every enqueued message. The QueueTrigger
attribute supports the following types as input arguments for the queue message:
String
Byte[]
Any POCO type
CloudQueueMessage
The second argument of the method allows you to keep track of the job
activities by logging them through a TextWriter object.
In Listing 10-16, you can see how the GroupCreationInformation type is
defined. It is a lightweight object with just a few properties that define the
general information useful to create the Office 365 Group under the cover of the
business project. It is fundamental to notice that any custom type used as a
message for a Blob Storage Queue will have to be serializable through the
NewtonSoft.Json library because internally the Azure WebJobs SDK will use
that serialization engine to serialize and deserialize queued messages.
In a real business job, you can handle the typed input argument and implement
your own business logic, processing every queued message. In Listing 10-17,
you can see the real implementation of the job related to the sample solution for
this project.
LISTING 10-17 The real implementation of the queue triggered method of the job
Click here to view code image
log.WriteLine("Completed Job");
}
As you can see, the method creates a new Office 365 Group, like in Listing
10-12. After that, it sends the welcome message, using the same syntax that we
used in Listing 10-14. Overall, the method implementation is straightforward,
and the only difference is that the method will be activated by a queued message.
Moreover, because the job will be executed in the background and without an
interactive user’s session, from an authentication and authorization perspective
you have three options: passing the interactive user’s OAuth access token to the
background job; using an app-only access token with an X.509 certificate
authentication; or using an explicit set of credentials (username and password) of
an application account. Because in the current sample the Office 365 Group
needs to be created by an explicit user’s account that will automatically become
the owner of the group, in Listing 10-17 you see the accessToken argument
passed to both the methods that create the group and start the first conversation
in the group. That access token is the one of the interactive user who enqueued
the async job item in the Blob Storage Queue, and it is passed through the
AccessToken property of the GroupCreationInformation instance. For security
reasons, you should encipher the access token value to avoid having sensitive
data represented in cleartext in the Blob Storage Queue.
To make the job effective, you will also need to add a couple of statements in
the Main static method entry point of the console application. In Listing 10-18,
you can see the Main method implementation.
LISTING 10-18The Main method of the console application under the cover of the
remote timer job
Click here to view code image
class Program {
static void Main() {
var host = new JobHost();
// The following code ensures that the WebJob will be
running continuously
host.RunAndBlock();
}
}
The RunAndBlock method of the JobHost type allows the WebJob to run
continuously, waiting for external triggers (like a new message in the Blob
Storage Queue), keeping the foreground thread on hold, and leveraging
background threads for the asynchronous events.
Moreover, to run the job properly, you will have to configure a couple of
connection strings in the App.Config of the console application. These settings
reference the Azure Storage Queue and will be used by the Azure WebJobs
infrastructure to run the job. In Listing 10-19, you can see an excerpt of the
App.Config file showing those connection strings. You will also have to
configure the App.Config file of the console application to have the settings
related to the Office 365 application and the custom configuration settings for
the app.
LISTING 10-19 An excerpt of the App.Config file of the remote timer job
Click here to view code image
<connectionStrings>
<add name="AzureWebJobsDashboard"
connectionString="[Azure Blob Storage Connection
String]" />
<add name="AzureWebJobsStorage"
connectionString="[Azure Blob Storage Connection
String]" />
</connectionStrings>
</configuration>
"DefaultEndpointsProtocol=https;AccountName=
[StorageAccountName];AccountKey=[Key]"
However, the key element of an asynchronous job like the one we are
discussing is in the code that enqueues a new message in the Azure Blob Queue.
Returning to the ASP.NET MVC application, we need to replace the code that
creates the Office 365 Group in the controller with some new statements that
will send the typed message to the queue. In Listing 10-20, you can see a sample
implementation for the new MVC controller.
The above card provides text content that will be the message body in the
conversation. The content of the JSON message has to adhere to the card formats
defined in the online documentation for Office 365 Connectors.
More info
You can find further details about the supported card formats at the
following URL: https://dev.outlook.com/Connectors/GetStarted.
{
"title":"New card from a webhook!",
"text":"Please visit the [OfficeDev PnP site]
(http://aka.ms/OfficeDevPnP)!",
"themeColor":"C0C0C0"
}
This second sample sends a card with a title, a body that includes a hyperlink,
and a custom color. As you can see, the syntax of the hyperlink in the body is
based on the Markdown language, which is also used for editing .MD files on
GitHub. You can do much more with the cards’ content.
More info
For a full reference about the syntax for creating cards, you can read
the document “Office 365 Connectors API Reference,” which is
available at the following URL:
https://dev.outlook.com/connectors/reference.
If you want to send a message to the webhook using .NET code, you need to
use the HttpClient type like we did in Section III, “Consuming Office 365,”
while consuming the Microsoft Graph.
See Also
For further details about publishing applications in the Office Store,
please refer to Chapter 12, “Publishing your applications and add-
ins.”
The Connect To Office 365 button will open a URL like the following:
https://outlook.office.com/connectors/Connect?
state=myAppsState&app_id=440ae2b5-aa7e-4bbc-9145-
ce57ec0fcdb9&callback_url=<Callback_URL>
The state argument in the query string defines a custom state that you can pass
back to your application within the query string of the callback_url. The app_id
argument in the query string is a GUID to uniquely identify your app. Last, the
callback_url query string argument defines the URL of the page to which your
users will be redirected after having configured the new connector, and it is the
URL that you provided in the previous step. In Figure 10-13, you can see the UI
of the Connect To Office 365 page, which is provided by clicking the Connect
To Office 365 button.
FIGURE 10-13 The UI of the Connect To Office 365 page for adding a custom
connector to an Office 365 Group
The page will provide a list of Office 365 Groups, and by selecting a group
and clicking the Allow button, the user’s browser will be redirected to the
callback_url defined in the query string, getting in the query string of the target
page some useful arguments to properly register the webhook for the connector.
The following is an example of the callback_url with the query string
parameters:
<Callback_URL>?
state=myAppsState&webhook_url=https%3A%2F%2Foutlook.office365.com%2Fwebho
ece2-4951-bab7-6e89ad8b6f10%406c94075a-da0a-4c6a-8411-
badf652e8b53%2F440ae2b5-aa7e-4bbc-9145-
ce57ec0fcdb9%2Fbb274c954fbc4ce7b2996b1f90a5f0b4%2Fbea7a848-
0459-4bee-9034-5513ee7f66e0&group_name=Sample%20Group
Notice the webhook_url argument, which defines the URL of the webhook to
use for communicating with the target Office 365 Group. The URL will also
provide the group_name argument, which defines the name of the target Office
365 Group. If you defined a custom state in the Connect To Office 365 button
definition, you will get it back in the callback URL.
There is one more query string argument called error, which will be provided
to the callback_url if the end user clicks the Deny button instead of clicking the
Allow button to connect the connector with the target Office 365 Group. In this
last scenario, the value of the error argument will be AccessDenied. There are
some other possibile values for the error argument, but for the sake of brevity
we will not cover them here.
Furthermore, if you want to play with the connectors without having to create
and register a real application, you can leverage the Connectors Sandbox tool,
which is available at the following URL:
http://connectplayground.azurewebsites.net/. In Figure 10-14, you can see the UI
of the Connectors Sandbox, which is basically a tool that allows you to send
cards to an Office 365 Group using a web-based helper UI and inspect the JSON
sent to the target group.
FIGURE 10-14 The UI of the Connectors Sandbox for playing with Office 365
Connectors
Summary
In this chapter, you learned a lot of information about how to create real
enterprise-level solutions by leveraging Office 365, the services provided by this
Platform as a Service (PaaS) offering, and Microsoft Azure.
In particular, you saw how to create and register an Office 365 application in
Azure AD. You learned how to configure the application to leverage app-only
authentication/authorization and how to set up app permissions properly. You
discovered the purpose and the business value of Office UI Fabric to create
professional UI and UX for your custom solutions.
Moreover, you saw how to extend SharePoint with custom UI elements and
by provisioning custom artifacts like site columns, content types, lists and
libraries, and so on. You also experienced how to consume the Microsoft Graph
API in real enterprise-level scenarios, like automatically creating Office 365
Groups either in real time or by leveraging a background job. You learned how
to create remote timer jobs for SharePoint Online and for Office 365 in general.
Then, you saw how to publish an Office 365 application and any remote timer
job to an Azure App Service.
Last, you learned how to create connectors for Office 365 Groups, both simple
webhooks registered for a single Office 365 Group and complex connectors
available at the tenant level or even globally available in the Office Store.
Chapter 11. Overview of Office Add-ins
The Office Add-ins are another kind of custom developed solution that you can
create to extend the Microsoft Office 365 ecosystem.
In this chapter, you will have an overview of the various flavors of Office
Add-ins that you can create, and you will understand the potential of the Office
Add-ins development model.
Add-in manifest
As you just saw, one key component of every Office Add-in is the manifest file,
which is an XML file that defines settings, capabilities, and information about an
add-in. The XML manifest file contains information like the following:
The display name of the add-in, its description, its version, the default
locale, and a unique ID for the add-in
How the add-in can integrate with the various Office client applications
(Word, Excel, PowerPoint, Project, Outlook), including any custom UI
element
The permission levels and data access requirements for the add-in
The file by itself is simple and has to be validated against the XML Schema
definition for Office Add-in manifests, for which you can find some useful
samples at the following URL: https://dev.office.com/docs/Add-
ins/overview/Add-in-manifests. Moreover, you can find the XSD (XML Schema
Definition) files for the XML manifests at the following URL:
https://github.com/OfficeDev/officejs-docs/tree/master/docs/overview/schemas.
If you are using Microsoft Visual Studio to create your add-ins, you don’t
need to dig into the details of the XML Schema. You will need to design the
add-in settings by using the manifest designer provided by the Office Developer
Tools for Visual Studio. In Figure 11-2, you can see the user interface of the
manifest designer in Visual Studio 2015 for an Outlook Add-in.
FIGURE 11-2 The UI of the manifest designer for Office Add-ins in Visual
Studio 2015
In addition to the general information about the add-in, in the manifest file
you can declare a lot of information about the UI elements (like buttons, tabs,
menu items, and so on) if the add-in defines custom commands for the UI. In
addition, depending on the type of add-in, the XML manifest file can have
different shapes and different XML elements.
In the following sections, you will see some kinds of add-ins and have an
overview of the most important settings in the corresponding XML manifest
files.
<Requirements>
<Sets>
<Set Name="Mailbox" MinVersion="1.1" />
</Sets>
</Requirements>
Note
FIGURE 11-4 The outline of the Solution Explorer in Visual Studio 2015 for an
Outlook Add-in
The Office Developer Tools for Visual Studio will create a new solution with
two projects: one that provides the add-in metadata, including by default the
XML manifest file, and another that represents the web application, which will
host the add-in UI and business logic.
Double-click the manifest file (the one with the Office icon) to manage the
XML manifest definition by using the manifest designer, as you saw in the
previous section. Configure the add-in with proper display name, version, icon,
and description. Give it the Read Write Item permission level, and configure the
mailbox requirements to version 1.3 (for this example).
By default, the Visual Studio project template creates a sample custom pane
add-in that will be activated whenever the end users read any email message.
The web application project contains a MessageRead.html page on the root
folder, which provides the UI of the add-in. Moreover, there is a MessageRead.js
file and a MessageRead.css file that provide the JavaScript logic for the add-in
and its UI style.
If you open the MessageRead.html page, you can see that there are just
HTML elements and every logic is loaded from the supporting MessageRead.js
file. The HTML page by default uses the Office UI Fabric components,
downloaded from the public Microsoft CDN
(https://appsforoffice.microsoft.com/), to give a standard UI to the sample add-
in. Moreover, the page downloads the Office JavaScript APIs from the Microsoft
CDN to leverage the Office object model for add-ins.
The add-in template will create an add-in that reads some properties from the
current context item and shows them in the custom pane. You can start playing
with the add-in by starting the project in Visual Studio by pressing F5. Visual
Studio will start the deployment of the add-in and will prompt you for
credentials of the target mailbox to which you want to install the add-in for
testing and debugging purposes. In Figure 11-5, you can see the UI of Visual
Studio 2015 prompting for target mailbox credentials.
FIGURE 11-5 The dialog of Visual Studio 2015 to collect target mailbox
credentials for testing and debugging an Outlook Add-in
If deployment succeeds, the default web browser will be started, and you will
be brought to the Outlook Web Access site for the target mailbox. Select any
email message, and you will see the add-in in action. In Figure 11-6, you can see
the output in the web browser.
FIGURE 11-6 The sample Outlook Add-in rendered in Outlook Web Access
Under the cover, Visual Studio 2015 will attach the browser process for
debugging of JavaScript code so that you easily can inspect what happens under
the cover of the sample add-in. For the sake of simplicity, open the
MessageRead.js file and browse the source code. At the beginning of the file,
you will find the Office.initialize function implementation. The following
section, “Office JavaScript APIs,” will introduce the Office namespace in
JavaScript. For now, it will suffice to know that the Office.initialize function is
fundamental and must be defined for each page in your app for every kind of
add-in.
In the sample implementation of the current Outlook Add-in, inside the
Office.initialize function there is the handling of a message banner and the
invocation of a method that will load all of the properties from the current
context item. Generally, from the Office.initialize function you will have to
invoke all the JavaScript business logic of your add-in to shape the HTML
output. The JavaScript business logic will use the Office JavaScript APIs to
interact with the hosting Office application and with the current context item, if
any.
To make the add-in show up when the email subject satisfies the rule, you can
edit the manifest file and add an activation rule of type Item Matches A Regular
Expression, which will be in logical AND with the rule Item Is A Message. In
Figure 11-7, you can see how the activation rules are configured.
FIGURE 11-7 The Read Form rules to activate the add-in in Outlook
Once the add-in is activated, instead of rendering the properties of the current
context item like the out-of-box add-in template does, the custom add-in will
render some information about the current sender of the request for quote. First,
the add-in will search for any existing contact in the current user’s organization
by using the Microsoft Graph API. Furthermore, the add-in gets the list of the
top documents in the current user’s OneDrive for Business with a reference to
the customer’s display name by using the search capabilities of OneDrive for
Business in the Microsoft Graph API.
In Listing 11-1, you can see the updated HTML for the MessageRead.html
page, including some HTML placeholder elements that will be used to render the
output of the queries executed against the Microsoft Graph API.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title></title>
<script src="Scripts/jquery-1.9.1.js" type="text/javascript">
</script>
See Also
For further details about the OAuth 2.0 implicit flow capability, you
can read the official OAuth 2.0 specification—in particular, the
section that explains the implicit flow—which is available at the
following URL: https://tools.ietf.org/html/rfc6749#section-1.3.2.
To configure the OAuth 2.0 implicit flow, while in the configuration page of
the just-created Azure AD application, click the Manage Manifest command in
the lower part of the screen and select the Download Manifest option. Save the
manifest file and open it with a text editor of your choice. Search for the
oauth2AllowImplicitFlow configuration option, which by default has a value of
false, and change it to true. Save the manifest file and upload it back to Azure
AD, overwriting the existing one (Manage Manifest > Upload Manifest).
Now, you have to configure the add-in manifest to allow access to the URL
domains related to Azure AD from within the add-in frame. To do that, as you
saw previously, you need to edit the App Domains section of the manifest. In
Figure 11-8, you can see how the sample add-in is configured.
FIGURE 11-8 The App Domains settings for the sample Outlook Add-in
The domains to configure are:
https://login.microsoftonline.com
https://login.windows.net
Configuring these URLs in the manifest will enable your add-in to
authenticate the current user and to retrieve an OAuth 2.0 access token without
opening any pop-up browser window.
Note
You are now ready to plug the ADAL.JS library and its related logic into the
add-in business logic. First, you will have to reference the ADAL.JS library,
which can be done by downloading the source code from GitHub
(https://github.com/AzureAD/azure-activedirectory-library-for-js) or by
referencing the official CDN provided by Microsoft through the following script
tag:
Click here to view code image
<script src="https://secure.aadcdn.microsoftonline-
p.com/lib/1.0.10/js/adal.min.js"></script>
You can add the previous script element to the headers of the
MessageRead.html page in the add-in web application, just after the JavaScript
APIs for Office script element and before the reference to the MessageRead.js
file, as you can see highlighted in bold in Listing 11-1. If you are using the
webstack, you can also reference ADAL.JS via some of the web package
managers like npm (https://www.npmjs.com/) or Bower (https://bower.io/), and
there are ADAL libraries for Angular.JS, Cordova, and so on.
Once you have referenced ADAL.JS, you are ready to use it. Open the
MessageRead.js file and paste the code illustrated in Listing 11-2 at the
beginning of the file.
As you can see, the code excerpt registers some configuration variables,
including the target Azure AD tenant name, the client ID for the current
application, and the resources that you want to consume using ADAL for
authorization. In Listing 11-2, the target resources are the Microsoft Graph API
and SharePoint Online.
After you have prepared the configuration settings, you need to create an
instance of the AuthenticationContext object of ADAL.JS. Then, the code
excerpt checks if the current user is authenticated against Azure AD, and if she
isn’t, it fires a login request. In a common scenario, because the add-in usually is
running within an authenticated context (Office client), the authentication flow
will be just one round trip to Azure AD and back to the add-in implementation.
You are now ready to consume the Microsoft Graph API with ADAL.JS, which
will get an OAuth 2.0 access token for you. In Listing 11-3, you can see the
remainder of the MessageRead.js file.
(function () {
"use strict";
var messageBanner;
// The Office initialize function must be run each time a new
page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
var element = document.querySelector('.ms-MessageBanner');
messageBanner = new fabric.MessageBanner(element);
messageBanner.hideBanner();
loadContextData();
});
};
function loadContextData() {
var item = Office.context.mailbox.item;
var senderDisplayName = item.sender.displayName;
authContext.acquireToken(config.endpoints.graphApiUri,
function (error, token) {
if (error || !token) {
console.log("ADAL error occurred: " + error);
return;
}
else {
var senderContactUri = config.endpoints.graphApiUri
+ "v1.0me/
contacts?$filter=displayName%20eq%20'" + senderDisplayName +
"'&$top=1";
$.ajax({
type: "GET",
url: senderContactUri,
headers: {
"Authorization": "Bearer " + token
}
}).done(function (response) {
console.log("Query for sender contact
executed.");
var items = response.value;
for (var i = 0; i < items.length; i++) {
console.log(items[i].displayName);
$("#senderDisplayName").text(items[i].displayName);
$("#senderCompanyName").text(items[i].companyName);
$("#senderMobilePhone").text(items[i].mobilePhone);
}
}).fail(function () {
console.log("Error while searching for sender
contact.");
});
$.ajax({
type: "GET",
url: filesUri,
headers: {
"Authorization": "Bearer " + token
}
}).done(function (response) {
console.log("Successfully fetched files from
OneDrive.");
var items = response.value;
for (var i = 0; i < items.length; i++) {
console.log(items[i].name);
$("#filesTable").append("<div class='ms-
Table-row'><span class='ms-
Table-cell'><a href='" + items[i].webUrl + "'>" + items[i].name +
"</a></span></div>");
}
}).fail(function () {
console.log("Fetching files from OneDrive
failed.");
});
}
});
}
FIGURE 11-9 The sample Outlook Add-in in action within Outlook Web Access
This is a simple example of how to consume the Microsoft Graph API from
within an Outlook Add-in. You can do much more, and you can always enrich
the client-side development model of the Office Add-in by implementing a set of
custom REST API services (could be ASP.NET MVC ApiController types) that
can provide custom and additional capabilities to the Office Add-in business
logic.
Note
The Yeoman generator will start a generation template for creating an Office
Add-in.
Note
You can find further details about the Yeoman generator for Office
and about all the available commands for Office Add-ins at the
following URL: https://github.com/OfficeDev/generator-office.
First, you have to provide a name for the add-in project that will be generated.
Then, you have to select the target folder where all the autogenerated files will
be stored. After that, you have to choose the kind of add-in you want to develop.
At the time of this writing, the available options are:
Mail Add-in (read & compose forms)
Task Pane Add-in
Content Add-in
Then, you can choose the technology you want to use for creating the Office
Add-in. The available options are:
HTML, CSS & JavaScript: The option we used earlier.
Angular: Will use AngularJS only.
Angular ADAL: Will use AngularJS together with ADAL.JS for Angular.
Manifest.xml only (no application source files): Will just create the
manifest file. Everything else will be in your charge.
Select the first option for the sake of comparing the Yeoman result with what
you can get by using the project templates of Visual Studio 2015.
Because we chose to create a Mail Add-in, you have to select what kind of
extension points you want to support with the target add-in. The available
options are:
Message read
Message compose
Appointment attendee
Appointment organizer
Custom pane (for message read and appointment attendee forms)
You can make multiple selections based on the real needs of your target add-
in solution. After you select an option, the Yeoman generator will start creating
the project files for you. It will take a while (about one or two minutes),
depending on the network bandwidth you have, to download the packages and
create the full project files. The Yeoman generator will download a bunch of
packages and template files from the network to create a project on your
environment. In Figure 11-10, you can see the UI of the Yeoman generator
within Cmder while creating an Outlook Add-in.
FIGURE 11-10 The Yeoman generator in action within Cmder while creating an
Outlook Add-in
Once the Yeoman generator has created the source files, you can start Visual
Studio Code against the current project folder by executing the following
command:
Code .
The above command will start Visual Studio Code, directly targeting the
current folder, which is the folder of the just-created add-in. In Figure 11-11, you
can see the UI of Visual Studio Code, editing an Outlook Add-in project created
by using the Yeoman generator.
FIGURE 11-11 A sample Outlook Add-in project created by using the Yeoman
generator and edited in Visual Studio Code
The Solution Explorer on the left side of Figure 11-11 shows the outline of the
Outlook Add-in project. As you can see, there is the XML manifest file on the
root of the project. Under the appread and appcompose folders, there are the
HTML, CSS, and JS files that implement the UI of the add-in for both the
Message Read and Message Compose use cases. You can change the JavaScript
code, like you did in Visual Studio 2015, in this project according to your
business needs.
Once you are ready with your code implementation, you can start the project
using, for example, Node.js as the target hosting environment. If you installed
your development machine properly, as illustrated in Chapter 2, you can just run
the following command from the Cmder console:
gulp serve-static
FIGURE 11-12 The Manage Add-ins page of the Outlook Web Access settings
Browse for the XML manifest file of the Outlook Add-in that you want to add
and follow the instructions, and your Yeoman-generated add-in will be available
in Outlook Web Access for testing purposes.
Once you are ready to release the add-in, you will have to publish it on a
public hosting infrastructure, and you will have to update the manifest file to
target the public URL of the hosting platform.
LISTING 11-4 Code prototype of the Office.initialize event handler for an Office
Add-in
Click here to view code image
// The Office initialize function must be run each time a new page
is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
Note
If you would like to see some examples of Office Add-ins, you can
refer to the following open source solutions available in the
OfficeDev repository on GitHub: 1) the main Office Add-ins
repository of PnP: https://github.com/OfficeDev/PnP-OfficeAddins;
2) a Word Add-in sample with several real-life examples about how
to use Office JavaScript API: https://github.com/OfficeDev/Word-
Add-in-DocumentAssembly; 3) a sample Excel Add-in to see how to
visualize custom content in the document:
https://github.com/OfficeDev/Excel-Add-in-VisualizeExcelData; 4)
a sample PowerPoint Add-in to insert a chart into a presentation:
https://github.com/OfficeDev/PowerPoint-Add-in-Microsoft-Graph-
ASPNET-InsertChart; and 5) a sample solution about how to use the
new commands in the UI of Office:
https://github.com/OfficeDev/OfficeAdd-in-Commands-Samples.
FIGURE 11-14 The Activation tab in the manifest designer of Visual Studio
2015 for a Task Pane Add-in
When you define the requirements, Visual Studio will match the supported
API Sets against an Office API NuGet package and will determine the versions
of Office client hosts that will be able to run the add-in.
The General tab of the manifest designer is also slightly different in a Task
Pane Add-in. The permissions that you can declare are:
Restricted
Read document
Read all document
Write document
Read write document
Note
You can find further details about the available permissions in the
document “Permission element,” which is available at the following
URL: https://dev.office.com/reference/Add-
ins/manifest/permissions. You can also read the document “Privacy
and security for Office Add-ins,” which is available at the following
URL: http://dev.office.com/docs/Add-ins/develop/privacy-and-
security.
Moreover, if you create an add-in for Excel by using Visual Studio 2015,
there will be a wizard-like tool that will help you define the goal of your add-in.
First, the wizard will ask you if you want to Add New Functionalities To Excel,
which means adding new commands to the UI of Excel, or if you want to Insert
Content Into Excel Spreadsheets, which means creating a Content Add-in that
will insert embeddable objects into Excel spreadsheets. In Figure 11-15, you can
see the two steps of the wizard. In the top part of the screen, there is the first
step; in the bottom part of the screen, there is the second step, which is shown
just for Content Add-ins.
FIGURE 11-15 The wizard for creating an Excel Add-in in Visual Studio 2015
From an implementation perspective, all the add-ins will implement the
Office.initialize event handler and will work with the current document through
the Office.context.document object that you encountered in the section “Office
JavaScript APIs” earlier in this chapter.
In Listing 11-5, you can see an excerpt of the JavaScript code under the cover
of a Word Add-in that inserts some content into the current document, which is
the default template created by Visual Studio 2015 when you create a new Word
Add-in.
LISTING 11-5Code excerpt of a Word Add-in that inserts some content into the
current document
Click here to view code image
As you can see, the core of the code excerpt is the Word.run method
invocation, which provides to the Word engine an anonymous function to
execute. That function will get a reference to the current context through which
the add-in can access the current document. Moreover, the Word JavaScript
object model allows you to insert some text in the current document by using the
insertText method, to get the current user’s selection by invoking the
getSelection method of the document object, and so on.
Almost the same approach applies to an Excel Add-in. For example, in Listing
11-6 you can see a code excerpt of an Excel Add-in.
LISTING 11-6 Code excerpt of an Excel Add-in that inserts some random values
into a set of cells in the current worksheet
Click here to view code image
var values = [
[Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000)],
[Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000)],
[Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000)]
];
By using the Excel.run method, the sample provides to the Excel object model
a bunch of actions to perform through an anonymous function. Basically, the
code selects the current active worksheet and inserts an array of random values
into a range of cells.
In such add-ins, you most likely will leverage the Microsoft Graph API and
the SharePoint Online sites to load data or look up data related to the current
Word or Excel document. To do that, you can use the techniques and tools you
saw in the previous pages applied to an Outlook Add-in and based on ADAL.JS
and a bunch of REST requests via AJAX.
Summary
In this chapter, you had an overview of the architecture of the Office Add-ins in
general, which are based on an HTML, CSS, and JavaScript solution plus an
XML manifest file. Moreover, you saw the most common tools to create Office
Add-ins and manage their manifest files.
In particular, you learned how to create an Outlook Add-in that can consume
the Microsoft Graph API via REST in a secure way by leveraging the ADAL.JS
library. Last, you saw a few key differences—compared with Outlook Add-ins—
and the challenges of developing Content and Task Pane Add-ins for Word,
Excel, PowerPoint, and Project.
Chapter 12. Publishing your applications and add-ins
Note
By default, the app catalog can be read by any member of the group
Everyone Except External Users. Moreover, the site collection
administrator designated when the site is created will have full
control of the site contents. Of course, you can add more users with
specific permissions for publishing Office Add-ins or SharePoint
Add-ins.
If you are publishing an Office Add-in, you will have to register the app
catalog in the Office client applications through the Trust Center of the
applications. You can find detailed instructions about how to accomplish this
task in the document “Publish task pane and content add-ins to an add-in catalog
on SharePoint,” which is available at the following URL:
http://dev.office.com/docs/add-ins/publish/publish-task-pane-and-content-add-
ins-to-an-add-in-catalog. If you like, you can also configure the app catalog
URLs of the Trust Center in Office client by using a Group Policy in Windows
Active Directory.
After you have published an add-in in the Corporate Catalog, you and your
tenant users will be able to use it. If you published a SharePoint Add-in, browse
to the Site Contents page of the site where you want to install the add-in and
select Add An App, choose the add-ins coming From Your Organization, and
you will find the SharePoint Add-ins that you published in the app catalog. If
you published an Office Add-in, open the target Office client application and
select Insert A New Item From The Store. Choose the My Organization tab and
search for your tailor-made add-ins.
If you want to update your add-in, you will need to upload an updated version
of the file into the target library, increasing the version number, and everything
else will be managed by the infrastructure of the add-in.
Office Store
Another option you have is to publish your add-ins or applications to the public
marketplace, making them available to everyone through the Office Store.
Before being listed in the Office Store, your add-ins or applications will have
to adhere to some strict validation rules.
See Also
For details about the rules to which your add-ins and applications
must adhere, you can read the document “Validation policies for
apps and add-ins submitted to the Office Store,” which is in version
1.9 at the time of this writing. It is available at the following URL
on MSDN: https://msdn.microsoft.com/en-
us/library/office/jj220035.aspx.
To publish a solution on the Office Store, you will have to create an account
on the Microsoft Seller Dashboard. More details about this procedure are
provided in the following section, “Using the Seller Dashboard.”
After you have submitted your solution to the Seller Dashboard and it has
been validated and approved by Microsoft, your users will be able to find and
been validated and approved by Microsoft, your users will be able to find and
install (and possibly buy) your add-ins or applications directly from the Office
Store. We say “possibly buy” because you can provide solutions for free and still
list them in the public marketplace.
FIGURE 12-2 The screen to add languages to an add-in within the Seller
Dashboard
The third wizard step is called Block Access, which allows you to block
customers in certain countries or regions from purchasing or even using the add-
in. As you can see in Figure 12-3, the list of countries and regions is long and
detailed.
FIGURE 12-3A list of some of the countries and regions that you can block
from purchasing or using the add-in within the Seller Dashboard
The last wizard step is Pricing, which allows you to define the pricing model
for your add-in. You have three options from which to choose:
This App Is Free The app is available for free, indeed.
This App Is For One-Time Purchase Customers pay for the app only
once.
This App Will Be Sold As A Subscription You want to sell the app
according to the Software as a Service (SaaS) model.
If you want to provide the app for free, later you will be able to change the
pricing model and convert the app to SaaS or to a one-time purchase mode. If
you are not providing the app for free, you will have to choose a pricing model.
If you select to sell the add-in using a one-time purchase model, you will later be
able to convert the app to the free model, but you cannot convert it to a
subscription model. If you select to sell the add-in with a subscription model,
you cannot change it to free or one-time purchase model after it has been
published.
published.
The price, if any, has to be declared on a per-user basis, through the Price Per
User field, and you cannot define whatever price you like. There is a long list of
available prices ranging from $1.49 to $999.99, and the available prices are
predefined. You can also select a Price Threshold field, which enables you to
declare a maximum number of per-user licenses for which you want a single
customer to pay. After that number, any further per-user license will have no
additional charge. You can select No Threshold or any of the predefined values
like 1, 10, 25, ..., up to 1,000.
Here are some example scenarios:
If you select a per-user price of $9.99 and a price threshold of 10, your
customers will pay $9.99 for each user up to 10 users. Starting from the
eleventh user, they will not pay any more.
If you select a per-user price of $9.99 and no price threshold, your
customers will pay $9.99 for every user.
If you select a per-user price of $9.99 and a price threshold of 1, your
customers will pay $9.99 just once, and the result will be like a per-site
license.
Note
To sell an app, you will also have to configure a valid tax account in
the Seller Dashboard. Otherwise, you will not be able to publish the
add-in in the Office Store because you wouldn’t be able to get your
money back from Microsoft.
You can also enable trial support for your add-in by enabling the option with
label My App Supports A Trial. If you enable a trial, you have to specify the
Duration Of Trial, which can assume any of the following values: 15 days, 30
days, 60 days, or Unlimited. One more setting related to the trial of your app is
the Number Of Users In Trial option, which can assume any of the following
values: 1 user, 5 user(s), 20 user(s), or Unlimited.
Once you have configured all of the settings for the add-in, you can save it as
a draft for further editing by clicking the SAVE AS DRAFT button, or you can
submit it for publishing by clicking the SUBMIT FOR APPROVAL button.
After submitting an add-in, your solution will go through the validation
process and eventually be approved and published in the Office Store, based on
the publishing date that you provided.
the publishing date that you provided.
Licensing model
In the previous section, you saw that whenever you sell add-ins or applications
through the Office Store you can choose a pricing model, which is related to a
specific licensing model.
You saw that you can provide your solution for free, but you can also sell it by
using a per-seat or per-site model or by using a subscription model. Aside from
the marketing and sales perspectives, which are out of the scope of this book, it
is important to understand how you can programmatically manage and monitor
is important to understand how you can programmatically manage and monitor
that your customers use your solutions according to the pricing and licensing
model that you choose. You will need to verify and enforce that everybody uses
your solutions according to the legal use policies that you defined.
In the following paragraphs, you will dig into the techniques for managing
and checking licenses from a developer perspective.
Types of licenses
First of all, it’s worth explaining in detail the types of licenses available through
the Office Store, based on the flavors of solutions that you can publish.
In Table 12-1, you can see a matrix that explains what models are available
for what kind of solutions.
TABLE 12-1 Licensing models comparison for the available add-in flavors
As you can see, Office Add-ins (Task Pane or Content) are available only on a
per-user basis, while Outlook Add-ins and SharePoint Add-ins can be purchased
on a per-user or a per-site basis. Note that an Outlook Add-in can be purchased
on a per-site basis only by an administrator, thereby making it available to all the
users of the organization.
All the paid or subscription options can have a trial period, if you want.
Whenever a customer acquires an add-in, whether it is for free, paid, or with a
subscription, the Office Store will require the customer to log in with a valid
Microsoft account and will generate a license file on the fly, downloading a
license token to the purchaser’s environment.
For an Office Add-in (Task Pane or Content), the license token will be stored
in the user’s Office client application. For an Outlook Add-in, the license token
will be downloaded and stored in the user’s mailbox for a per-user scope or in a
system mailbox at the tenant level for a per-site scope. For a SharePoint Add-in,
the license token is downloaded and stored in the SharePoint tenant or farm
deployment, depending on whether the SharePoint environment is in Office 365
or on-premises.
Furthermore, in the case of SharePoint Add-ins, only site, tenant, or farm
administrators can install an add-in. Thus, only those kinds of users can purchase
administrators can install an add-in. Thus, only those kinds of users can purchase
an app, and they can assign the purchased licenses to target users for whom they
purchased the licenses.
Whatever your scenario, a license token has an expiration and must be
renewed periodically. The only exceptions are the add-ins with a perpetual
license type, which have a token that does not expire. The license token
expiration model also supports the trial period for add-ins.
When a user launches an Office Add-in (Task Pane or Content), the Office
client application checks the license token and renews it if necessary and
possible. If the license token is expired and the license model is a trial, the user
will not be able to launch the add-in unless she converts the trial into a paid
license.
When a user launches Outlook and logs in, the Exchange server infrastructure
will check for the license token validity of any Outlook Add-in and will renew it
if necessary and possible.
For SharePoint Add-ins, the license token is verified and renewed by the
SharePoint App Management services by using a specific timer job. It is also
possible to renew license tokens manually, if needed.
LISTING 12-1 Sample outline of the XML license file for an Office Add-in
Click here to view code image
<r>
<t
aid="{2[A-Z] 8-12[0-9]}"
pid="{GUID}" | "string"
cid="{16[0-H]}"
oid="{GUID}"
did="{GUID}"
ts="Integer"
et="Free" |"Trial" | "Paid"
sl="true | 1" | "false| 0"
ad="UTC"
ed="UTC"
sd="UTC"
te="UTC"
test="true | 1" | "false | 0"
ss="0" | "1" | "2" | "3" | "4" />
<d>VNNAnf36IrkyUVZlihQJNdUUZl/YFEfJOeldWBtd3IM=</d>
</r>
As you can see, the schema is minimal and provides information about the
license type, the pricing model, and so on. In Table 12-2, you can see the list of
attributes with their meanings.
TABLE 12-2 Attributes for an Office Store license token XML file
In Listing 12-2, you can see a test XML license file for a SharePoint Add-in
licensed for 10 seats with a valid subscription active for one year from the date
of purchase.
LISTING 12-2 Sample test XML license token for a SharePoint Add-in
Click here to view code image
<r>
<t
aid="WA900006056"
pid="{F49ADBD9-6D1F-4396-B52C-9289ADC6DFF8}"
cid="32F3E7FC559F4F49"
did="{9EB13155-D449-430B-A1BC-7DBB95573F01}"
ts="10"
et="Paid"
test="true"
ad="2016-06-02T09:15:12Z"
ed="2017-06-02T09:15:12Z"
sd="2016-06-02T09:15:12Z"
te="2016-07-02T09:15:12Z"
ss="1" />
<d>VNNAnf36IrkyUVZlihQJNdUUZl/YFEfJOeldWBtd3IM=</d>
</r>
In the code sample of Listing 12-2, the encrypted token is fake, but it is a test
license token, so the encryption token will not be verified by the license server.
LISTING 12-4 Code excerpt validating an XML license token through the Office
Store license verification service
Click here to view code image
if (!result.IsValid) {
// Handle any license issue here
}
LISTING 12-5 Full code sample about how to handle the license verification
response
Click here to view code image
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.
As explained earlier, the license verification service is also available as a
REST endpoint, which can be invoked through an HTTP GET request against
the following URL:
https://verificationservice.officeapps.live.com/ova/verificationagent.svc/rest/verify?
token={decodedToken}
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.
Note
LISTING 12-6 Code excerpt to retrieve an XML license token for a SharePoint
Add-in
Click here to view code image
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
}
}
}
See Also
You can find further details about the ImportAppLicense method in
the document “Utility.ImportAppLicense method,” which is
available at the following URL: https://msdn.microsoft.com/en-
us/library/office/microsoft.sharepoint.client.utilities.utility.importapplicense.aspx
Note
You can find further details about Azure Redis Cache by reading
the document available at the following URL:
https://azure.microsoft.com/en-us/services/cache/.
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 the 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
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
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:
Browser hits The number of times your solution has been viewed in the
Office Store
Downloads The number of times your solution has been downloaded from
the Office Store
Trial downloads The number of times your solution has been purchased
for free from the Office Store
Trial conversions The number of times a trial has been converted into a
paid version of your solution
Purchased seats The overall number of seats that have been purchased for
your solution
Purchased site licenses The overall number of site licenses that have been
purchased for your solution
For SharePoint Add-ins only, you will also have the following metrics related
to the time frame of analysis:
Installs The overall number of install attempts
Launches The overall number of times the solution has been started
Daily unique users The sum of daily unique users for your solution
Uninstalls The overall number of uninstall attempts
Failed installs The overall number of failed installs, including any retries
Runtime errors The overall number of errors logged by SharePoint and
by the solution within its custom code
Failed upgrades The overall number of failed upgrades, including any
retries
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:
Logo of the company The logo that will be shown in the Office Store for
your company
Description The description of your business, useful in the Office Store
Website The URL of the website for your business
Marketing Contact Email The email address of a marketing contact in
your company, if any
Address The physical postal address of your company
Phone Number The phone number of your company
The Seller Dashboard will use all of these fields to build you company profile
in the Office Store.
Summary
In this final chapter, 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
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!
Index
A
Accept HTTP header, used with REST requests, 243–244
access tokens
accessing resources or services, 121
accessing SPO, 255
app-only, 291–292
Azure AD, 116–118
claims presented in JWT-formatted OAuth token, 118–120
consuming Graph API, 334
consuming SPO, 296–297
options for getting, 211
properties, 117
refreshing, 121
retrieving from ADAL cache, 134–135
activation rules
activating Outlook add-ins using Read Form rules, 331–333
Outlook add-ins, 326
Active Directory Authentication Library. see ADAL (Active Directory
Authentication Library)
Active Directory Authentication Library for JavaScript (ADAL.JS). see
ADAL.JS (Active Directory Authentication Library for JavaScript)
Active Directory Federation Services (ADFS), 95, 97
AD (Active Directory), Azure. see Azure AD (Azure Active Directory)
ADAL (Active Directory Authentication Library)
in ASP.NET MVC web application, 123
authenticating graph client, 212
installing for .NET, 131–132
leveraging, 127
overview of, 123
retrieving access tokens, 134–135, 211
supporting multitenancy, 125–127
supporting single tenancy, 123–125
ADAL.JS (Active Directory Authentication Library for JavaScript) consuming
Graph API, 336–338
getting access tokens for consuming Graph API, 334
referencing in Outlook add-in, 335–336
AddAsync method, adding resources to Graph SDK collections, 221
add-ins
activating in Outlook, 331–333
App Domain settings for Outlook add-in, 334–335
consuming Graph API, 336–338
creating content and task pane add-ins, 344–346
creating Excel add-ins, 347–349
creating Outlook add-ins, 324–325, 328–330
creating using Visual Studio 2015 templates, 32
creating with Yeoman generator, 339–342
interacting by email to quote request, 330
manifest file, 323–324
manifest file for Outlook add-in, 325–327
Office JavaScript APIs and, 342–344
overview of, 321–322
publishing by private corporations, 351–353
publishing Office add-ins, 355–359
publishing using file shares, 353
publishing using Office Store, 353
publishing using Seller Dashboard, 354–355
referencing ADAL.JS in Outlook add-in, 335–336
remote client development and, 47–49
searching for, 15
SharePoint Add-ins, 43–44
summary, 349
tools for creating, 322–323
types of Office add-ins, 339
updating or deleting, 361–362
viewing Outlook add-ins, 338
ADFS (Active Directory Federation Services), 95, 97
Admin Center
classic version, 23–25
new version, 20–23
for SharePoint, 27–29
Admin command, 15
admin section, of classic Admin Center, 25
administration
benefits of Organization Profiles, 25–27
classic Admin Center, 23–25
new Admin Center, 20–23
Office 365 Video tools, 12
SharePoint Admin Center, 27–29
tools for, 19–20
Android, platforms supporting Graph SDK, 209
AngularJS, creating add-ins, 339–340
anonymous access, to REST API, 256
APIs (application programming interfaces)
.NET client library for Office 365, 56
Graph API. see Graph API
REST APIs. see REST APIs
SharePoint. see SharePoint REST API
App Domain, Outlook add-ins, 327, 334–335
app launcher
customizing, 26–27
main apps in, 6
pinning new application to, 274
application programming interfaces. see APIs (application programming
interfaces)
applications/apps
ADAL use in web application, 123
adding application to Azure AD tenant, 102
app-only form of authorization registration, 274–276
authenticating web applications, 132
Azure AD management UI, 100
basic UI elements, 279–281
checking for groups with Graph API, 299–300
configuring Azure AD apps, 100–102
configuring Azure AD permissions, 105, 278–279
connectors and, 313
consuming Graph API, 297
consuming SPO, 296–297
creating and enqueueing messages in Blob Queue, 308–310
creating and registering, 272
creating and registering webhooks for, 314–316
creating ASP.NET application for use with mail service, 131
creating asynchronous jobs, 302
creating responsive grid, 287–288
creating/configuring groups with Graph API, 298–299
customizing components and styles, 288–290
developing, 37–38
developing web applications, 38
extending SPO UI, 291–293
full-page web applications, 38–40
.JSON manifest file of Azure AD application, 276–278
main apps in app launcher, 6
message triggering function in Blob Storage Queue, 305–307
mimicking suite bar, 281–284
native applications, 41
navigation bar, 286–287
PowerApps, 14–15
provisioning SharePoint artifacts, 293–296
publishing Azure App Service, 311–312
publishing Azure WebJob, 312–313
publishing by private corporations, 351–353
publishing on Azure, 310
publishing using file shares, 353
publishing using Office Store, 353
publishing using Seller Dashboard, 354–355
publishing web applications and add-ins, 361
registering app for use with Graph SDK, 210
registering apps using Visual Studio, 107–110
registering Azure AD apps, 273–274
remote timer job App.Config file, 308
remote timer job Main method, 307–308
remote timer jobs generally, 302–303
remote timer jobs in Azure, 303–305
rendering profile picture for current user, 284–285
searching for, 15
sending email messages using web application, 147–148
sending notifications/messages to groups, 300–301
settings section of Admin Center, 22
single-page web applications, 40–41
solution tasks, 271–272
summary, 316–319
updating or deleting web applications or add-ins, 361–362
web applications and web APIs, 40
writing connectors for, 316–319
app-only
access tokens, 291–292
authorization registration, 274–276
creating remote timer jobs, 45
Apps section, SharePoint Admin Center, 28
App-V, 16
arithmetic operators, in OData queries, 245–246
artifacts, provisioning SharePoint artifacts, 293–296
ASP.NET applications. see also web applications
ADAL use in, 123
creating and registering, 272
creating for use with mail service, 131
developing, 38
publishing, 310–313
sending email messages, 147–148
asynchronous jobs
creating, 302
creating and enqueueing message in Blob Queue, 308–310
message triggering function in Blob Storage Queue, 305–307
remote timer job App.Config file, 308
remote timer job Main method, 307–308
remote timer jobs, 302–303
remote timer jobs in Azure, 303–305
attachments, email, 145–147
authentication
Azure AD providing, 54, 95
interface for Graph SDK, 210–211
of new application, 107–108
OpenID Connect communication flow, 111–113
REST API security and, 254–256
retrieving authentication cookies, 239
v2 authentication endpoint, 210
of web application, 132
authentication library, Azure AD. see ADAL (Active Directory Authentication
Library)
authorization. see also OAuth 2.0
accessing navigation properties, 175
app-only form of, 274–276
Azure AD endpoints and, 114
Azure AD providing, 54, 95
getting Azure AD authorization codes, 114–116
managing groups, 180
OAuth 2.0 framework, 110–111
Startup.Auth.cs file handling OAuth 2.0 authorization flow, 132–133
Azure
Blob storage, 45
publishing applications, 310–313
remote timer jobs, 303–305
subscription requirements, 31–32
Azure AD (Azure Active Directory)
access tokens, 116–118
accessing resources or services, 121
Admin Center, 23
authentication, 254–255
authentication and authorization provided by, 54
authentication library. see ADAL (Active Directory Authentication Library)
authentication of graph client, 212
authorization codes, 114–116
comparing version features, 96
configuring apps and services, 100–102
configuring group permissions, 105–106
configuring multitenancy apps, 106–107
identity types, 97–98
introduction to, 95–96
.JSON manifest file of registered application, 276–278
JWT-formatted OAuth token and, 118–120
management UI, 103–105
managing identities, 98–100
navigation properties, 175
Office 365 based on, 4–5
OpenID Connect and OAuth and, 110–111
OpenID Connect communication flow, 111–113
OpenID Connect inspected using Fiddler, 113–114
publishing web applications, 361
querying groups, 178–179
refreshing access tokens, 121
registering apps, 107–110, 273–274
registering apps for use with Graph SDK, 210
retrieving authentication cookies, 239
security flow in, 112
setting permissions, 278–279
summary, 127
user object navigation in Azure AD vs. SPO, 175
Azure AD B2C, 95
Azure App Service
creating full page web applications, 38–40
publishing, 311–312
Azure Media Services, 13
Azure WebJob
overview of, 45
publishing, 312–313
B
background images, customizing Office 365 using Organization Profiles, 26
BCS (Business Connectivity Services) section, SharePoint Admin Center, 28
billing administrators, administrative roles, 19
billing section, Admin Center, 22, 24
Blob storage, Azure
creating account for, 304
creating and enqueueing message in, 308–310
message triggering function in Blob Storage Queue, 305–307
overview of, 45
Business Connectivity Services (BCS) section, SharePoint Admin Center, 28
business model, SaaS (Software as a Service), 3
C
calendar property, navigation properties, 186
calendars
accessing group calendar, 92, 186
adding events to, 76–77
browsing calendar views, 155
consuming services in Graph API, 73–78
creating and updating events, 159–162
defining CalendarList type, 150
Event and EventList types, 152–155
GetCalendar method, 150–151
as Graph API service, 55
ListCalendars method, 149–150
ListEvents method, 151
managing meeting invitations, 162
managing series of events, 156–159
retrieving group calendar view, 187
retrieving group content, 230–231
CDN (content delivery network), 280–281
charts, in Planner app, 9
claims, JWT-formatted OAuth token, 118–120
Click-to-Run installation, 16–17
clients
creating (GraphServiceClient), 211–214
defining (GraphServiceClient), 214–215
developing, 47–50
mapping Outlook client versions to API versions, 325
cloud identities, Azure AD, 97–98
cloud storage, 7. see also OD4B (OneDrive for Business)
collaboration, via S4B, 5
collections
adding resources to Graph SDK collections, 221–223
paging Graph SDK collections, 218–220
storing video in SPO site collections, 13
colors, customizing Office 365, 26
Command, Office Add-ins, 48
company profiles, in classic Admin Center, 24
compliance, Admin Center and, 22–23
components, custom components available in Office UI fabric, 288–290
concurrency conflicts
avoiding, 249
handling in Graph SDK for .NET, 225–226
managing data with SharePoint REST API, 253
conference calls, via S4B, 5
configuration options, Azure AD management UI, 100
Configure Hybrid section, SharePoint Admin Center, 28
configuring Azure AD
apps and services, 100–102
group permissions, 105–106
management UI options, 103–105
multitenancy apps, 106–107
registering app using Visual Studio, 107–110
connectors
creating, 41–42
creating and registering webhooks, 314–316
overview of, 313
writing, 316–319
Connectors Developer Dashboard, 317
Connectors Sandbox, 319
contact services
adding contacts, 169
consuming in Graph API, 71–72
defining Contact and ContactList types, 164–167
deleting contacts, 169
as Graph API services, 55
helper methods, 167–168
managing meeting invitations, 162
overview of, 163
retrieving contacts, 167
updating contacts, 168
contacts section, in classic Admin Center, 24
Content Add-in
creating, 344–346
types of Office add-ins, 48, 339
content delivery network (CDN), 280–281
controls, customizing application controls, 288–290
conversations
replying to conversation threads of group, 185–186
retrieving content of Office 365 Group using Graph SDK, 229
retrieving conversation threads of group, 184–185
conversations property, navigation properties, 184–185
Corporate Catalog, publishing via, 351–352
corporations, publishing applications and add-ins, 351–353
CORS (cross-origin sharing issues), SPO (SharePoint Online), 40
cross-domain calls, SharePoint REST API, 253–254
cross-origin sharing issues (CORS), SPO (SharePoint Online), 40
CSOM (client-side object model)
accessing objects, properties and methods, 237
accessing REST classes and members, 241
consuming SPO using delegated permissions, 296–297
interacting with SPO to create artifacts and customizations, 291–293
interacting with SPO via remote jobs, 44–45
remote provisioning and, 46
CSS, options for creating add-ins, 321–322, 339
Custom Tiles section, of Organization Profiles, 27
D
dashboards
building with Power BI, 10
in classic Admin Center, 23
Connectors Developer Dashboard, 317
Seller Dashboard, 353–355, 372–373
data management, with OData and REST API
concurrency conflicts, 253
overview of, 249–250
updating list title using JavaScript, 250–252
data visualization, via Power BI, 10
delegated permissions
configuring Azure AD, 105–106
consuming SPO using, 296–297
DeleteAsync method, deleting resources in Graph SDK, 224
Delve Analytics, 12
Delve service, 11–12
Developer PnP (Patterns and Practices). see PnP (Patterns and Practices)
Developer Program, signing up for, 31
development
of applications, 37–38
configuring development machine, 32
creating connectors, 41–42
of full-page web applications, 38–40
of native applications, 41
Office 365 Developer PnP in, 33–36
of Office clients, 47–50
preparing for SharePoint framework, 36–37
remote event receivers for SPO, 45
remote provisioning for SPO, 46–47
remote timer jobs for SPO, 44–45
setting up developer tenant, 31–32
setting up development environment, 31
SharePoint Add-ins, 43–44
of single-page web applications, 40–41
SPO (SharePoint Online), 42
web APIs and, 40
of web applications, 38
devices section, Admin Center, 25
direct reports
managing with Graph SDK, 232–233
retrieving, 175–176
Directory Sync status, in Admin Center, 23
directory systems, 4–5. see also Azure AD (Azure Active Directory)
disk drives
accessing personal drive of current user, 191–192
defining DriveItem type, 193–194
moving DriveItem type within OneDrive for Business, 203
retrieving children items of target folder in specific drive, 194–195
Document Object Model (DOM), 43
documents
checking in and checking out, 265–267
creating new document library, 263
deleting, 267
querying list of, 268
uploading or updating, 264–265
working with via web browser, 7–8
DOM (Document Object Model), 43
domains
Azure AD management UI, 100
cross-domain calls, 253–254
domains section, Admin Center, 22, 24
drive property, navigation properties, 187
DriveItem type
defining, 193–194
deleting permissions for within OneDrive for Business, 205
downloading files in OneDrive for Business, 235–236
getting permissions for within OneDrive for Business, 204–205
moving within OneDrive for Business, 203
searching files in OneDrive for Business, 235
sharing link for in OneDrive for Business, 206
uploading files to OneDrive for Business, 234
drives (disk). see disk drives
drives, retrieving group drive, 187–188
dynamic groups, 178
E
ECB menu item, 291–293
email
accessing, 65–66
consuming using Graph API, 64–65
deleting messages, 70
replying to, 148–149
responding to quote request, 330
retrieving attachments, 145–147
sending messages, 67–69, 147–148
sending messages using Graph SDK, 227
endpoints
Azure AD, 113–114
Graph API groups endpoint, 177
v2 authentication endpoint, 210
equipment, in resources section of Admin Center, 21
errors, exception handling in Graph SDK, 225–226
events
adding to calendar, 76–77
consuming services in Graph API, 73–78
creating series, 160–161
creating single instance, 159–160
deleting, 162
Event and EventList types, 152–155
invitations to, 78–79
ListEvents method, 151
managing series (EventRecurrence type), 156–158
retrieving events from series of events, 158–159
retrieving group content, 229–230
retrieving single event from series of events, 161
retrieving within date range, 155
updating, 162
Excel, Office Add-ins, 47–49, 347–349
exception handling, Graph SDK for .NET, 225–226
Exchange, 5. see also EXO (Exchange Online)
Exchange, cloud based SaaS version of, 5
exchange administrators, administrative roles, 19
EXO (Exchange Online)
accessing Admin Center for, 23
consuming mail, contacts, and calendars, 63–64
quick tour of Office 365 services, 5
extensions
creating custom extensions for groups, 41–42
PnP, 33
SPO UI, 291–293
external sharing section, Admin Center, 24
F
federated identities, Azure AD, 97–98
federation metadata document, Azure AD endpoints, 113
Fiddler
inspecting OpenID Connect, 113–114
testing and developing REST APIs, 56, 240
tracing HTTP(S) communication flow, 115
File services
accessing personal drive of current user, 191–192
browsing for files and folders, 192–193
consuming file contents from OD4B, 195–197
creating new folders, 199
defining DriveItem type, 193–194
deleting files, 201
deleting permissions for DriveItem, 205
getting permissions for DriveItem, 204–205
as Graph API service, 54
moving DriveItem type, 203
retrieving children items of target folder in specific drive, 194–195
retrieving image file of thumbnail, 198
sharing files, 206
sharing link for target DriveItem, 206
summary, 207
updating existing files, 202–203
uploading file into target folder, 201–202
file shares, options for publishing applications and add-in, 353
files
browsing for, 192–193
consuming file contents from OD4B, 195–197
creating, 234
deleting, 201
downloading, 235–236
managing, 85–87
querying, 80–84
retrieving all files in root folder of group, 229
retrieving image file of thumbnail, 198
searching, 88–89, 235
sharing, 89–90, 206, 353
updating existing, 202–203
uploading into target folder, 201–202
uploading to OD4B, 234
filters, 173, 216–217
folders
browsing for, 192–193
creating, 199
creating new file in OD4B root folder, 234
defining email folder types, 140–141
enumerating email folders in mailbox, 140
managing, 85–87
querying, 80–84
retrieving all files in group root folder, 229
retrieving children items of target folder, 194–195
retrieving email messages, 141–142
retrieving folder and children items, 218
searching, 88–89
sharing, 89–90
uploading files into target folder, 201–202
forward, replying to email, 148–149
frameworks
OAuth 2.0 framework, 110–111
PnP offerings, 33, 45
preparation for SharePoint framework, 36–37
functions
checking in and checking out documents, 265–267
creating list items, 259
creating new document library, 263
deleting documents, 267
deleting list items, 261–262
message triggering, 305–307
in OData queries, 246–248
querying list of documents, 268
querying list of items, 262
updating list items, 260
uploading or updating documents, 264–265
G
GitHub, 55
global administrators, administrative roles, 19–20
Graph API
access tokens for, 134–135, 334
accessing resources or services, 121
Azure AD endpoints, 114
checking for groups, 299–300
consuming and managing groups, 9, 298–299
consuming calendars and events, 73–78
consuming contacts, 71–72
consuming groups, 63
consuming mail, 64–71
consuming OD4B, 79
consuming users, 59–63
consuming using ADAL.JS, 336–338
consuming using .NET and C#, 129
event invitations, 78–79
groups endpoint of, 177
HTTP-related methods, 137–139
leveraging OAuth access token, 135–137
managing files and folders, 85–87
metadata, 57–58
preparing for SharePoint framework, 36–37
querying files and folders, 80–84
rendering profile picture for current user, 284–285
replying to email, 148–149
searching files and folder, 88–89
secure access, 116–118
security. see Azure AD (Azure Active Directory)
sending email messages, 147–148
sending notifications/messages to groups, 300–301
setting up environment for, 131–132
sharing files and folder, 89–90
Startup.Auth.cs file for handling OAuth 2.0 authorization flow, 132–133
summary, 93
what it is, 53–56
working with groups, 90–92
Graph Explorer, testing and developing REST APIs, 56–57
Graph SDK for .NET
adding resources to collections, 221–223
basic queries, 216–218
creating clients (GraphServiceClient), 211–214
defining authentication provider interface (IAuthenticationProvider), 210–211
defining clients (GraphServiceClient), 214–215
deleting resources, 224
downloading files, 235–236
handling exceptions and concurrency, 225–226
handling group content, 228–231
library, 55–56
managing manager and direct reports of current user, 232–233
managing photo of current user, 231–232
overview of, 209
paging collections, 218–220
registering apps, 210
request model for, 214–216
searching files, 235
searching groups, 228
sending email messages, 227
summary, 236
updating resources, 224
uploading files, 234
graphs (Office Graph), 11
GraphServiceClient
constructor arguments, 211–212
creating class, 212–213
defining, 214–215
interface-level definition, 214–215
grids, creating responsive grid for UI fabric, 287–288
groups
accessing calendar, 186
accessing calendar of a group, 92
adding members, 181
Azure AD management UI, 99
Azure AD permissions, 105–106
checking for, 299–300
consuming services in Graph API, 63, 90–92
creating, 188–189
creating/consuming, 298–299
deleting, 190
Graph API services, 54
handling content, 228–231
maintaining, 180
permissions for adding, 223
querying, 178–179, 182–183
removing members, 182
retrieving by ID, 179
retrieving group drive, 187–188
retrieving group picture, 183–184
retrieving members and owners, 180–181
searching, 228
setting group picture, 189
types of, 178
Groups (Office 365). see also Unified Groups
accessing group calendar, 186
adding owners and members, 222–223
adding webhooks to, 315
capabilities of, 184
checking for groups, 299–300
connectors for, 314
creating groups, 188–189
creating/consuming groups, 298–299
custom extensions for, 41–42
deleting groups, 190
handling group content, 228–231
overview of, 182
permissions for adding new group, 223
querying groups, 182–183
quick tour of Office 365 services, 8–9
registering new connectors, 317–319
replying to conversation threads, 185–186
retrieving calendar view, 187
retrieving conversation threads, 184–185
retrieving group drive, 187–188
retrieving group picture, 183–184
searching groups, 228
sending notifications/messages, 300–301
setting group picture, 189
summary, 189
groups section, Admin Center, 21, 24
Groups services
adding members to groups, 181
defining datatype (GroupType), 177–178
defining members (GroupMemberToAdd ), 181–182
maintaining groups, 180
managing membership, 181
overview of, 177
querying groups, 178–179
removing members from groups, 182
retrieving group by ID, 179
retrieving group members and group owners, 180–181
summary, 190
types of groups, 178
GroupType
defining, 177–178
Unified value, 182–183
guests, 173–174
Gulp tool, 37
H
helper methods
checking existence of groups, 299–300
creating and configuring groups, 298–299
sending notifications/messages to groups, 300–301
helper types, PnP offerings, 33
home page, Admin Center, 21
hosting models, for software, 3–4
HTML, creating add-ins, 321–322, 339
HTTP DELETE
deleting files and folders, 87
deleting group member, 182
deleting groups, 189–190
deleting messages, 70
methods and headers available for use with REST API, 239
HTTP GET
accessing calendar of a group, 92
accessing email, 65–66
consuming calendars, 73–74
consuming contacts, 71–72
enumerating users in current tenant, 172–173
Graph SDK request model and, 214–216
methods and headers available for use with REST API, 238
querying files and folders, 80–84
searching for files and folder, 88–89
HTTP MERGE, 238
HTTP PATCH, 87, 238
HTTP POST
accepting meeting request, 79
adding events to calendar, 76–77
creating groups, 188
deleting list items, 261–262
getting access tokens, 117
managing files and folders, 85–87
methods and headers available for use with REST API, 238
sending email, 67–69
sharing files and folder, 89–90
HTTP PUT, 238
HTTP(S)
accessing user profile, 59–63
avoiding concurrency conflicts, 249
leveraging Graph API, 55
methods and headers available for use with REST API, 238–239
methods for graph helper class, 137–139
security flow summary, 122
I
IaaS (Infrastructure as a Service), 3–4
IAuthenticationProvider interface, authentication interface for Graph SDK,
210–211
ICollectionPage interface, paging collections, 219–220
icons, customizing using Organization Profiles, 26
ID, retrieving group by, 179
identities, Azure AD
managing, 98–100
types, 97–98
image files, retrieving image file of thumbnail, 198
import capability, Admin Center, 24
InfoPath section, SharePoint Admin Center, 28
Infrastructure as a Service (IaaS), 3–4
interfaces
application programming. see APIs (application programming interfaces)
authentication provider (IAuthenticationProvider), 210–211
interface-level definition of GraphServiceClient, 214–215
paging graph collections (ICollectionPage), 219–220
user interface. see UI (user interface)
iOS, platforms supporting Graph SDK, 209
J
JavaScript
APIs used with add-ins, 342–344
creating add-ins, 321–323, 339
cross-domain calls, 253–254
customizing SharePoint UI, 43–44
developing remote clients, 48
development projects and, 37
function for checking in and checking out documents, 265–267
function for creating list items, 259
function for creating new document library, 263
function for deleting documents, 267
function for deleting list items, 261–262
function for querying list of items, 262
function for updating list items, 260
function for uploading or updating documents, 264–265
function querying list of documents, 268
security form digest required for operations that modify data, 241
updating list title, 250–252
JSON
Accept HTTP header used with REST requests, 243–244
accessing group calendar, 92
consuming calendars and events, 73–78
consuming contacts, 71–72
consuming mail messages, 64–70
consuming users, 59–63
ContextInfo method invocation, 241–242
creating lists, 258
.JSON manifest file of Azure AD application, 276–278
searching files and folder, 88–89
sharing files and folder, 90
support for JSON Light format, 243
working with groups, 91
JWT
claims presented in JWT-formatted OAuth token, 118–120
token decoder, 118
L
libraries
Azure authentication library. see ADAL (Active Directory Authentication
Library)
creating new document library, 263
Graph SDK library, 55–56
SharePoint core packages, 34
library forms, custom SharePoint solutions, 44
licenses
Azure AD management UI, 100
best practices, 371–372
checking for valid, 17
enforcing license checks, 364
enforcing license checks for Office add-ins, 366–369
enforcing license checks for SharePoint add-ins, 369–371
license XMl file, 364–366
models for, 362–363
options, 17–19
PCs/Macs, 16
lists
creating, 258
creating/updating list items, 259–261
custom SharePoint solutions, 44
defining mail lists, 142–145
deleting lists, 261–262
querying list items, 250, 262
retrieving list of users, 174–175
updating list title, 250–252
logical operators, in OData queries, 245–246
logon, Single Sign-On (SSO), 97, 104
logos (corporate), customizing, 26
M
Macs
running Office 365 on, 16
as SharePoint development machine, 37
Mail Add-in
creating, 340–342
types of Office add-ins, 339
mail services
consuming, 64–71
defining email folders, 140–141
defining mail lists and mail message types, 142–145
enumerating email folders, 140
as Graph API service, 55
overview of, 140
replying to email, 148–149
retrieving email attachments, 145–147
retrieving email messages, 141–142
sending email messages, 147–148
Manage Add-in page, OWA (Outlook Web Access), 342
management UI, Azure AD
configuring Azure AD, 103–105
Users management panel, 99
manager reports
managing with Graph SDK, 232–233
retrieving, 175–176
manifest files
add-ins, 47
.JSON manifest file of Azure AD application, 276–278
Office add-ins, 323–324
Outlook add-ins, 325–327
MCSMs (Microsoft Certified Solution Masters), 33
meeting rooms section, Admin Center, 24
meetings
accepting meeting request, 79
Skype Meeting Broadcast, 6–7
members property, navigation properties for groups, 180
membership, in groups
adding members, 181, 222–223
defining GroupMemberToAdd type, 181–182
managing members, 181
removing members, 182
retrieving members, 180–181
message center section, Admin Center, 23, 25
messages. see also email
creating and enqueueing in Blob Storage, 308–310
defining mail message types, 142–145
sending notifications/messages to groups, 300–301
triggering functions in Blob Storage Queue, 305–307
metadata, OData 4.0 protocol, 57–58
methods, REST API reference, 240–241
metrics, Seller Dashboard, 372–373
Microsoft
application virtualization. see App-V
Azure. see Azure
Exchange. see Exchange
Exchange Online. see EXO (Exchange Online)
Graph API. see Graph API
OneDrive for Business. see OD4B (OneDrive for Business)
Power BI. see Power BI
SharePoint Online. see SPO (SharePoint Online)
Skype for Business. see Skype for Business
Visual Studio 2015. see Visual Studio 2015
Microsoft application registration portal, 210
Microsoft Certified Solution Masters (MCSMs), 33
Microsoft Most Valuable Professionals (MVPs), 33
Microsoft.NET, platforms supporting Graph SDK, 209
Model-View-Controller pattern. see MVC (Model-View-Controller) pattern
MSAL (Microsoft Authentication Library)
access tokens, 211
authentication of graph client, 212–213
MSI (Windows installer), 16–17
MVC (Model-View-Controller) pattern
developing ASP.NET applications, 38
sending email messages, 147–148
MVPs (Microsoft Most Valuable Professionals), 33
N
Napa SharePoint Add-in, 322
native applications, 41
navigation bar, 286–287
navigation properties
calendar property, 186
conversations and threads properties, 184–185
drive property, 187–188
members and owners properties, 180–181
of user objects, 175–176
.NET
ASP.NET applications. see ASP.NET applications
Graph SDK. see Graph SDK for .NET
installing ADAL for .NET, 131–132
OWIN (Open Web Interface for .NET), 132–133
NextGen Portals
Office Delve feature, 11
Office Video 365 feature, 12
Node.js, 37
notifications, 300–301. see also messages
NPM package manager, 37
NuGet Package Manager
in development, 34
Graph SDK library, 55
installing ADAL for .NET, 131–132
installing .css and.js files for UI fabric, 280–281
O
OAuth 2.0
access tokens for Graph API, 134–135
App-Only OAuth token, 45
authentication and authorization, 95
authorization framework, 110–111
Azure AD endpoints, 114
claims presented in JWT-formatted OAuth token, 118–120
consuming SPO using delegated permissions, 296–297
consuming using ADAL, 123
handling authorization flow, 132–133
REST API security and, 254–256
security flow summary, 122
objects
browsing client properties, 213–214
creating clients (GraphServiceClient), 211–214
OD4B (OneDrive for Business)
consuming file contents, 195–197
consuming Graph API services, 79
creating folders, 199
dedicated folder for Office 365 Groups, 187
deleting DriveItem permissions, 205
deleting files, 201
downloading files, 235–236
getting DriveItem permissions, 204–205
managing files and folders, 85–87
moving DriveItem type, 203
querying files and folders, 80–84
quick tour of Office 365 services, 7
retrieving thumbnail of image file, 198
searching files and folder, 88–89, 235
sharing files and folder, 89–90
sharing link for target DriveItem, 206
uploading files, 234
working with documents stored in, 7–8
OData 4.0 protocol
concurrency conflicts, 253
functions in queries, 246–248
Graph API compliance with, 57, 216
implementing compliant endpoints, 237
logical and arithmetic operators for queries, 245–246
overview of, 249–250
query parameters, 245
querying data, 244
updating list title, 250–252
Office 365 Admin Center. see Admin Center
Office 365 Business Essentials, subscription plans, 18
Office 365 Business Premium, subscription plans, 18
Office 365 Business, subscription plans, 18
Office 365 Connectors. see connectors
Office 365 Developer PnP (Patterns and Practices). see PnP (Patterns and
Practices)
Office 365 Developer Program, 31
Office 365 Education, subscription plans, 18
Office 365 Enterprise (E1, E3, and E5), subscription plans, 18
Office 365 Government (E1, E3, and E4), subscription plans, 18
Office 365 Groups. see Groups (Office 365)
Office 365 Home, subscription plans, 18
Office 365 Nonprofilt Business Essentials, subscription plans, 18
Office 365 Nonprofilt Business Premium, subscription plans, 18
Office 365 Nonprofilt (E1 and E3), subscription plans, 18
Office 365 Personal, subscription plans, 18
Office 365 ProPlus, subscription plans, 18
Office 365 Public Roadmap, 15
Office 365, quick tour
accessing services, 5
Admin Center, classic version, 23–25
Admin Center for SharePoint, 27–29
Admin Center, new version, 20–23
Admin command, 15
administrative tools, 19–20
Azure AD and, 4–5
Click-to-Run installation, 16–17
EXO (Exchange Online) services, 5
licensing and subscription plans, 17–19
NextGen Portal, 12
OD4B (OneDrive for Business), 7
Office 365 Groups, 8–9
Office 365 Video, 12–13
Office Delve, 11–12
Office Graph, 11
Office Sway, 13–14
Organization Profiles, 25–27
Planner app, 9
Power BI, 10
PowerApps, 14–15
running on PC or Mac desktops, 16
S4B (Skype for Business), 5
searching for add-ins and applications, 15
Skype Meeting Broadcast, 6–7
SPO (SharePoint Online), 7–8
summary, 27–29
what it is, 3–4
Yammer, 10
Office add-ins. see also add-ins
activating in Outlook, 331–333
App Domain settings, 334–335
consuming Graph API, 336–338
creating content and task pane add-ins, 344–346
creating Excel add-in, 347–349
creating Outlook add-ins, 324–325, 328–330
creating using Yeoman generator, 339–342
email response to quote request, 330
enforcing license checks, 366–369
licensing models, 363
manifest file, 323–324
manifest file for Outlook, 325–327
metrics, 373
Office JavaScript APIs, 342–344
overview of, 321–322
publishing, 355–359
publishing by private corporations, 352
publishing via file share, 353
publishing with Seller Dashboard, 355
referencing ADAL.JS in, 335–336
summary, 349
tools for creating, 322–323
types of, 339
viewing Outlook add-in in Outlook Web Access, 338
Office clients. see clients
Office Delve, 11–12
Office Delve Analytics, 12
Office Developer Tools for Visual Studio, 328–330
Office Graph, 11
Office Licensing Service, 17
Office Profile, 373
Office Store
license XMl file, 364–366
publishing applications and add-in, 353
searching for applications and add-ins, 15
Office Sway, 13–14
on-premises, hosting models, 3–4
Open Web Interface for .NET (OWIN), 132–133
OpenID Connect
authentication and authorization, 95
authentication flow, 133–134
communication flow, 111–113
consuming using ADAL, 123
inspecting with Fiddler, 113–114
preferred for authentication of Azure AD, 110–111
security flow summary, 122
operators, logical and arithmetic, 245–246
Organization Profiles, 25–27
Outlook add-ins
activating, 331–333
App Domain settings, 334–335
consuming Graph API, 336–338
creating, 324–325, 328–330
interacting by email to quote request, 330
manifest file, 325–327
overview of, 48–49
publishing by private corporations, 351–352
publishing with Seller Dashboard, 355
referencing ADAL.JS in, 335–336
viewing in Outlook Web Access, 338
Outlook Web Access. see OWA (Outlook Web Access)
OWA (Outlook Web Access)
creating Outlook add-ins, 324–325
Manage Add-in page, 342
rendering output of Outlook add-in, 330
viewing Outlook add-ins, 338
OWIN (Open Web Interface for .NET), 132–133
owners property, navigation properties for groups, 180–181
ownership, of groups
adding owners, 222–223
retrieving owners, 180–181
P
PaaS (Platform as a Service)
Azure leveraging capabilities of, 310
hosting models, 3–4
packages
NPM package manager, 37
NuGet Package Manager. see NuGet Package Manager
SharePoint core, 34
UI Fabric package. see UI Fabric package
paging Graph SDK collections, 218–220, 228
Partner Pack, PnP (Patterns and Practices), 35–36, 275
partner relationships, Admin Center, 22
password administrators, administrative roles, 19
patches, Click-to-Run and, 17
Patterns and Practices. see PnP (Patterns and Practices)
PCs
development machines, 37
running Office 365 on, 16
permissions
adding groups, 223
configuring Azure AD, 104–105, 278–279
delegated permissions, 105–106, 296–297
deleting DriveItem permissions, 205
getting DriveItem permissions, 204–205
PHAs (provider-hosted applications), 38–40
photographs
managing photo of current user, 231–232
rendering profile picture for current user, 284–285
retrieving group picture, 183–184
retrieving user photo, 176–177
setting group picture, 189
uploading for groups, 221–222
Planner app, 9
PnP (Patterns and Practices)
developer support, 33
OfficeDev logo, 273
Partner Pack, 35–36, 275
PowerShell cmdlets, 36
provisioning SharePoint artifacts, 293–296
Remote Provisioning Engine, 35, 46
remote timer job framework, 45
SharePoint core library packages, 34
Power BI, 10
PowerApps, 14–15
PowerPoint (Microsoft), Office Add-ins, 49–50
PowerShell
administering SPO, 29
automating application configuration, 275
creating KeyCredentials array from X.509 certificate, 278
invoking EnsureUser method, 242–243
preparing SharePoint framework, 36
reading title of list instance, 239
remote provisioning, 46–47
presentations
creating with Office Sway, 13–14
via Skype Meeting Broadcast, 6–7
privacy settings, Admin Center, 22
profiles
accessing user profile, 59–63
listing users by attributes, 174–175
Office Profile, 373
Organization Profile, 25–27
rendering picture for user profile, 284–285
programming model, 51–52
provider-hosted applications (PHAs), 38–40
Public Roadmap, keeping current with apps and features, 15
public websites section, Admin Center, 24
Publish Web Wizard, 310
publishing
on Azure, 310
Azure App Service, 311–312
Azure WebJob, 312–313
enforcing license checks, 364
enforcing license checks for Office add-ins, 366–369
enforcing license checks for SharePoint add-ins, 369–371
file shares for, 353
licensing best practices, 371–372
metrics and, 372–373
Office add-ins, 355–359
Office Profile and, 373
Office Store for, 353
options for, 351
private corporate, 351–353
Seller Dashboard for, 354–355
SharePoint add-ins, 359–360
summary, 373–374
types of licenses and, 362–363
updating or deleting web applications or add-ins and, 361–362
web applications, 361
XML license file, 364–366
purchase services section, Admin Center, 25
Q
queries
files and folders, 80–84
Graph SDK, 216–218
groups, 178–179, 182–183
list items, 262
list of documents, 268
logical and arithmetic operators in, 245–246
OData functions in, 246–248
paging collections, 218–220
query parameters, OData 4.0, 245
query string parameters, SharePoint, 249
R
Read Form rules, activating Outlook add-ins, 331–333
Records Management section, SharePoint Admin Center, 28
refresh token, 122
registration, of applications/apps
app-only authorization, 274–276
Azure AD apps, 273–274
connectors, 317–319
creating applications and, 272
for Graph SDK, 210
.JSON manifest file of Azure AD application, 276–278
setting Azure AD permissions, 278–279
Visual Studio 2015 use for, 107–110
webhooks, 314–316
remote event receivers (RERs), 44–45
Remote Provisioning Engine, PnP, 35, 46
remote provisioning, SPO, 46–47
remote timer jobs
App.Config file, 308
in Azure, 303–305
Main method, 307–308
overview of, 302–303
SPO (SharePoint Online), 44–45
reply, email options, 148–149
replyAll, email options, 148–149
reports
managing manager and direct reports, 232–233
options of Azure AD management UI, 100
retrieving manager and direct reports, 175–176
reports section, Admin Center, 22, 25
Representational State Transfer APIs. see REST APIs
request model, underlying Graph SDK, 214–216
RERs (remote event receivers), 44–45
resources
accessing Azure AD resources, 121
adding to Graph SDK collections, 221–223
deleting in Graph SDK, 224
updating in Graph SDK, 224
resources section, Admin Center, 21–22
responsive grid, 287–288
REST APIs
classes and members, 241
compliance with OData v4, 216
Graph. see Graph API
hosting custom, 40
managing Office 365 Groups, 9
.NET client library for Office 365, 56
Planner app and, 9
preparing for SharePoint framework, 36–37
SharePoint. see SharePoint REST API
rooms section, Admin Center, 21
S
S4B (Skype for Business)
accessing Admin Center for, 23
administrative roles, 19
overview of, 5
SaaS (Software as a Service), 1, 3–4
SAML (Security Assertion Markup Language), 110–111, 113
scripts. see PowerShell
Search section, SharePoint Admin Center, 28
searches
files, 235
files and folders, 88–89
groups, 228
Secure store section, SharePoint Admin Center, 28
security
accessing Admin Center, 23
reports, 22
settings, 22
SharePoint REST API, 254–256
summarizing security flow in Azure AD, 112
Security Assertion Markup Language (SAML), 110–111, 113
security groups
consuming services, 63
querying, 178–179
types of groups, 178
Seller Dashboard
creating account on, 353
metrics, 372–373
publishing applications and add-ins, 354–355
SendMail method, Graph SDK, 227
service administrators, administrative roles, 19
service health section, Admin Center, 22, 25
ServiceException type, exception handling in Graph SDK, 225–226
services
accessing, 5
accessing Azure AD, 121
availability determined by subscriptions and licenses, 17–18
EXO (Exchange Online) services, 5
list of main services, 53
NextGen Portal, 12
OD4B, 7
Office 365 Groups, 8–9
Office 365 Video, 12–13
Office Delve, 11–12
Office Graph, 11
Office Sway, 13–14
Planner app, 9
Power BI, 10
PowerApps, 14–15
S4B, 5
searching for add-ins and applications, 15
Skype Meeting Broadcast, 6–7
SPO, 7–8
Yammer, 10
services section, Admin Center, 25
settings section, Admin Center, 22
Settings section, SharePoint Admin Center, 28
shared mailboxes section, Admin Center, 21, 24
SharePoint
administrators, 20
online development, 42
online site collections for storing video, 13
PnP offerings, 33–34
SPO (SharePoint Online), 7–8
storing SharePoint libraries offline, 7
SharePoint Add-ins
developing, 43–44
enforcing license checks, 369–371
publishing, 359–360
SharePoint add-ins
licensing models, 363
metrics, 373
Seller Dashboard for publishing, 355
SharePoint Admin Center, 23, 27–29
SharePoint Online. see SPO (SharePoint Online)
SharePoint REST API
Accept HTTP header used with requests, 243–244
checking in and checking out documents, 265–267
common usage, 256–257
concurrency conflicts, 253
creating document libraries, 263
creating lists, 258
creating/updating list items, 259–261
cross-domain calls, 253–254
deleting documents, 267
deleting list items, 261–262
invoking methods using reference URLs, 240–241
managing data, 249–250
OData logical and arithmetic operators for queries, 245–246
OData query functions, 246–248
OData query parameters, 245
overview of, 237–240
PowerShell for invoking EnsureUser method, 242–243
query string parameters, 249
querying data, 244
querying list of documents, 268
querying list of items, 262
security, 254–256
security form digest required for operations that modify data, 241–242
summary, 268–269
updating list title, 250–252
uploading or updating documents, 264–265
sharing
CORS (cross-origin sharing issues), 40
files and folders, 89–90, 206, 353
link in OD4B, 206
sharing section, Admin Center, 24
Single Sign-On (SSO), 97, 104
single-page applications (SPAs), developing web applications, 40–41
Site Collections section, Admin Center, 28
sites, in resources section of Admin Center, 22
Skype for Business. see S4B (Skype for Business)
Skype Meeting Broadcast, 6–7
social networks, creating enterprise network using Yammer, 10
software, hosting models, 3
Software as a Service (SaaS), 1, 3–4
SPAs (single-page applications), developing web applications, 40–41
SPO (SharePoint Online)
accessing, 255
comparing object navigation with Azure AD, 175
consuming using delegated permissions, 296–297
Corporate Catalog, 351–352
CORS (cross-origin sharing issues), 40
development with, 42
extending UI, 291–293
NextGen Portals based on, 11
preparing for SharePoint framework, 36–37
provisioning SharePoint artifacts, 293–296
quick tour of Office 365 services, 7–8
remote event receivers, 45
remote provisioning, 46–47
remote timer jobs, 44–45
retrieving authentication cookies, 239
SharePoint Add-ins, 43–44
site collections for storing video, 13
SSO (Single Sign-On), 97, 104
storage. see also OD4B (OneDrive for Business)
Blob storage. see Blob storage, Azure
cloud storage. see cloud storage
styles
applying to grid columns, 288
customizing, 288–290
subscriptions
subscriptions
for Office 365, 31
PCs/Macs, 16
plans, 17–19
suite bar, mimicking behavior of, 281–284
support section, Admin Center, 22
synchronized identities, Azure AD, 97–98
T
Task Pane Add-in
creating, 344–346
types of Office add-ins, 48, 339
teamwork
Planner app for, 9
via S4B, 5
telephone, connecting premises telephone infrastructure to S4B, 5
tenants
ADAL supporting multitenancy, 125–127
ADAL supporting single tenancy, 123–125
adding new application to Azure AD tenant, 102
as basis of Azure AD service, 95
enumerating external users for target tenant, 174
enumerating users in current tenant, 172–173
setting up developer tenant, 31–32
Teper, Jeff, 7
Term Store section, SharePoint Admin Center, 28
testing environment, setting up, 31
text, customizing using Organization Profiles, 26
themes, customizing using Organization Profiles, 25–27
threads, navigation properties, 184–185
thumbnails, retrieving image file of, 198
time management, with Delve Analytics, 12
tokens
access tokens, 116–118
access tokens for Graph API, 134–135
access tokens for SPO, 255
app-only, 45, 291–292
Azure AD endpoints, 114
claims presented in JWT-formatted OAuth token, 118–120
communication flow in OpenID Connect, 112–113
consuming Graph API, 334
consuming SPO, 296–297
leveraging access tokens, 135–137
refreshing, 121–122
retrieving access tokens, 211
SAML, 110–111
tools
add-in, 322–323
administrative, 19–20
top navigation bar, 286–287
TypeScript, 37
U
UI (user interface)
Azure management UI, 99, 103–105
customizing SharePoint, 43–44
developing full-page web applications, 38–40
developing native applications, 41
extending SPO, 291–293
UI Fabric package
basic UI elements, 279–281
creating responsive grid, 287–288
customizing components and styles, 288–290
mimicking suite bar behavior, 281–284
providing top navigation bar, 286–287
rendering output of Outlook add-in, 333
rendering profile picture for current user, 284–285
tools for creating add-ins, 323
Unified Groups. see also Groups (Office 365)
accessing calendar of, 186
types of groups, 178
Universal Windows Platform (UWP), 41
UpdateAsync method, updating resources in Graph SDK, 224
updates, Click-to-Run and, 17
UPN (User Principal Name), 176
UPS (User Profile Service), 175
URLs
for common SharePoint REST APIs, 238
entry points for consuming calendars and events, 73
invoking REST API methods, 240–241
usage, in reports section of Admin Center, 22
user interface. see UI (user interface)
user management administrators, administrative roles, 20
User Principal Name (UPN), 176
User Profile Service (UPS), 175
User Profiles section, SharePoint Admin Center, 28
User type, defining, 171–172
users
Azure AD management UI options, 99
consuming services in Graph API, 59–63
Graph API services, 54
managing manager and direct reports, 232–233
managing photo of current user, 231–232
rendering profile picture for current user, 284–285
sending notifications/messages to groups, 300–301
users section, Admin Center, 21, 24
Users services
defining User type, 171–172
enumerating external users for target tenant, 174
enumerating users in current tenant, 172–173
retrieving list of users, 174–175
retrieving manager and direct reports of users, 175–176
retrieving user instance, 176
retrieving user photo, 176–177
UWP (Universal Windows Platform), 41
UX (user experience)
UX (user experience)
basic UI elements, 279–281
developing full-page web applications, 38–40
developing native applications, 41
developing single-page web applications, 40–41
V
v2 authentication endpoint, 212–213
Video (Office 365), 12–13
video, working with content, 12
VIPR tools, leveraging Graph API, 55
virtualization technology, 16
Visual Studio 2015
as application development platform, 272–273
creating add-ins, 322
creating custom workflow solutions, 44
creating Outlook add-ins, 328–330
developing native applications, 41
developing Office Add-ins, 49
in development environment, 32–33
Graph SDK library, 55
registering Azure AD apps, 107–110
Visual Studio Code
creating add-ins, 322
developing Office Add-ins, 49
in development environment, 32
editing add-in created with Yeoman generator, 341
visualization tools
Office Sway, 13–14
Power BI, 10
W
WCF (Windows Communication Foundation), 237
web applications
ADAL in, 123
authentication for, 132
creating and registering, 272
creating and registering webhooks, 314–316
creating for use with mail service, 131
developing, 38
full-page, 38–40
publishing, 361
publishing ASP.NET applications, 310–312
publishing Azure WebJob, 312–313
publishing using Seller Dashboard, 355
sending email messages using ASP.NET application, 147–148
single-page, 40–41
updating or deleting, 361–362
web APIs and, 40
web browsers
consuming Office groups, 9
working with Office documents, 7–8
webhooks
connectors acting as, 313
creating and registering, 314–316
WebJobs
architecture of asynchronous, 303
Azure, 45
creating, 302
publishing, 312–313
running on multiple App Service instances, 309
Windows authentication, 107–108
Windows Communication Foundation (WCF), 237
Windows installer (MSI), 16–17
Windows PCs, 37. see also PCs
Word (Microsoft), Office Add-ins, 47–49
work groups, Office 365 Groups, 8
workflow, creating custom solutions, 44
WS-Federation
Azure AD endpoints, 113
user authentication, 110–111
X
X.509 certificate
authenticating applications, 275
authentication against Azure AD, 293
creating KeyCredentials array from X.509 certificate, 278
.PFX file, 310
Xamarin, 41
X-Http-Method, 238
XML
license XMl file, 364–366
manifest file, 323–324
manifest file for Outlook, 325–327
provisioning SharePoint artifacts, 293–296
X-RequestDigest, security form digest, 241
Y
Yammer
accessing Admin Center for, 23
integration with Skype Meeting Broadcast, 7
quick tour of Office 365 services, 10
Yeoman
creating add-ins, 339–342
developing Office Add-ins, 49
preparing for SharePoint framework, 37
About the Author