带有 MimeKit、C# Winforms 和 Google API 的 Gmail 草稿(带附件的 HTML)

Posted

技术标签:

【中文标题】带有 MimeKit、C# Winforms 和 Google API 的 Gmail 草稿(带附件的 HTML)【英文标题】:Gmail Draft (HTML with Attachment) with MimeKit, C# Winforms and Google API 【发布时间】:2016-06-09 20:43:25 【问题描述】:

我正在尝试使用 C# 在 winforms 应用程序中生成 Gmail 草稿消息。草稿消息需要采用 html 格式并且能够包含附件。

我能够使用AE.Net.Mail 生成带有附件的草稿,但草稿邮件是纯文本的(我不知道如何编码AE.Net.Mail 来给我一个HTML Gmail 草稿邮件)。

为了将消息转换为 HTML 格式,我使用 MimeKit 获取 System.Net.Mail 消息并将其转换为 MimeMessage 消息。但是,我无法弄清楚如何按照 Gmail 草案规范的要求将 MIME 消息放入 RFC 2822 格式和 URL 安全的 base64 编码字符串中。

这是 MimeKit 转换尝试的代码

var service = new GmailService(new BaseClientService.Initializer()

    HttpClientInitializer = credential,
    ApplicationName = ApplicationName,
);

MailMessage msg = new MailMessage(); //System.Net.Mail
msg.IsBodyHtml = true;
msg.Subject = "HTML Email";
msg.Body = "<a href = 'http://www.yahoo.com/'>Enjoy Yahoo!</a>";
msg.Attachments.Add(file);

MimeMessage message = MimeMessage.CreateFromMailMessage(msg); //MimeKit conversion

//At this point I cannot figure out how to get the MIME message into 
//an RFC 2822 formatted and URL-safe base64 encoded string
//as required by the Gmail draft specification
//See working code below for how this works in AE.Net.Mail

下面是使用 AE.Net.Mail 的代码,它可以正常工作,但会将 Gmail 草稿的正文生成为纯文本(基于 Jason Pettys 的 this article):

 var service = new GmailService(new BaseClientService.Initializer()
 
     HttpClientInitializer = credential,
     ApplicationName = ApplicationName,
 );

 var msg = new AE.Net.Mail.MailMessage //msg created in plain text not HTML format
 
     Body = "<a href = 'http://www.yahoo.com/'>Enjoy Yahoo!</a>"
 ;

 var bytes = System.IO.File.ReadAllBytes(filePath);
 AE.Net.Mail.Attachment file = new AE.Net.Mail.Attachment(bytes, @"application/pdf", FileName, true);
 msg.Attachments.Add(file);

 var msgStr = new StringWriter();
 msg.Save(msgStr);
 Message m = new Message();
 m.Raw = Base64UrlEncode(msgStr.ToString());

 Draft draft = new Draft(); //Gmail draft
 draft.Message = m;

 service.Users.Drafts.Create(draft, "me").Execute();

 private static string Base64UrlEncode(string input)
 
     var inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
     // Special "url-safe" base64 encode.
     return Convert.ToBase64String(inputBytes)
       .Replace('+', '-')
       .Replace('/', '_')
       .Replace("=", "");
 

有没有办法将 MimeKit 的 MimeMessage 消息转换为 RFC 2822 格式和 URL 安全的 base64 编码字符串,以便可以将其生成为 Gmail 草稿?如果做不到这一点,有没有办法在编码之前以 HTML 格式创建 AE.Net.Mail 消息?非常感谢所有帮助。

【问题讨论】:

您有几篇关于 Drafts API Gmail 的帖子。您是否尝试下载 EML 格式的草稿或消息? @Kiquenet 您在此问题下方的先前评论中提出了问题,我在回复评论中回答,然后两者都被删除。 (也许是你?)现在你在评论中发布了上述问题。我在这篇文章中的问题是一年多前解决的。我不知道您为什么要进一步询问我是否尝试过 EML 格式。我的问题已经得到解答。我确实发布了三个不同的问题。两个得到了答复,一个保持打开状态(我应该关闭它并将其放在我的待办事项列表中)。感谢您的关注,但我不再需要这个特定问题的帮助。 【参考方案1】:

做你想做的最简单的方法是这样的:

static string Base64UrlEncode (MimeMessage message)

    using (var stream = new MemoryStream ()) 
        message.WriteTo (stream);

        return Convert.ToBase64String (stream.GetBuffer (), 0, (int) stream.Length)
            .Replace ('+', '-')
            .Replace ('/', '_')
            .Replace ("=", "");
    

但更有效的方法是实现我们自己的 UrlEncoderFilter 来替换您的“.Replace (...)”逻辑:

using MimeKit;
using MimeKit.IO;
using MimeKit.IO.Filters;

// ...

class UrlEncodeFilter : IMimeFilter

    byte[] output = new byte[8192];

    #region IMimeFilter implementation
    public byte[] Filter (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength)
    
        if (output.Length < input.Length)
            Array.Resize (ref output, input.Length);

        int endIndex = startIndex + length;

        outputLength = 0;
        outputIndex = 0;

        for (int index = startIndex; index < endIndex; index++) 
            switch ((char) input[index]) 
            case '\r': case '\n': case '=': break;
            case '+': output[outputLength++] = (byte) '-'; break;
            case '/': output[outputLength++] = (byte) '_'; break;
            default: output[outputLength++] = input[index]; break;
            
        

        return output;
    

    public byte[] Flush (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength)
    
        return Filter (input, startIndex, length, out outputIndex, out outputLength);
    

    public void Reset ()
    
    
    #endregion

而你使用它的方式是这样的:

static string Base64UrlEncode (MimeMessage message)

    using (var stream = new MemoryStream ()) 
        using (var filtered = new FilteredStream (stream)) 
            filtered.Add (EncoderFilter.Create (ContentEncoding.Base64));
            filtered.Add (new UrlEncodeFilter ());

            message.WriteTo (filtered);
            filtered.Flush ();
        

        return Encoding.ASCII.GetString (stream.GetBuffer (), 0, (int) stream.Length);
    

【讨论】:

谢谢!传递MimeMessage 并将其写入流非常有意义。我最终使用了我的“替换”逻辑,因为我无法让UrlEncoderFilter 工作。我尝试放置在电子邮件正文中的所有 HTML 都没有正确编码,并被放置为 gobbledygook 字符串。如果时间允许,我会在这方面做更多的工作,看看我是否能弄清楚我哪里出错了。非常感谢您的帮助,并感谢您创建 MimeKit!【参考方案2】:

这是我用来创建带有附件的 gmail 草稿的 C# 代码...

using System;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System.IO;
using System.Threading;
using System.Net.Mail;

namespace SendStatusReportsAddin1

    class Program
    
        // If modifying these scopes, delete your previously saved credentials
        // at ~/.credentials/gmail-dotnet-quickstart.json
        //static string[] Scopes =  GmailService.Scope.GmailReadonly ;
        static string[] Scopes =  GmailService.Scope.MailGoogleCom ;
        static string ApplicationName = "Gmail API .NET Quickstart";

        static void Main(string[] args)
        
            //Authorization
              UserCredential credential;

              using (var stream =
                  new FileStream("client_secret2.json", FileMode.Open, FileAccess.Read))
              
                  string credPath = System.Environment.GetFolderPath(
                      System.Environment.SpecialFolder.Personal);
                  credPath = Path.Combine(credPath, ".credentials3/gmail-dotnet.json");

                  credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                      GoogleClientSecrets.Load(stream).Secrets,
                      Scopes,
                      "user",
                      CancellationToken.None,
                      new FileDataStore(credPath, true)).Result;
                  Console.WriteLine("Credential file saved to: " + credPath);
              

            //Create Gmail API service.
              var service = new GmailService(new BaseClientService.Initializer()
              
                  HttpClientInitializer = credential,
                  ApplicationName = ApplicationName,
              );

            //Create mail message
              MailMessage mailmsg = new MailMessage();
              
                  mailmsg.Subject = "My test subject";
                  mailmsg.Body = "<b>My smart message </b>";
                  mailmsg.From = new MailAddress("joe.blow@hotmail.com");
                  mailmsg.To.Add(new MailAddress("jeff.jones@gmail.com"));
                  mailmsg.IsBodyHtml = true;
              

            //add attachment
              string statusreportfile =
                        @"C:\Users\ey96a\Google Drive\10 Status-Vacation-Expense\Status Reports\UUM RewriteStatus Report.pdf";

              Attachment data = new Attachment(statusreportfile);
              mailmsg.Attachments.Add(data);

            //Make mail message a Mime message
              MimeKit.MimeMessage mimemessage = MimeKit.MimeMessage.CreateFromMailMessage(mailmsg);

            //Use Base64URLEncode to encode the Mime message
              Google.Apis.Gmail.v1.Data.Message finalmessage = new Google.Apis.Gmail.v1.Data.Message();
              finalmessage.Raw = Base64UrlEncode(mimemessage.ToString());

            //Create the draft email
              var mydraft = new Google.Apis.Gmail.v1.Data.Draft();
              mydraft.Message = finalmessage;

              var resultdraft = service.Users.Drafts.Create(mydraft, "me").Execute();

            //Send the email (instead of creating a draft)
              var resultsend = service.Users.Messages.Send(finalmessage, "me").Execute();

            //Open the SendStatusReports form
              aOpenForm.myForm1();

          //end of Main

        //Base64 URL encode
        public static string Base64UrlEncode(string input)
        
            var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
            // Special "url-safe" base64 encode.

            return System.Convert.ToBase64String(inputBytes)
                .Replace('+', '-')
                .Replace('/', '_')
                .Replace("=", "");
        

     //end of class Program


【讨论】:

如何找到草稿并发送?【参考方案3】:

如果做不到这一点,有没有办法在编码之前以 HTML 格式创建 AE.Net.Mail 消息?

你可以试试

msg.ContentType = "text/html"; 

在您的 AE.Net.Mail.MailMessage 中

【讨论】:

以上是关于带有 MimeKit、C# Winforms 和 Google API 的 Gmail 草稿(带附件的 HTML)的主要内容,如果未能解决你的问题,请参考以下文章

带有 FileStreams 的 MimeKit 迭代器

如何使用来自客户端 C# Winforms 的 asmx 服务 (BL) 的类函数

为 C# 寻找干净的 WinForms MVC 教程 [关闭]

在 c# winforms 中扩展文本框

在 WinForms c# 面板上绘制矩阵[x,y]

C# WinForms 面板问题