在 Xamarin Forms 应用中开始使用 Microsoft Graph

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

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

这是将要创建的应用。

UWP Android iOS
Connect sample on UWP Connect sample on Android Connect sample on iOS

不想生成一个应用吗?使用 Microsoft Graph 快速入门 快速准备就绪并开始运行,或下载本文基于的 Xamarin Forms 的 Microsoft Graph Connect 示例

先决条件

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

如果想要在此示例中运行 iOS 项目,则要求如下:

注册应用

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

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

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

  5. 选择“本机应用程序”****。
  6. 复制应用程序 ID。将需要在示例应用中输入该值。

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

  7. 选择“保存”。

配置项目

  1. 在 Visual Studio 中打开初学者项目的解决方案文件。
  2. 打开 XamarinConnect (可移植) 项目中的 App.cs 文件,然后找到 ClientId 字段。使用注册应用的应用程序 ID 替换应用程序 ID 占位符。
public static string ClientID = "ENTER_YOUR_CLIENT_ID";
public static string[] Scopes = { "User.Read", "Mail.Send", "Files.ReadWrite" };

当用户进行身份验证时,Scopes 值将存储应用需要请求的 Microsoft Graph 权限范围。请注意,App 类构造函数使用 ClientID 值来实例化 MSAL PublicClientApplication 类的实例。稍后,将使用该类来验证用户身份。

IdentityClientApp = new PublicClientApplication(ClientID);

使用 Microsoft Graph 发送电子邮件

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

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

使用声明

请确保将这些声明置于文件顶部:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Graph;

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)
            {
                var assembly = typeof(MailHelper).GetTypeInfo().Assembly;
                photoStream = assembly.GetManifestResourceStream("XamarinConnect.test.jpg");
            }

现在已拥有图像流,可以通过调用无存根的 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)
            {
                var assembly = typeof(MailHelper).GetTypeInfo().Assembly;
                photoStream = assembly.GetManifestResourceStream("XamarinConnect.test.jpg");
            }

            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. 选择想要运行的项目。如果选择“通用 Windows 平台”选项,则可以在本地计算机上运行示例。如果想要运行 iOS 项目,则需连接到安装在其上的 具有 Xamarin 工具的 Mac。(还可以在 Mac 上的 Xamarin Studio 中打开此解决方案并直接从此处运行示例。)如果想要运行 Android 项目,可以使用适用于 Android 的 Visual Studio 模拟器

  2. 按 F5 进行构建和调试。运行此解决方案并使用个人或工作或学校帐户登录。

    注意 可能需要打开生成配置管理器,以确保为 UWP 项目选择“生成”和“部署”步骤。

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

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

后续步骤

另请参阅