在通用 Windows 10 应用中开始使用 Microsoft Graph

为企业客户生成应用?如果企业客户启用企业移动性安全功能,如条件性设备访问,应用可能无法运行。在这种情况下,你可能不知道,而且客户可能会遇到错误。

若要在所有企业方案中支持所有企业客户,必须使用 Azure AD 终结点并使用 Azure 管理门户管理应用。有关详细信息,请参阅 在 Azure AD 和 Azure AD v2.0 终结点之间进行选择

本文介绍了从 Azure AD v2.0 终结点获取访问令牌和调用 Microsoft Graph 所需的任务。本文演示了适用于 UWP(库)的 Microsoft Graph Connect 示例中的代码,以说明在使用 Microsoft Graph 的应用中必须实现的主要概念。

不想生成一个应用吗?使用 Microsoft Graph 快速入门快速准备就绪并开始运行,或下载本文基于的适用于 UWP(库)的 Microsoft Graph Connect 示例。此外请注意,我们有此示例的 REST 版本

示例用户界面

该示例包含一个非常简单的用户界面,其中包括一个顶部命令栏、一个连接按钮、一个发送邮件按钮,以及一个会自动填充登录用户电子邮件地址的文本框,但是该文本框可以进行编辑。

当用户未连接时,发送邮件按钮处于禁用状态:

屏幕显示连接按钮已启用和发送邮件按钮已禁用

当用户已连接时,顶部命令栏包含一个断开连接按钮。

屏幕显示已连接用户的电子邮件地址和发送邮件按钮已启用

所有示例的 UI 字符串均存储在 Assets 文件夹中的 Resources.resw 文件中。

先决条件

若要开始,将需要以下各项:

注册应用

  1. 使用个人或工作或学校帐户登录到 应用注册门户
  2. 选择“添加应用”。
  3. 为应用输入名称,并选择“创建应用程序”。

    将显示注册页,其中列出应用的属性。

  4. 在“平台”下,选择“添加平台”

  5. 选择“本机应用程序”****。
  6. 将客户端 ID(应用程序 ID)和重定向 URL 值复制到剪切板。将需要在示例应用中输入这些值。

    应用 ID 是应用的唯一标识符。重定向 URL 是由 Windows 10 为每个应用提供的唯一 URI,以确保发送到该 URI 的邮件只发送到该应用程序。

  7. 选择“保存”。

配置项目

  1. 在 Visual Studio 中打开初学者项目的解决方案文件。
  2. 打开项目的 App.xaml 文件,并找到 Application.Resources 节点。使用注册应用的相应值替换应用程序 ID 和重定向 URI 占位符。
    <Application.Resources>
        <!-- Add your Client Id here. -->
        <x:String x:Key="ida:ClientID">ENTER_YOUR_CLIENT_ID</x:String>
        <!-- Add your Redirect URI here. -->
        <x:String x:Key="ida:ReturnUrl">ENTER_YOUR_REDIRECT_URI</x:String>
    </Application.Resources>

使用 Microsoft Graph 发送电子邮件

打开初学者项目中的 MailHelper.cs 文件。该文件中包含构建并发送电子邮件的代码。它包含一个方法 -- ComposeAndSendMailAsync -- 该方法构建 POST 请求并将其发送到 https://graph.microsoft.com/v1.0/me/microsoft.graph.SendMail 终结点。

ComposeAndSendMailAsync 方法采用三个字符串值 -- subjectbodyContentrecipients -- 通过 MainPage.xaml.cs 文件将这些值传递给它。subjectbodyContent 字符串随所有其他 UI 字符串存储在 Resources.resw 文件中。recipients 字符串来自应用界面中的地址框中。

ComposeAndSendMailAsync 方法中的第一个任务是通过 Microsoft Graph 获取当前用户的照片。此行调用 GetCurrentUserPhotoStreamAsync 方法:

            // Get current user photo
            Stream photoStream = await GetCurrentUserPhotoStreamAsync();

完整的 GetCurrentUserPhotoStreamAsync 方法如下所示:

        // Gets the stream content of the signed-in user's photo. 
        // This snippet doesn't work with consumer accounts.
        public async Task<Stream> GetCurrentUserPhotoStreamAsync()
        {
            Stream currentUserPhotoStream = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                currentUserPhotoStream = await graphClient.Me.Photo.Content.Request().GetAsync();

            }

            // If the user account is MSA (not work or school), the service will throw an exception.
            catch (ServiceException)
            {
                return null;
            }

            return currentUserPhotoStream;

        }

如果用户没有照片,此逻辑会获取项目中包含的其他图像文件:

            // If the user doesn't have a photo, or if the user account is MSA, we use a default photo

            if (photoStream == null)
            {
                StorageFile file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("test.jpg");
                photoStream = (await file.OpenReadAsync()).AsStreamForRead();
            }

现在我们已拥有图像流,可以通过调用 UploadFileToOneDriveAsync 方法将此文件上载到 OneDrive:

            MemoryStream photoStreamMS = new MemoryStream();
            // Copy stream to MemoryStream object so that it can be converted to byte array.
            photoStream.CopyTo(photoStreamMS);

            DriveItem photoFile = await UploadFileToOneDriveAsync(photoStreamMS.ToArray());

完整的 UploadFileToOneDriveAsync 方法如下所示:

        // Uploads the specified file to the user's root OneDrive directory.
        public async Task<DriveItem> UploadFileToOneDriveAsync(byte[] file)
        {
            DriveItem uploadedFile = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                MemoryStream fileStream = new MemoryStream(file);
                uploadedFile = await graphClient.Me.Drive.Root.ItemWithPath("me.png").Content.Request().PutAsync<DriveItem>(fileStream);

            }


            catch (ServiceException)
            {
                return null;
            }

            return uploadedFile;
        }

我们还可以使用该流创建可以随消息传递的 MessageAttachmentsCollectionPage 对象。

            MessageAttachmentsCollectionPage attachments = new MessageAttachmentsCollectionPage();
            attachments.Add(new FileAttachment
            {
                ODataType = "#microsoft.graph.fileAttachment",
                ContentBytes = photoStreamMS.ToArray(),
                ContentType = "image/png",
                Name = "me.png"
            });

我们可以通过调用 GetSharingLinkAsync 方法获取新上载的 OneDrive 文件的共享链接。bodyContent 字符串包含共享链接的占位符:

            // Get the sharing link and insert it into the message body.
            Permission sharingLink = await GetSharingLinkAsync(photoFile.Id);
            string bodyContentWithSharingLink = String.Format(bodyContent, sharingLink.Link.WebUrl);

完整的 GetSharingLinkAsync 方法如下所示:

        public static async Task<Permission> GetSharingLinkAsync(string Id)
        {
            Permission permission = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                permission = await graphClient.Me.Drive.Items[Id].CreateLink("view").Request().PostAsync();
            }

            catch (ServiceException)
            {
                return null;
            }

            return permission;
        }

由于用户可以传递多个地址,因此下一项任务是将 recipients 字符串拆分为一组可用于构建 Recipients 对象列表的 EmailAddress 对象,然后能够以请求 POST 正文的形式传递这些对象:

            // Prepare the recipient list
            string[] splitter = { ";" };
            var splitRecipientsString = recipients.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
            List<Recipient> recipientList = new List<Recipient>();

            foreach (string recipient in splitRecipientsString)
            {
                recipientList.Add(new Recipient { EmailAddress = new EmailAddress { Address = recipient.Trim() } });
            }

最后一项任务是构建 Message 对象,然后通过 GraphServiceClient 将它发送至 me/microsoft.graph.SendMail 终结点。由于 bodyContent 字符串是一个 HTML 文档,因此请求将 ContentType 值设置为 HTML。

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();

                var email = new Message
                {
                    Body = new ItemBody
                    {
                        Content = bodyContentWithSharingLink,
                        ContentType = BodyType.Html,
                    },
                    Subject = subject,
                    ToRecipients = recipientList,
                    Attachments = attachments
                };

                try
                {
                    await graphClient.Me.SendMail(email, true).Request().PostAsync();
                }
                catch (ServiceException exception)
                {
                    throw new Exception("We could not send the message: " + exception.Error == null ? "No error message returned." : exception.Error.Message);
                }


            }

            catch (Exception e)
            {
                throw new Exception("We could not send the message: " + e.Message);
            }

完整的类如下所示:

    public class MailHelper
    {
        /// <summary>
        /// Compose and send a new email.
        /// </summary>
        /// <param name="subject">The subject line of the email.</param>
        /// <param name="bodyContent">The body of the email.</param>
        /// <param name="recipients">A semicolon-separated list of email addresses.</param>
        /// <returns></returns>
        public async Task ComposeAndSendMailAsync(string subject,
                                                            string bodyContent,
                                                            string recipients)
        {

            // Get current user photo
            Stream photoStream = await GetCurrentUserPhotoStreamAsync();


            // If the user doesn't have a photo, or if the user account is MSA, we use a default photo

            if (photoStream == null)
            {
                StorageFile file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("test.jpg");
                photoStream = (await file.OpenReadAsync()).AsStreamForRead();
            }

            MemoryStream photoStreamMS = new MemoryStream();
            // Copy stream to MemoryStream object so that it can be converted to byte array.
            photoStream.CopyTo(photoStreamMS);

            DriveItem photoFile = await UploadFileToOneDriveAsync(photoStreamMS.ToArray());

            MessageAttachmentsCollectionPage attachments = new MessageAttachmentsCollectionPage();
            attachments.Add(new FileAttachment
            {
                ODataType = "#microsoft.graph.fileAttachment",
                ContentBytes = photoStreamMS.ToArray(),
                ContentType = "image/png",
                Name = "me.png"
            });

            // Get the sharing link and insert it into the message body.
            Permission sharingLink = await GetSharingLinkAsync(photoFile.Id);
            string bodyContentWithSharingLink = String.Format(bodyContent, sharingLink.Link.WebUrl);

            // Prepare the recipient list
            string[] splitter = { ";" };
            var splitRecipientsString = recipients.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
            List<Recipient> recipientList = new List<Recipient>();

            foreach (string recipient in splitRecipientsString)
            {
                recipientList.Add(new Recipient { EmailAddress = new EmailAddress { Address = recipient.Trim() } });
            }

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();

                var email = new Message
                {
                    Body = new ItemBody
                    {
                        Content = bodyContentWithSharingLink,
                        ContentType = BodyType.Html,
                    },
                    Subject = subject,
                    ToRecipients = recipientList,
                    Attachments = attachments
                };

                try
                {
                    await graphClient.Me.SendMail(email, true).Request().PostAsync();
                }
                catch (ServiceException exception)
                {
                    throw new Exception("We could not send the message: " + exception.Error == null ? "No error message returned." : exception.Error.Message);
                }


            }

            catch (Exception e)
            {
                throw new Exception("We could not send the message: " + e.Message);
            }
        }


        // Gets the stream content of the signed-in user's photo. 
        // This snippet doesn't work with consumer accounts.
        public async Task<Stream> GetCurrentUserPhotoStreamAsync()
        {
            Stream currentUserPhotoStream = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                currentUserPhotoStream = await graphClient.Me.Photo.Content.Request().GetAsync();

            }

            // If the user account is MSA (not work or school), the service will throw an exception.
            catch (ServiceException)
            {
                return null;
            }

            return currentUserPhotoStream;

        }

        // Uploads the specified file to the user's root OneDrive directory.
        public async Task<DriveItem> UploadFileToOneDriveAsync(byte[] file)
        {
            DriveItem uploadedFile = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                MemoryStream fileStream = new MemoryStream(file);
                uploadedFile = await graphClient.Me.Drive.Root.ItemWithPath("me.png").Content.Request().PutAsync<DriveItem>(fileStream);

            }


            catch (ServiceException)
            {
                return null;
            }

            return uploadedFile;
        }

        public static async Task<Permission> GetSharingLinkAsync(string Id)
        {
            Permission permission = null;

            try
            {
                var graphClient = AuthenticationHelper.GetAuthenticatedClient();
                permission = await graphClient.Me.Drive.Items[Id].CreateLink("view").Request().PostAsync();
            }

            catch (ServiceException)
            {
                return null;
            }

            return permission;
        }

    }

现在,已经执行了与 Microsoft Graph 进行交互所需的步骤:应用注册、用户身份验证和发出请求。

运行应用

  1. 按 F5 生成和运行此应用。

  2. 使用个人帐户、工作或学校帐户登录,并授予所请求的权限。

  3. 选择“发送电子邮件”****按钮。在发送邮件后,将在按钮下方显示成功消息。此邮件包含附件形式的照片,同时还提供到 OneDrive 中上载的文件的共享链接。

后续步骤

另请参阅