如何从WinForms阅读收件箱Outlook.com邮件?

时间:2016-05-11 18:40:46

标签: c# .net vb.net winforms outlook

SCENARIO

我想使用 WinForms 技术开发一个用C#或Vb.Net编写的非常简单的应用程序,它将帮助我自动执行一项简单的任务,包括访问我的 Outlook .com 帐户阅读从 Youtube 收到的电子邮件,然后提取视频网址。

问题

我的网络相关知识并不好,我陷入了最重要的一点,试图找到最简单的方法来完成这项任务(我的意思是.Net或第三方API的官方Microsoft API或其他方式可以做到此操作,尝试应用所需的 OAuth2 自动关联来访问电子邮件帐户。

我知道下面的代码并没有集中在正确的方向上,因为缺乏授权,但我不知道如何实现它既不是如何阅读电子邮件,所以这就是我试过的:

string url = "https://outlook.office.com/api/v2.0/me/messages";
string result = "";

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";

try {
    using (WebClient wc = new WebClient()) {
        wc.Encoding = Encoding.UTF8;
        result = wc.DownloadString(url);
    }

} catch (Exception ex) {
    MessageBox.Show(ex.Message);

}

问题

如何访问我的 Outlook.com 帐户以阅读我拥有的收件箱电子邮件的标题和内容?另外,但也可选择回复(只有在可能的情况下,并没有太多问题),我如何删除电子邮件?。

请注意,此问题仅适用于 Outlook.com 在线服务,而不是 Outlook 的桌面客户端,而不是 COM 库的使用或 Office365

答案要求

我知道我没有人要求帮助并提出一些必要条件,所有的帮助对我都很感激,但这次我需要特别要求,因为我的头脑疯狂试图理解,使用并且适应从头开始制作的 OAuth2 解决方案,它生成了很长的代码,我根本不理解,这对我来说太过分了。

出于这个原因,只有当提供的解决方案基于使用第三个pary库来促进所有这些任务时,我才会接受这个问题的答案,因为它将作为 OAuth2的完整抽象。 实施,例如 RestSharp CodeScales DotNetOpenAuth ,或其他任何一项(免费)lib将为我处理所需的东西,而不是需要自己从头开发 OAuth2 算法。

研究

我的调查开始阅读 this 微软的Outlook开发。页面,关注 this 邮件API参考, this REST API文档, this Outlook。 com API, this 一种入门文章,以 this 结尾使用ASP.Net作为完全说明性示例。

我从微软的文章中清楚地看到的只是......没有什么,我注册了应用程序并创建了客户端ID和秘密,但微软没有为 Winforms <提供任何示例/ strong>所以我试图将他们的官方 ASP.NET 示例翻译成 WinForms 但没有成功。

在浪费时间之后,我找到了 this OAuth 文档页面,其中提供了一些.NET,我想这将有助于OAuth2授权,然后我发现 DotNetOpenAuth 库似乎非常完整,我还使用 DotNetOpenAuth 找到了Outlook的 this 代码示例但同样是 ASP.NET ,还有 these 通用和官方 DotNetOpenAuth 的代码示例,但我不能找到任何可以帮助我做我想做的事情。

3 个答案:

答案 0 :(得分:2)

一般的想法是遵循这里的教程:Get Started with Mail, Calendar, and Contacts REST APIs但我会尝试简化它并使用Winforms示例演示它。

注册应用

首先,您需要创建应用程序注册门户并将其注册到应用程序注册门户(当然,对于给定的应用程序,您只需执行一次):

  1. 制作应用
  2. 生成新密码
  3. 添加一个平台,然后选择手机(“手机”在这里表示“任何设备”或“不适用于浏览器”......)
  4. 别忘了点击保存!
  5. 验证

    现在,在您的“任何设备”代码(包括winforms)中,您需要进行身份验证。最简单的方法是使用ADAL(“Active Directory身份验证库”)。源代码在此处https://github.com/AzureAD/azure-activedirectory-library-for-dotnet可用,二进制文件可用作nuget。

    不幸的是,今天你可以在nuget上获得的最新版本命名为 Microsoft.IdentityModel.Clients.ActiveDirectory 对我不起作用(它有一个我在这里报告的错误https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/412已经修复了,但现在服务器抱怨某些应用程序与服务器不兼容)。

    所以你必须使用旧的“实验性的”(记住将来的某一天,我们将不得不切换):Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory

    您希望在获取身份验证令牌时确保使用正确的范围。范围在此处定义:Outlook mail, calendar, and contacts scopes并表示许可区域。如果不指定范围,除了进行身份验证之外,您无需执行任何操作。

    因此,如果您想阅读邮件,请使用“https://outlook.office.com/mail.read”范围。

    当您尝试应用程序时,在身份验证对话框之后,它应该向用户显示同意屏幕(这里我们看到我们要求邮件范围:“阅读邮件”):

    enter image description here

    Office / Outlook / OData API

    一旦身份验证工作,您可以直接使用REST api,这可能不是那么容易,或者是懒惰并使用另一个包:Microsoft.Office365.OutlookServices-V2.0将为您完成所有底层的REST / OData魔术。好消息是这个API非常完整,所以它应该允许你做其他的事情,比如消息创建,删除等。

    outlook.com案例有一个重要的注意事项:并非所有帐户都为此整个REST API启用(请查看此处的“REST API可用性”一章:Outlook Mail),因此您可能希望创建一个新的测试。

    以下是示例应用的winforms代码,该应用将查询10条消息并将其添加到列表框中。

    using System;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory;
    using Microsoft.Office365.OutlookServices;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private const string authority = "https://login.microsoftonline.com/common";
            private const string clientId = "blablabl-abla-blab-abla-blablablab"; // TODO: put your application id here
            private const string redirectUri = "urn:ietf:wg:oauth:2.0:oob"; // put your redirect uri here (should be the same)
    
            // we cache the token for the duration of this form
            // you could/should use the FileCache class provided in the sample here https://dev.outlook.com/restapi/tutorial/dotnet
            private TokenCache _cache = new TokenCache();
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                // since all packages force async,
                // we have to avoid threading issues
                BeginInvoke((Action)(() => GetMessages()));
            }
    
            private async void GetMessages()
            {
                // use the Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory nuget package for auth
                var authContext = new AuthenticationContext(authority, _cache);
                var result = await authContext.AcquireTokenAsync(
                    new[] { "https://outlook.office.com/mail.read" },
                    null,
                    clientId,
                    new Uri(redirectUri),
                    new PlatformParameters(PromptBehavior.Always, this));
    
                // use the Microsoft.Office365.OutlookServices-V2.0 nuget package from now on
                var client = new OutlookServicesClient(new Uri("https://outlook.office.com/api/v2.0"), () => Task.FromResult(result.Token));
    
                var messages = await client.Me.Messages
                                            .Take(10) // get only 10 messages
                                            .ExecuteAsync();
    
                // fill some list box
                // (beware, some messages have a null subject)
                foreach (var msg in messages.CurrentPage)
                {
                    listBox1.Items.Add(msg.Subject);
                }
            }
        }
    }
    

答案 1 :(得分:0)

我只是想分享(几乎)最终的解决方案,我扩展了@ Simon Mourier 提供的解决方案,以迭代特定文件夹的所有电子邮件,如果是电子邮件来自Youtube,废弃里面的网址,然后收回电子邮件。

什么时候应用?,好吧,只需根据你需要解析电子邮件的情况进行调整,我的情况非常具体,我有大约500个频道订阅,所以我在一个月内从Youtube积累了大约200封电子邮件,大多数电子邮件来自音乐频道,我刚刚阅读了电子邮件以复制网址以使用JDownloader下载它,因此这对于救助者来说非常有用,因为它将为我完成所有任务。

Private Const Authority As String = "https://login.microsoftonline.com/common"
Private Const ClientId As String = "OUR API ID"

' Put your redirect uri here (should be the same).
Private Const RedirectUri As String = "urn:ietf:wg:oauth:2.0:oob"

' We cache the token for the duration of this Form.
' You could/should use the FileCache class provided in the sample here: 
' https://dev.outlook.com/restapi/tutorial/dotnet
Private cache As New TokenCache()

Private Sub Form1_Shown() Handles MyBase.Shown
    ' Since all packages force async, we have to avoid threading issues.
    Me.BeginInvoke(Sub() GetMessages())
End Sub

Private Async Sub GetMessages()

    ' Use the 'Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory' Nuget package for auth.
    Dim authContext As New AuthenticationContext(Authority, cache)
    Dim result As AuthenticationResult =
        Await authContext.AcquireTokenAsync({"https://outlook.office.com/mail.readwrite"},
                                            Nothing, ClientId, New Uri(RedirectUri),
                                            New PlatformParameters(PromptBehavior.Auto, Me))

    ' Use the 'Microsoft.Office365.OutlookServices-V2.0' Nuget package from now on.
    Dim client As New OutlookServicesClient(New Uri("https://outlook.office.com/api/v2.0"),
                                            Function() Task.FromResult(result.Token))

    ' I have a rule set to automatically move all emails received from Youtube to a folder with name "Youtube".
    Dim folder As IMailFolder =
       Await client.[Me].MailFolders.Where(Function(f As IMailFolder) f.DisplayName = "Youtube").ExecuteSingleAsync()

    Dim messages As IPagedCollection(Of IMessage) =
        Await client.[Me].MailFolders.GetById(folder.Id).Messages.ExecuteAsync()

    Do While True
        Me.ParseYoutubeMessages(messages.CurrentPage)

        If messages.MorePagesAvailable Then
            messages = Await messages.GetNextPageAsync
        Else
            Exit Do
        End If
    Loop

End Sub

Private Async Sub ParseYoutubeMessages(ByVal messageList As IReadOnlyList(Of IMessage))

    Dim urlRegex As New Regex("""http://www.youtube.com/.+watch.+uploademail""", RegexOptions.IgnoreCase)

    For Each msg As IMessage In messageList

        If (msg.From.EmailAddress.Name.Equals("YouTube", StringComparison.OrdinalIgnoreCase)) Then

            Dim body As String = msg.Body.Content
            Dim isMatch As Boolean = urlRegex.IsMatch(body)

            If Not (isMatch) Then
                Throw New InvalidOperationException("Youtube url regex doesn't match.")

            Else
                Dim urlMatches As MatchCollection = urlRegex.Matches(body)
                Dim urls As String() =
                    (From m As Match In urlMatches.Cast(Of Match)
                     Select Environment.NewLine & m.Value).Distinct().ToArray()

                File.AppendAllText("C:\Youtube Urls.txt", String.Join("", urls))

                msg.IsRead = True
                Await msg.MoveAsync("DeletedItems")

            End If

        End If

    Next msg

End Sub

答案 2 :(得分:0)

在2018年进行了更新-Microsoft Graph API现在可以控制授予对365世界中对象(包括电子邮件)的访问权限。请参见此链接以获取4个步骤的概述:https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user