3

EAgetmail というライブラリを使って指定したメールの本文を取得していたのですが、うまくいきましたが、今は Mailkit を使っています。問題は、EAgetmail では、message.body に相当するものは、ユーザーが電子メール クライアントで見るのと同じように本文を返しますが、mailkit では多くの異なるデータを返します。

これは関連するコードです:

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() を試し、メッセージ部分でプレーンテキストを検索しましたが、どちらも機能しませんでした。私の質問は、Mailkit を使用して EAgetmail の .body プロパティの効果を複製するにはどうすればよいですか (ユーザーが見るように、本文の内容のみをプレーン テキストで返すため)?

4

2 に答える 2

12

電子メールに関する一般的な誤解は、明確に定義されたメッセージ本文と添付ファイルのリストがあるというものです。これは実際には当てはまりません。実際には、MIME はコンテンツのツリー構造であり、ファイル システムによく似ています。

幸いなことに、MIME では、メール クライアントがこの MIME パーツのツリー構造をどのように解釈するかについて、一連の一般的な規則が定義されています。ヘッダーは、Content-Dispositionどの部分がメッセージ本文の一部として表示されることを意図しており、どの部分が添付ファイルとして解釈されることを意図しているかについて、受信クライアントにヒントを提供することを目的としています。

ヘッダーは通常、またはContent-Dispositionの 2 つの値のinlineいずれかになりattachmentます。

これらの値の意味は明らかです。値が の場合、attachmentMIME 部分のコンテンツは、コア メッセージとは別の添付ファイルとして表示されることを意味します。ただし、値が の場合、inlineその MIME 部分のコンテンツは、メール クライアントのコア メッセージ本文のレンダリング内にインラインで表示されることを意味します。Content-Dispositionヘッダーが存在しない場合は、値が であるかのように処理する必要がありますinline

Content-Disposition技術的には、ヘッダーがない、または としてマークされているすべての部分inlineは、コア メッセージ本文の一部です。

ただし、それだけではありません。

最近の MIME メッセージには、通常、送信者が書いたテキストのバージョンを含むmultipart/alternativeMIME コンテナが含まれていることがよくあります。通常、バージョンは、送信者が WYSIWYG エディターで見たバージョンよりもはるかに近い形式になっています。text/plaintext/htmltext/htmltext/plain

メッセージ テキストを両方の形式で送信する理由は、すべてのメール クライアントが HTML を表示できるわけではないためです。

受信クライアントは、コンテナー内に含まれる代替ビューの 1 つだけを表示する必要がありますmultipart/alternative。代替ビューは、送信者が WYSIWYG エディタで見たものに最も忠実でないものから最も忠実なものの順にリストされているため、受信側のクライアントは、代替ビューのリストを最後から始めて、その部分が見つかるまで逆方向に作業する必要があります。表示可能です。

例:

multipart/alternative
  text/plain
  text/html

上記の例に見られるようにtext/html、送信者がメッセージを作成するときに WYSIWYG エディターで見たものに最も忠実であるため、パーツは最後にリストされています。

さらに複雑なことに、最新のメール クライアントは、HTML 内に画像やその他のマルチメディア コンテンツを埋め込むためにmultipart/related、単純なパーツではなく MIME コンテナーを使用することがあります。text/html

例:

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

上記の例では、代替ビューの 1 つはmultipart/related、兄弟のビデオと画像を参照するメッセージ本文の HTML バージョンを含むコンテナーです。

メッセージがどのように構造化され、さまざまな MIME エンティティをどのように解釈するかについて大まかなアイデアが得られたので、メッセージを意図したとおりに実際にレンダリングする方法を考え始めることができます。

MimeVisitor の使用 (メッセージをレンダリングする最も正確な方法)

MimeKit にはMimeVisitor、MIME ツリー構造の各ノードにアクセスするためのクラスが含まれています。たとえば、次のMimeVisitorサブクラスを使用して、ブラウザ コントロール ( などWebBrowser)によってレンダリングされる HTML を生成できます。

/// <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を取得するのに役立つ 2 つのプロパティが含まれています。これらは、それぞれ と です。text/plaintext/htmlTextBodyHtmlBody

ただし、少なくともHtmlBodyプロパティでは、HTML 部分が の子である可能性があることに注意してください。これにより、そのエンティティmultipart/relatedに含まれる画像やその他の種類のメディアを参照できます。multipart/relatedこのプロパティは実際には単なる便利なプロパティであり、関連するコンテンツを適切に解釈できるように、MIME 構造を自分でトラバースする代わりにはなりません。

于 2016-01-25T13:58:50.303 に答える