2016-01-24 7 views
3

Я использовал библиотеку EAgetmail для извлечения тела указанного электронного письма, и он работал хорошо, однако теперь я использую Mailkit. Проблема заключается в том, что EAgetmail эквивалентен message.body возвращает тело, поскольку пользователь видит его в почтовых клиентах, но в mailkit он возвращает много разных данных.Как получить тело WYSIWYG сообщения электронной почты с использованием MimeKit

Это соответствующий код:

using (var client = new ImapClient()) 
{ 
    client.Connect(emailServer, 993, true); 
    client.AuthenticationMechanisms.Remove("XOAUTH2"); 
    client.Authenticate(username, password); 
    var inbox = client.Inbox; 
    inbox.Open(FolderAccess.ReadOnly); 
    SearchQuery query; 
    if (checkBox.IsChecked == false) 
    { 
     query = SearchQuery.DeliveredBefore((DateTime)dateEnd).And(
      SearchQuery.DeliveredAfter((DateTime)dateStart)).And(
      SearchQuery.SubjectContains("Subject to find")); 
    } 
    else 
    { 
     query = SearchQuery.SubjectContains("Subject to find"); 
    } 
    foreach (var uid in inbox.Search(query)) 
    { 
     var message = inbox.GetMessage(uid); 
     formEmails.Add(message.TextBody); 
     messageDate.Add(message.Date.LocalDateTime); 
    } 
    client.Disconnect(true); 
} 

Я также попытался message.Body.ToString() и поиск частей сообщения для обычного текста, но и не работал. Мой вопрос в том, как воспроизвести эффект свойства .body объекта EAgetmail с помощью Mailkit (чтобы вернуть только содержимое тела в виде обычного текста, как видит пользователь)?

ответ

10

Общее недоразумение в отношении электронной почты заключается в том, что существует четкое тело сообщения, а затем список вложений. На самом деле это не так. Реальность такова, что MIME является древовидной структурой контента, как файловая система.

К счастью, MIME определяет набор общих правил того, как почтовые клиенты должны интерпретировать эту древовидную структуру частей MIME. Заголовок Content-Disposition предназначен для предоставления подсказок принимающему клиенту относительно того, какие части предназначены для отображения как части тела сообщения и которые предназначены для интерпретации как вложения.

Заголовок Content-Disposition, как правило, имеет одно из двух значений: inline или attachment.

Смысл этих значений должен быть достаточно очевиден. Если значение равно attachment, то содержимое указанной части MIME предназначено для представления в виде прикрепленного файла отдельно от основного сообщения. Однако, если значение равно inline, то содержимое этой части MIME предназначено для отображения внутри линии в рендеринге почтового клиента основного тела сообщения. Если заголовок Content-Disposition не существует, его следует обрабатывать так, как если бы значение было inline.

Технически каждая часть, которой не хватает заголовка Content-Disposition, или который обозначен как inline, является частью основного тела сообщения.

Это немного больше, чем это.

Современные сообщения MIME часто содержат контейнер MIME multipart/alternative, который, как правило, содержит text/plain и text/html текст, который отправил отправитель. Версия text/html обычно форматируется гораздо ближе к тому, что увидел отправитель в своем редакторе WYSIWYG, чем версия text/plain.

Причина отправки текста сообщения в обоих форматах заключается в том, что не все почтовые клиенты способны отображать HTML.

Принимающий клиент должен отображать только один из альтернативных видов, содержащийся в контейнере multipart/alternative. Поскольку альтернативные представления перечислены в порядке наименее верного большинству верующих с тем, что увидел отправитель в своем редакторе WYSIWYG, получающий клиент должен пройти по списку альтернативных представлений, начиная с конца, и работать назад, пока не найдет ту часть, что она способен отображать.

Пример:

multipart/alternative 
    text/plain 
    text/html 

Как видно в приведенном выше примере, text/html часть является последним в списке, потому что это самый верный, что отправитель видел в своем редакторе WYSIWYG при написании сообщения.

Чтобы сделать еще более сложным, иногда современные почтовые клиенты будут использовать контейнер MIME multipart/related вместо простой части text/html, чтобы вставлять изображения и другой мультимедийный контент в HTML.

Пример:

multipart/alternative 
    text/plain 
    multipart/related 
    text/html 
    image/jpeg 
    video/mp4 
    image/png 

В приведенном выше примере, один из альтернативных точек зрения является multipart/related контейнером, который содержит HTML версию тела сообщения, который ссылается на родственные видео и изображения.

Теперь, когда у вас есть общее представление о том, как структурировано сообщение и как интерпретировать различные объекты MIME, мы можем начать выяснять, как на самом деле визуализировать сообщение, как предполагалось.

Использование MimeVisitor (наиболее точным способ визуализации сообщений)

MimeKit включает MimeVisitor класса для посещения каждого узла в структуре MIME дерева. Например, следующий MimeVisitor подкласс может быть использован для генерации HTML, чтобы быть оказаны с помощью управления браузером (например, как WebBrowser):

/// <summary> 
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control. 
/// </summary> 
class HtmlPreviewVisitor : MimeVisitor 
{ 
    List<MultipartRelated> stack = new List<MultipartRelated>(); 
    List<MimeEntity> attachments = new List<MimeEntity>(); 
    readonly string tempDir; 
    string body; 

    /// <summary> 
    /// Creates a new HtmlPreviewVisitor. 
    /// </summary> 
    /// <param name="tempDirectory">A temporary directory used for storing image files.</param> 
    public HtmlPreviewVisitor (string tempDirectory) 
    { 
     tempDir = tempDirectory; 
    } 

    /// <summary> 
    /// The list of attachments that were in the MimeMessage. 
    /// </summary> 
    public IList<MimeEntity> Attachments { 
     get { return attachments; } 
    } 

    /// <summary> 
    /// The HTML string that can be set on the BrowserControl. 
    /// </summary> 
    public string HtmlBody { 
     get { return body ?? string.Empty; } 
    } 

    protected override void VisitMultipartAlternative (MultipartAlternative alternative) 
    { 
     // walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful 
     for (int i = alternative.Count - 1; i >= 0 && body == null; i--) 
      alternative[i].Accept (this); 
    } 

    protected override void VisitMultipartRelated (MultipartRelated related) 
    { 
     var root = related.Root; 

     // push this multipart/related onto our stack 
     stack.Add (related); 

     // visit the root document 
     root.Accept (this); 

     // pop this multipart/related off our stack 
     stack.RemoveAt (stack.Count - 1); 
    } 

    // look up the image based on the img src url within our multipart/related stack 
    bool TryGetImage (string url, out MimePart image) 
    { 
     UriKind kind; 
     int index; 
     Uri uri; 

     if (Uri.IsWellFormedUriString (url, UriKind.Absolute)) 
      kind = UriKind.Absolute; 
     else if (Uri.IsWellFormedUriString (url, UriKind.Relative)) 
      kind = UriKind.Relative; 
     else 
      kind = UriKind.RelativeOrAbsolute; 

     try { 
      uri = new Uri (url, kind); 
     } catch { 
      image = null; 
      return false; 
     } 

     for (int i = stack.Count - 1; i >= 0; i--) { 
      if ((index = stack[i].IndexOf (uri)) == -1) 
       continue; 

      image = stack[i][index] as MimePart; 
      return image != null; 
     } 

     image = null; 

     return false; 
    } 

    // Save the image to our temp directory and return a "file://" url suitable for 
    // the browser control to load. 
    // Note: if you'd rather embed the image data into the HTML, you can construct a 
    // "data:" url instead. 
    string SaveImage (MimePart image, string url) 
    { 
     string fileName = url.Replace (':', '_').Replace ('\\', '_').Replace ('/', '_'); 

     string path = Path.Combine (tempDir, fileName); 

     if (!File.Exists (path)) { 
      using (var output = File.Create (path)) 
       image.ContentObject.DecodeTo (output); 
     } 

     return "file://" + path.Replace ('\\', '/'); 
    } 

    // Replaces <img src=...> urls that refer to images embedded within the message with 
    // "file://" urls that the browser control will actually be able to load. 
    void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter) 
    { 
     if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) { 
      ctx.WriteTag (htmlWriter, false); 

      // replace the src attribute with a file:// URL 
      foreach (var attribute in ctx.Attributes) { 
       if (attribute.Id == HtmlAttributeId.Src) { 
        MimePart image; 
        string url; 

        if (!TryGetImage (attribute.Value, out image)) { 
         htmlWriter.WriteAttribute (attribute); 
         continue; 
        } 

        url = SaveImage (image, attribute.Value); 

        htmlWriter.WriteAttributeName (attribute.Name); 
        htmlWriter.WriteAttributeValue (url); 
       } else { 
        htmlWriter.WriteAttribute (attribute); 
       } 
      } 
     } else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag) { 
      ctx.WriteTag (htmlWriter, false); 

      // add and/or replace oncontextmenu="return false;" 
      foreach (var attribute in ctx.Attributes) { 
       if (attribute.Name.ToLowerInvariant() == "oncontextmenu") 
        continue; 

       htmlWriter.WriteAttribute (attribute); 
      } 

      htmlWriter.WriteAttribute ("oncontextmenu", "return false;"); 
     } else { 
      // pass the tag through to the output 
      ctx.WriteTag (htmlWriter, true); 
     } 
    } 

    protected override void VisitTextPart (TextPart entity) 
    { 
     TextConverter converter; 

     if (body != null) { 
      // since we've already found the body, treat this as an attachment 
      attachments.Add (entity); 
      return; 
     } 

     if (entity.IsHtml) { 
      converter = new HtmlToHtml { 
       HtmlTagCallback = HtmlTagCallback 
      }; 
     } else if (entity.IsFlowed) { 
      var flowed = new FlowedToHtml(); 
      string delsp; 

      if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp)) 
       flowed.DeleteSpace = delsp.ToLowerInvariant() == "yes"; 

      converter = flowed; 
     } else { 
      converter = new TextToHtml(); 
     } 

     body = converter.Convert (entity.Text); 
    } 

    protected override void VisitTnefPart (TnefPart entity) 
    { 
     // extract any attachments in the MS-TNEF part 
     attachments.AddRange (entity.ExtractAttachments()); 
    } 

    protected override void VisitMessagePart (MessagePart entity) 
    { 
     // treat message/rfc822 parts as attachments 
     attachments.Add (entity); 
    } 

    protected override void VisitMimePart (MimePart entity) 
    { 
     // realistically, if we've gotten this far, then we can treat this as an attachment 
     // even if the IsAttachment property is false. 
     attachments.Add (entity); 
    } 
} 

И так, как вы бы использовать этот посетитель может выглядеть примерно так:

void Render (MimeMessage message) 
{ 
    var tmpDir = Path.Combine (Path.GetTempPath(), message.MessageId); 
    var visitor = new HtmlPreviewVisitor (tmpDir); 

    Directory.CreateDirectory (tmpDir); 

    message.Accept (visitor); 

    DisplayHtml (visitor.HtmlBody); 
    DisplayAttachments (visitor.Attachments); 
} 

Использование свойств TextBody и HtmlBody (самый простой способ)

Чтобы упростить общую задачу получения текста сообщение MimeMessage содержит два свойства, которые могут помочь вам получить версию тела сообщения text/plain или text/html. Это TextBody и HtmlBody, соответственно.

Имейте в виду, однако, что по крайней мере с HtmlBody собственности, может быть, что HTML часть является дочерним элементом multipart/related, что позволяет ссылаться на изображения и другие виды средств массовой информации, которые также содержатся в этом multipart/related организация. Это свойство действительно только свойство удобства и не является действительно хорошей заменой для перемещения структуры MIME самостоятельно, чтобы вы могли правильно интерпретировать связанный контент.

+0

Спасибо за ответ. Если бы я не заботился о форматировании и хотел только сохранить необработанный текст в строку, как бы это сделать, учитывая некоторые из сообщений, с которыми я работаю, не содержат текстовую/открытую часть. Должен ли я просто удалить теги из text/html или каким-то образом преобразовать его? – non

+1

Сначала я должен проверить свойство 'TextBody' на' MimeMessage', но если сообщение не содержит 'text/plain', оно будет null. Учитывая, что, да, следующим шагом будет стрижка тегов HTML. MimeKit имеет «HtmlTokenizer», который может оказаться полезным для этой цели. – jstedfast

+0

Спасибо, я попытаюсь заставить его работать с помощью HtmlTokenizer. – non

Смежные вопросы