Microsoft Graph 桌面应用程序
作者:陈希章 发表于 2017年3月22日
桌面应用程序,在我这篇文章的语境中,我是特指在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上面测试通过。请注意,作为演示目的,我尽可能在范例代码中仅包含最必要的代码。
注册Microsoft Graph应用程序
要进行具体的编程之前,你需要注册Microsfot Graph应用程序。本系列文章约定,针对国际版我将采用Azure AD 2.0这种方式进行注册,而针对中国版将采用Azure AD 1.0这种方式。这两种方式的详细操作步骤,以及我注册好的范例应用程序,请参考
注册Azure AD 2.0 应用程序
注册中国版Microsoft Graph应用程序
创建Console Application
不要问我怎么做这个事情,你应该知道的。

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

简单地说,OAuth认证一般会有三个步骤
- 客户端代表用户发起认证请求(通常是/authorize 这个地址),这个会跳转到Office 365的登录页面,让用户输入账号和密码
 - 如果用户提供了正确的账号和密码,并确认授权,Azure AD会向我们在注册应用程序时提供的回调地址(redirectUrl)POST一个请求过来,附上一个code,然后我们的应用需要继续用这个code去发起一个请求,申请访问令牌(通常是/token这个地址)
 - 客户端得到令牌(Access_Token),就可以代表用户访问Microsoft Graph的资源(通常是放在请求的头部里面)。这里需要注意的是,通常令牌都是会一定时间过期的,Micrsoft Graph的令牌默认为1小时有效。过期前可以通过一定的方式刷新令牌。
 
你当然可以在了解上述原理马上开始编写代码,但这里要请你了解,为了降低开发人员在这块的工作量,并且尽量进行标准化,Microsoft Graph针对不同的平台和语言都有对应的SDK。请参考 https://developer.microsoft.com/zh-cn/graph/docs/get-started/get-started
具体到我们本篇文章的目标,如果Office 365是国际版,你可以使用Microsoft Graph Client Library https://www.nuget.org/packages/Microsoft.Graph/
和 Microsoft Authentication Library https://www.nuget.org/packages/Microsoft.Identity.Client/1.0.304142221-alpha
目前这个 Microsoft Authentication Library 的状态是Preview,但是很值得期待,因为它还有针对.NET Core的版本
而如果是用中国版,你也可以使用 Active Directory Authentication Library .NET https://msdn.microsoft.com/library/en-us/Mt417579.aspx
实现国际版Microsoft Graph调用
首先,运行下面的命令安装上面提到的两个Library,并且进行更新
Install-Package Microsoft.Graph
Install-Package Microsoft.Identity.Client -IncludePrerelease
Update-Package
接下来,我们需要编写一个方法,封装一下Graph Authentication这个步骤.
备注,我认为这里还有可以改进的空间,最好是连这一步都可以省略掉。产品组是还没有完全想好,日后应该会加上这块实现。
class GraphAuthenticator : IAuthenticationProvider
{
    static string token;
    static DateTimeOffset Expiration;
    public async Task AuthenticateRequestAsync(HttpRequestMessage request)
    {
        string clientID = "45aa2ecc-5e57-4c91-86c1-b93064800c39";//这个ID是我创建的一个临时App的ID,请替换为自己的
        string[] scopes = { "user.read", "mail.read", "mail.send"};
        var app = new PublicClientApplication(clientID);
        AuthenticationResult result = null;
        try
        {
            result = await app.AcquireTokenSilentAsync(scopes);
            token = result.Token;
        }
        catch (Exception)
        {
            if (string.IsNullOrEmpty(token) || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
            {
                result = await app.AcquireTokenAsync(scopes);
                Expiration = result.ExpiresOn;
                token = result.Token;
            }
        }
        request.Headers.Add("Authorization", $"Bearer {token}");
    }
}
有了这个类,接下来我们要调用Microsoft Graph简直可以说是易如反掌,请参考下面的代码
var client = new GraphServiceClient(new GraphAuthenticator());//创建客户端代理
var user = client.Me.Request().GetAsync().Result;//获取当前用户信息
Console.WriteLine(user.DisplayName);
var messages = client.Me.Messages.Request().GetAsync().Result;//获取用户的前十封邮件
foreach (var item in messages)
{
    Console.WriteLine(item.Subject);
}
client.Me.SendMail(new Message() //发送邮件
{
    Subject = "调用Microsoft Graph发出的邮件",
    Body = new ItemBody()
    {
        ContentType = BodyType.Text,
        Content = "这是一封调用了Microsoft Graph服务发出的邮件,范例参考 https://github.com/chenxizhang/office365dev"
    },
    ToRecipients = new[]
    {
        new Recipient()
        {
            EmailAddress = new EmailAddress(){ Address ="ares@office365devlabs.onmicrosoft.com"}
        }
    }
}, true).Request().PostAsync();
Console.Read();
就是这么简单,就是这么任性,你可以马上运行这个应用程序看看效果了
输入你的Office 365账号和密码(请注意,需要是国际版),然后点击“Sign In”,Microsoft Graph将引导你进行授权确认
不出意外的话,你现在就可以在控制台窗口中看到当前登录的用户信息,十个邮件标题等信息了。
VB.NET 开发人员看过来
我不止一次听到开发人员反馈说,现在在网络上想一些VB或者VB.NET的代码范例比较难。这是一个事实,我自己对VB是有感情的,为了向这部分开发人员致意,我特别提供了一个VB.NET的版本。
我不能担保后续每一篇,每个范例都会提供VB.NET的版本,因为精力真的很有限。如果你有兴趣根据我的C#的范例转换为VB.NET代码,欢迎跟我联系。
Imports System.Net.Http
Imports Microsoft.Graph
Imports Microsoft.Identity.Client
''' <summary>
''' 这个是国际版Microsoft Graph的客户端应用程序范例
''' 作者:陈希章
''' 时间:2017年3月23日
''' </summary>
Module Module1
    Sub Main()
        Dim serviceClient = New GraphServiceClient(New GraphAuthenticator())
        Dim user = serviceClient.Me.Request.GetAsync().Result
        '获取用户基本信息
        Console.WriteLine(user.DisplayName)
        Console.WriteLine(user.Mail)
        '获取用户的邮件列表
        Dim messages = serviceClient.Me.MailFolders.Inbox.Messages.Request.GetAsync().Result
        For Each item In messages
            Console.WriteLine(item.Subject)
        Next
        '发送邮件
        serviceClient.Me.SendMail(New Message() With {
            .Subject = "调用Microsoft Graph发出的邮件(VB.NET)",
            .Body = New ItemBody() With {
                .Content = "这是一封调用了Microsoft Graph服务发出的邮件,范例参考 https://github.com/chenxizhang/office365dev",
                .ContentType = BodyType.Text
            },
            .ToRecipients = New List(Of Recipient) From {
                New Recipient() With {.EmailAddress = New EmailAddress() With {.Address = "ares@office365devlabs.onmicrosoft.com"}}
            }
        }, True).Request.PostAsync()
        Console.Read()
    End Sub
    Public Class GraphAuthenticator
        Implements IAuthenticationProvider
        Shared token As String
        Shared Expiration As DateTimeOffset
        Public Async Function AuthenticateRequestAsync(request As HttpRequestMessage) As Task Implements IAuthenticationProvider.AuthenticateRequestAsync
            Dim clientID As String = "45aa2ecc-5e57-4c91-86c1-b93064800c39" '这个ID是我创建的一个临时App的ID,请替换为自己的
            Dim scopes As String() = {"user.read", "mail.read", "mail.send"}
            Dim app As PublicClientApplication = New PublicClientApplication(clientID)
            Dim result As AuthenticationResult
            Try
                result = Await app.AcquireTokenSilentAsync(scopes)
                token = result.Token
            Catch ex As Exception
                If (String.IsNullOrEmpty(token) OrElse Expiration <= DateTimeOffset.UtcNow.AddMinutes(5)) Then
                    result = app.AcquireTokenAsync(scopes).Result
                    Expiration = result.ExpiresOn
                    token = result.Token
                End If
            End Try
            request.Headers.Add("Authorization", $"Bearer {token}")
        End Function
    End Class
End Module
实现中国版Microsoft Graph调用
接下来我们该看看在中国版Microsoft Graph调用方面有什么不同。虽然因为没有封装好的Microsoft Graph Client,但是看起来基本代码也还算简单易懂,请参考。
安装下面这个Package
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
Update-Package
编写一个自定义方法获取用户的访问令牌
static async Task<string> GetAccessToken()
{
    var appId = "9c7dd51c-072c-4aea-aaee-fc57efacb150";
    var authorizationEndpoint = "https://login.chinacloudapi.cn/common/oauth2/authorize";//国际版是https://login.microsoftonline.com/common/oauth2/authorize
    var resource = "https://microsoftgraph.chinacloudapi.cn"; //国际版是https://graph.microsoft.com
    var redirectUri = "http://nativeapplication";//其实这个应该去掉,目前必须要填,而且要跟注册时一样
    AuthenticationResult result = null;
    var context = new AuthenticationContext(authorizationEndpoint);
    result = await context.AcquireTokenAsync(resource, appId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always));
    return result.AccessToken;
}
编写一个自定义方法发起Microsoft Graph请求
/// <summary>
/// 定义这个方法用来进行Rest调用
/// </summary>
/// <param name="url"></param>
/// <param name="token"></param>
/// <returns></returns>
static async Task<string> InvokeRestReqeust(string url, string token)
{
    var client = new System.Net.WebClient();
    client.Headers.Add("Authorization", $"Bearer {token}");
    var result = await client.DownloadStringTaskAsync(url);
    return result;//请注意,这里直接返回字符串型的结果,它是Json格式的,有兴趣的可以继续在这个基础上进行处理
}
万事俱备,下面就可以在主程序中组合使用这两个方法进行Microsoft Graph调用了
static void Main(string[] args)
{
    ///获得用户的令牌
    var token = GetAccessToken().Result;
    //获得用户的基本信息
    var me = InvokeRestReqeust("https://microsoftgraph.chinacloudapi.cn/v1.0/me", token).Result;
    Console.WriteLine(me);
    //获得用户的邮件列表(前十封)
    var messages = InvokeRestReqeust("https://microsoftgraph.chinacloudapi.cn/v1.0/me/messages", token).Result;
    Console.WriteLine(messages);
    Console.Read();
}
VB.NET用户看过来
我同样为这个范例准备了一个VB.NET的版本,请大家参考
Imports System.Net
Imports Microsoft.IdentityModel.Clients.ActiveDirectory
Module Module1
    Sub Main()
        '获得用户令牌
        Dim token = GetAccessToken().Result
        '获得当前用户基本信息
        Dim user = InvokeRestRequest("https://microsoftgraph.chinacloudapi.cn/v1.0/me", token).Result
        Console.WriteLine(user)
        '获得用户的邮件列表(前十封)
        Dim messages = InvokeRestRequest("https://microsoftgraph.chinacloudapi.cn/v1.0/me/messages", token).Result
        Console.WriteLine(messages)
        Console.Read()
    End Sub
    Async Function InvokeRestRequest(url As String, token As String) As Task(Of String)
        Dim client = New WebClient()
        client.Headers.Add("Authorization", $"Bearer {token}")
        Dim result = Await client.DownloadStringTaskAsync(url)
        Return result
        '请注意,这里直接返回字符串型的结果,它是Json格式的,有兴趣的可以继续在这个基础上进行处理
    End Function
    Async Function GetAccessToken() As Task(Of String)
        Dim appId = "9c7dd51c-072c-4aea-aaee-fc57efacb150"
        Dim authorizationEndpoint = "https://login.chinacloudapi.cn/common/oauth2/authorize"
        '国际版是https://login.microsoftonline.com/common/oauth2/authorize
        Dim resource = "https://microsoftgraph.chinacloudapi.cn" '国际版是https://graph.microsoft.com
        Dim redirectUri = "http://nativeapplication" '其实这个应该去掉,目前必须要填,而且要跟注册时一样
        Dim result As AuthenticationResult
        Dim context = New AuthenticationContext(authorizationEndpoint)
        result = Await context.AcquireTokenAsync(resource, appId, New Uri(redirectUri), New PlatformParameters(PromptBehavior.Auto))
        Return result.AccessToken
    End Function
End Module
从上面的代码对照来看,Azure AD 1.0的方式,需要开发人员处理更多细节,例如身份验证,服务调用结果处理等等。如果有兴趣并且有能力的朋友,欢迎在这个基础上做一定的封装,简化开发。
本文所有代码范例,可以通过 https://github.com/chenxizhang/office365dev/tree/master/samples/graph-consoleapplicationsample 查看或者下载
结语
本文完整地介绍了针对国际版和中国版Office 365,在桌面应用程序中如何实现Microsoft Graph的集成。针对国际版,我采用的是Azure AD 2.0的方式;针对中国版,我采用的是Azure AD 1.0的方式。同时,为了照顾到VB.NET的开发人员,本文所有范例都提供了VB.NET的版本。
Microsoft Graph 桌面应用程序的更多相关文章
- Microsoft Graph Web应用程序极致开发体验
		
作者:陈希章 重写于 2017年5月24日 前言 这篇文章最早写于2017年5月2日,当时的想法是从最简单的方式来写如何在一个ASP.NET MVC应用程序中集成Microsoft Graph,但实际 ...
 - win10 uwp 使用 Microsoft.Graph 发送邮件
		
在 2018 年 10 月 13 号参加了 张队长 的 Office 365 训练营 学习如何开发 Office 365 插件和 OAuth 2.0 开发,于是我就使用 UWP 尝试使用 Micros ...
 - 在无人值守程序(服务)中调用Microsoft Graph
		
作者:陈希章 发表于 2017年5月31日 什么是无人值守程序(服务) 我在此前用了几篇文章分别介绍了在桌面应用程序(控制台),Web应用程序(ASP.NET MVC),以及PowerSehll脚本中 ...
 - 跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)
		
作者:陈希章 发表于 2017年6月25日 谈一谈.NET 的跨平台 终于要写到这一篇了.跨平台的支持可以说是 Office 365 平台在设计伊始就考虑的目标.我在前面的文章已经提到过了,Micro ...
 - 掀起Azure AD的盖头来——深入理解Microsoft Graph应用程序和服务权限声明
		
作者:陈希章 发表于 2017年7月12日 引子 这是一篇计划外的文章.我们都知道要进行Microsoft Graph的开发的话,需要进行应用程序注册.这个在此前我已经有专门的文章写过了.但这里存在一 ...
 - c#实现windows远程桌面连接程序
		
c#实现windows远程桌面连接程序 使用winform制作windows远程桌面连接程序,windows自带了远程桌面连接,我们需要将远程桌面连接集成 到自己的winform程序,并实现管理远程主 ...
 - 在PowerShell脚本中集成Microsoft Graph
		
作者:陈希章 发表于2017年4月23日 我旗帜鲜明地表态,我很喜欢PowerShell,相比较于此前的Cmd Shell,它有一些重大的创新,例如基于.NET的类型系统,以及管道.模块的概念等等.那 ...
 - 通过第三方工具体验Microsoft Graph
		
作者:陈希章 发表于 2017年3月22日 上一篇文章我介绍了如何利用官方提供的Graph 浏览器快速体验Microsoft Graph强大功能,这是极好的起点.官方的Graph浏览器力图用最简单的方 ...
 - 通过Graph 浏览器体验Microsoft Graph
		
作者:陈希章 发表于 2017年3月18日 上一篇介绍了Microsoft Graph的基本概念,接下来我们快速体验一下Microsoft Graph到底能做什么? 为了帮助开发人员直观和快速体验Mi ...
 
随机推荐
- 【机器学习实战】第15章 大数据与MapReduce
			
第15章 大数据与MapReduce 大数据 概述 大数据: 收集到的数据已经远远超出了我们的处理能力. 大数据 场景 假如你为一家网络购物商店工作,很多用户访问该网站,其中有些人会购买商品,有些人则 ...
 - Python学习笔记整理总结【语言基础篇】
			
一.变量赋值及命名规则① 声明一个变量及赋值 #!/usr/bin/env python # -*- coding:utf-8 -*- # _author_soloLi name1="sol ...
 - vue新手入门——vue-cli搭建
			
首先说明,以下内容vue官网都有文档,如果觉得麻烦啰嗦,请移步至 安装-vue.js . 准备工作: 1.下载并安装node环境,一般情况下安装好node之后,npm也会安装好.具体安装的话,百度大概 ...
 - [转载] 解读ClassLoader
			
转载自http://www.iteye.com/topic/83978 ClassLoader一个经常出现又让很多人望而却步的词,本文将试图以最浅显易懂的方式来讲解 ClassLoader,希望能对不 ...
 - 深入理解CSS盒模型
			
如果你在面试的时候面试官让你谈谈对盒模型的理解,你是不是不知从何谈起.这种看似简单的题其实是最不好答的. 下面本文章将会从以下几个方面谈谈盒模型. 基本概念:标准模型 和IE模型 CSS如何设置这两种 ...
 - python之socket模块
			
UDP client #!/usr/bin/env python2.7 #-*-coding:utf-8 -*- import socket s=socket.socket(socket.AF_INE ...
 - javascript 的继承
			
我们的JavaScript比较特别了,主要通过原型链实现继承的. 下面介绍各种实现继承的方式:原型链继承,借用构造函数,组合继承,原型式继承,寄生式继承,寄生组合式继承. 二.实现继承方式 1.原型链 ...
 - Java面试之框架篇(九)
			
spring现在无疑是Java中最火的框架,使用范围广,几乎每个公司面试都会涉及spring和数据库,你可以对Struts不熟悉,但一定不能表现出对spring不了解.第九篇赢在面试全篇介绍sprin ...
 - 关于node的前端项目编译时内存溢出问题
			
最近在做一个基于vue 的多页面项目 , 页面n++多,编译时发生node内存溢出问题,继而百度之,得到解答,故记录之. '如图' 只需在 package.json 里面 加上 --max ...
 - 做技术,有没有必要参加IT培训
			
近几年,IT培训机构可谓是琳琅满目,稂莠不齐.培训Java的,培训PHP的,培训大数据的等等吧,不一而足. 自己也算是IT技术圈子待了好多年了,面试过一些机构培训出来的学生,也有几个好哥们在培训机构做 ...