Microsoft Graph 桌面应用程序

    桌面应用程序,在我这篇文章的语境中,我是特指在Windows桌面上面直接运行的.NET应用程序,包括Console Application,WPF Application,Windows Forms Application, UWP Application,并且限于篇幅,我只会以Console Application作为演示,因为无论表现形式如何不同,它们从本质上是类似的。

    本文所附带示例代码可以通过https://github.com/chenxizhang/office365dev/tree/master/samples/graph-consoleapplicationsample 访问,这是由Visual Studio 2017编写,开发语言为C#,在Windows 10 Enterprise上面测试通过。请注意,作为演示目的,我尽可能在范例代码中仅包含最必要的代码。

    要进行具体的编程之前,你需要注册Microsfot Graph应用程序。本系列文章约定,针对国际版我将采用Azure AD 2.0这种方式进行注册,而针对中国版将采用Azure AD 1.0这种方式。这两种方式的详细操作步骤,以及我注册好的范例应用程序,请参考

    注册中国版Microsoft Graph应用程序

    Microsoft Graph 桌面应用程序 - 图2

    创建Console Application

    不要问我怎么做这个事情,你应该知道的。

    关键是,接下来你该如何考虑呢?有的人会联想到OAuth,这是一个很好的想法。我在此前已经提到过如何分三个步骤实现Microsoft Graph应用开发,第一步是注册应用程序,第二步是实现身份认证,第三步就是该怎么调用你就怎么调用。

    下图介绍了在Azure AD 2.0中支持的OAuth认证流程
    Microsoft Graph 桌面应用程序 - 图4

    简单地说,OAuth认证一般会有三个步骤

    1. 客户端代表用户发起认证请求(通常是/authorize 这个地址),这个会跳转到Office 365的登录页面,让用户输入账号和密码
    2. 如果用户提供了正确的账号和密码,并确认授权,Azure AD会向我们在注册应用程序时提供的回调地址(redirectUrl)POST一个请求过来,附上一个code,然后我们的应用需要继续用这个code去发起一个请求,申请访问令牌(通常是/token这个地址)
    3. 客户端得到令牌(Access_Token),就可以代表用户访问Microsoft Graph的资源(通常是放在请求的头部里面)。这里需要注意的是,通常令牌都是会一定时间过期的,Micrsoft Graph的令牌默认为1小时有效。过期前可以通过一定的方式刷新令牌。

    具体到我们本篇文章的目标,如果Office 365是国际版,你可以使用Microsoft Graph Client Library https://www.nuget.org/packages/Microsoft.Graph/
    Microsoft Graph 桌面应用程序 - 图6 和 Microsoft Authentication Library

    而如果是用中国版,你也可以使用 Active Directory Authentication Library .NET https://msdn.microsoft.com/library/en-us/Mt417579.aspx

    实现国际版Microsoft Graph调用

    首先,运行下面的命令安装上面提到的两个Library,并且进行更新

    接下来,我们需要编写一个方法,封装一下Graph Authentication这个步骤.

    备注,我认为这里还有可以改进的空间,最好是连这一步都可以省略掉。产品组是还没有完全想好,日后应该会加上这块实现。

    1. {
    2. static string token;
    3. static DateTimeOffset Expiration;
    4. public async Task AuthenticateRequestAsync(HttpRequestMessage request)
    5. {
    6. string clientID = "45aa2ecc-5e57-4c91-86c1-b93064800c39";//这个ID是我创建的一个临时App的ID,请替换为自己的
    7. string[] scopes = { "user.read", "mail.read", "mail.send"};
    8. var app = new PublicClientApplication(clientID);
    9. AuthenticationResult result = null;
    10. try
    11. {
    12. result = await app.AcquireTokenSilentAsync(scopes);
    13. token = result.Token;
    14. }
    15. catch (Exception)
    16. {
    17. if (string.IsNullOrEmpty(token) || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
    18. {
    19. result = await app.AcquireTokenAsync(scopes);
    20. Expiration = result.ExpiresOn;
    21. token = result.Token;
    22. }
    23. }
    24. request.Headers.Add("Authorization", $"Bearer {token}");
    25. }
    26. }

    有了这个类,接下来我们要调用Microsoft Graph简直可以说是易如反掌,请参考下面的代码

    1. var client = new GraphServiceClient(new GraphAuthenticator());//创建客户端代理
    2. var user = client.Me.Request().GetAsync().Result;//获取当前用户信息
    3. var messages = client.Me.Messages.Request().GetAsync().Result;//获取用户的前十封邮件
    4. foreach (var item in messages)
    5. {
    6. Console.WriteLine(item.Subject);
    7. }
    8. client.Me.SendMail(new Message() //发送邮件
    9. {
    10. Subject = "调用Microsoft Graph发出的邮件",
    11. {
    12. ContentType = BodyType.Text,
    13. Content = "这是一封调用了Microsoft Graph服务发出的邮件,范例参考 https://github.com/chenxizhang/office365dev"
    14. },
    15. ToRecipients = new[]
    16. {
    17. new Recipient()
    18. {
    19. EmailAddress = new EmailAddress(){ Address ="ares@office365devlabs.onmicrosoft.com"}
    20. }
    21. }
    22. }, true).Request().PostAsync();
    23. Console.Read();

    输入你的Office 365账号和密码(请注意,需要是国际版),然后点击“Sign In”,Microsoft Graph将引导你进行授权确认
    Microsoft Graph 桌面应用程序 - 图9

    不出意外的话,你现在就可以在控制台窗口中看到当前登录的用户信息,十个邮件标题等信息了。

    我不止一次听到开发人员反馈说,现在在网络上想一些VB或者VB.NET的代码范例比较难。这是一个事实,我自己对VB是有感情的,为了向这部分开发人员致意,我特别提供了一个VB.NET的版本。

    我不能担保后续每一篇,每个范例都会提供VB.NET的版本,因为精力真的很有限。如果你有兴趣根据我的C#的范例转换为VB.NET代码,欢迎跟我联系。

    接下来我们该看看在中国版Microsoft Graph调用方面有什么不同。虽然因为没有封装好的Microsoft Graph Client,但是看起来基本代码也还算简单易懂,请参考。

    安装下面这个Package

    1. Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
    2. Update-Package
    1. static async Task<string> GetAccessToken()
    2. {
    3. var appId = "9c7dd51c-072c-4aea-aaee-fc57efacb150";
    4. var authorizationEndpoint = "https://login.chinacloudapi.cn/common/oauth2/authorize";//国际版是https://login.microsoftonline.com/common/oauth2/authorize
    5. var resource = "https://microsoftgraph.chinacloudapi.cn"; //国际版是https://graph.microsoft.com
    6. var redirectUri = "http://nativeapplication";//其实这个应该去掉,目前必须要填,而且要跟注册时一样
    7. AuthenticationResult result = null;
    8. var context = new AuthenticationContext(authorizationEndpoint);
    9. result = await context.AcquireTokenAsync(resource, appId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always));
    10. return result.AccessToken;
    11. }

    编写一个自定义方法发起Microsoft Graph请求

    万事俱备,下面就可以在主程序中组合使用这两个方法进行Microsoft Graph调用了

    1. static void Main(string[] args)
    2. {
    3. ///获得用户的令牌
    4. var token = GetAccessToken().Result;
    5. //获得用户的基本信息
    6. var me = InvokeRestReqeust("https://microsoftgraph.chinacloudapi.cn/v1.0/me", token).Result;
    7. Console.WriteLine(me);
    8. //获得用户的邮件列表(前十封)
    9. var messages = InvokeRestReqeust("https://microsoftgraph.chinacloudapi.cn/v1.0/me/messages", token).Result;
    10. Console.WriteLine(messages);
    11. Console.Read();
    12. }

    我同样为这个范例准备了一个VB.NET的版本,请大家参考

    1. Imports System.Net
    2. Imports Microsoft.IdentityModel.Clients.ActiveDirectory
    3. Module Module1
    4. Sub Main()
    5. '获得用户令牌
    6. Dim token = GetAccessToken().Result
    7. '获得当前用户基本信息
    8. Dim user = InvokeRestRequest("https://microsoftgraph.chinacloudapi.cn/v1.0/me", token).Result
    9. Console.WriteLine(user)
    10. '获得用户的邮件列表(前十封)
    11. Dim messages = InvokeRestRequest("https://microsoftgraph.chinacloudapi.cn/v1.0/me/messages", token).Result
    12. Console.WriteLine(messages)
    13. Console.Read()
    14. End Sub
    15. Async Function InvokeRestRequest(url As String, token As String) As Task(Of String)
    16. Dim client = New WebClient()
    17. client.Headers.Add("Authorization", $"Bearer {token}")
    18. Dim result = Await client.DownloadStringTaskAsync(url)
    19. Return result
    20. '请注意,这里直接返回字符串型的结果,它是Json格式的,有兴趣的可以继续在这个基础上进行处理
    21. End Function
    22. Async Function GetAccessToken() As Task(Of String)
    23. Dim appId = "9c7dd51c-072c-4aea-aaee-fc57efacb150"
    24. Dim authorizationEndpoint = "https://login.chinacloudapi.cn/common/oauth2/authorize"
    25. '国际版是https://login.microsoftonline.com/common/oauth2/authorize
    26. Dim resource = "https://microsoftgraph.chinacloudapi.cn" '国际版是https://graph.microsoft.com
    27. Dim redirectUri = "http://nativeapplication" '其实这个应该去掉,目前必须要填,而且要跟注册时一样
    28. Dim result As AuthenticationResult
    29. Dim context = New AuthenticationContext(authorizationEndpoint)
    30. result = Await context.AcquireTokenAsync(resource, appId, New Uri(redirectUri), New PlatformParameters(PromptBehavior.Auto))
    31. Return result.AccessToken
    32. End Function

    本文所有代码范例,可以通过 https://github.com/chenxizhang/office365dev/tree/master/samples/graph-consoleapplicationsample 查看或者下载

    结语