Click or drag to resize
MimeKit

MultipartRelated Class

A multipart/related MIME entity.
Inheritance Hierarchy
SystemObject
  MimeKitMimeEntity
    MimeKitMultipart
      MimeKitMultipartRelated

Namespace: MimeKit
Assembly: MimeKit (in MimeKit.dll) Version: 4.3.0
Syntax
C#
public class MultipartRelated : Multipart

The MultipartRelated type exposes the following members.

Constructors
 NameDescription
Public methodMultipartRelated Initialize a new instance of the MultipartRelated class.
Public methodMultipartRelated(MimeEntityConstructorArgs) Initialize a new instance of the MultipartRelated class.
Public methodMultipartRelated(Object) Initialize a new instance of the MultipartRelated class.
Top
Properties
 NameDescription
Public propertyBoundary Get or set the boundary.
(Inherited from Multipart)
Public propertyContentBase Get or set the base content URI.
(Inherited from MimeEntity)
Public propertyContentDisposition Get or set the content disposition.
(Inherited from MimeEntity)
Public propertyContentId Get or set the Content-Id.
(Inherited from MimeEntity)
Public propertyContentLocation Get or set the content location.
(Inherited from MimeEntity)
Public propertyContentType Get the type of the content.
(Inherited from MimeEntity)
Public propertyCount Get the number of parts in the multipart.
(Inherited from Multipart)
Public propertyEpilogue Get or set the epilogue.
(Inherited from Multipart)
Public propertyHeaders Get the list of headers.
(Inherited from MimeEntity)
Public propertyIsAttachment Get a value indicating whether this MimePart is an attachment.
(Inherited from MimeEntity)
Public propertyIsReadOnly Get a value indicating whether this instance is read only.
(Inherited from Multipart)
Public propertyItem Get or set the MimeEntity at the specified index.
(Inherited from Multipart)
Public propertyPreamble Get or set the preamble.
(Inherited from Multipart)
Public propertyCode exampleRoot Get or set the root document of the multipart/related part and the appropriate Content-Type parameters.
Top
Methods
 NameDescription
Public methodAccept Dispatches to the specific visit method for this MIME entity.
(Overrides MultipartAccept(MimeVisitor))
Public methodAdd Add an entity to the multipart.
(Inherited from Multipart)
Public methodClear Clear a multipart.
(Inherited from Multipart)
Public methodClear(Boolean) Clear a multipart.
(Inherited from Multipart)
Public methodContains(MimeEntity) Check if the Multipart contains the specified entity.
(Inherited from Multipart)
Public methodContains(Uri) Check if the MultipartRelated contains a part matching the specified URI.
Public methodCopyTo Copy all of the entities in the Multipart to the specified array.
(Inherited from Multipart)
Public methodDispose Releases all resources used by the MimeEntity object.
(Inherited from MimeEntity)
Protected methodDispose(Boolean) Release the unmanaged resources used by the Multipart and optionally releases the managed resources.
(Inherited from Multipart)
Public methodEquals
(Inherited from Object)
Protected methodFinalize Releases unmanaged resources and performs other cleanup operations before the MimeEntity is reclaimed by garbage collection.
(Inherited from MimeEntity)
Public methodGetEnumerator Get the enumerator for the children of the Multipart.
(Inherited from Multipart)
Public methodGetHashCode
(Inherited from Object)
Public methodGetType
(Inherited from Object)
Public methodIndexOf(MimeEntity) Get the index of an entity.
(Inherited from Multipart)
Public methodCode exampleIndexOf(Uri) Get the index of the part matching the specified URI.
Public methodInsert Insert an entity into the Multipart at the specified index.
(Inherited from Multipart)
Protected methodMemberwiseClone
(Inherited from Object)
Protected methodOnHeadersChanged Called when the headers change in some way.
(Inherited from MimeEntity)
Public methodOpen(Uri) Open a stream for reading the decoded content of the MIME part specified by the provided URI.
Public methodOpen(Uri, String, String) Open a stream for reading the decoded content of the MIME part specified by the provided URI.
Public methodPrepare Prepare the MIME entity for transport using the specified encoding constraints.
(Inherited from Multipart)
Public methodRemove Remove an entity from the multipart.
(Inherited from Multipart)
Public methodRemoveAt Remove an entity from the Multipart at the specified index.
(Inherited from Multipart)
Protected methodRemoveHeader Remove a header by name.
(Inherited from MimeEntity)
Protected methodSetHeader(String, Byte) Set the value of a header using the raw value.
(Inherited from MimeEntity)
Protected methodSetHeader(String, String) Set the value of a header.
(Inherited from MimeEntity)
Public methodToString Return a String that represents the MimeEntity for debugging purposes.
(Inherited from MimeEntity)
Public methodTryGetValue Get the preferred message body if it exists.
(Overrides MultipartTryGetValue(TextFormat, TextPart))
Protected methodTryInit Tries to use the given object to initialize the appropriate property.
(Inherited from MimeEntity)
Public methodWriteTo(Stream, CancellationToken) Write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteTo(String, CancellationToken) Write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteTo(FormatOptions, Stream, CancellationToken) Write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteTo(FormatOptions, String, CancellationToken) Write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteTo(Stream, Boolean, CancellationToken) Write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteTo(String, Boolean, CancellationToken) Write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteTo(FormatOptions, String, Boolean, CancellationToken) Write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteTo(FormatOptions, Stream, Boolean, CancellationToken) Write the Multipart to the specified output stream.
(Inherited from Multipart)
Public methodWriteToAsync(Stream, CancellationToken) Asynchronously write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteToAsync(String, CancellationToken) Asynchronously write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteToAsync(FormatOptions, Stream, CancellationToken) Asynchronously write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteToAsync(FormatOptions, String, CancellationToken) Asynchronously write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteToAsync(Stream, Boolean, CancellationToken) Asynchronously write the MimeEntity to the specified output stream.
(Inherited from MimeEntity)
Public methodWriteToAsync(String, Boolean, CancellationToken) Asynchronously write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteToAsync(FormatOptions, String, Boolean, CancellationToken) Asynchronously write the MimeEntity to the specified file.
(Inherited from MimeEntity)
Public methodWriteToAsync(FormatOptions, Stream, Boolean, CancellationToken) Asynchronously write the Multipart to the specified output stream.
(Inherited from Multipart)
Top
Remarks
A multipart/related MIME entity contains, as one might expect, inter-related MIME parts which typically reference each other via URIs based on the Content-Id and/or Content-Location headers.
Example
C#
/// <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> ();
    string body;

    /// <summary>
    /// Creates a new HtmlPreviewVisitor.
    /// </summary>
    public HtmlPreviewVisitor ()
    {
    }

    /// <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;
    }

    /// <summary>
    /// Get a data: URI for the image attachment.
    /// </summary>
    /// <remarks>
    /// Encodes the image attachment into a string suitable for setting as a src= attribute value in
    /// an img tag.
    /// </remarks>
    /// <returns>The data: URI.</returns>
    /// <param name="image">The image attachment.</param>
    string GetDataUri (MimePart image)
    {
        using (var memory = new MemoryStream ()) {
            image.Content.DecodeTo (memory);
            var buffer = memory.GetBuffer ();
            var length = (int) memory.Length;
            var base64 = Convert.ToBase64String (buffer, 0, length);

            return string.Format ("data:{0};base64,{1}", image.ContentType.MimeType, base64);
        }
    }

    // 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.Meta && !ctx.IsEndTag) {
            bool isContentType = false;

            ctx.WriteTag (htmlWriter, false);

            // replace charsets with "utf-8" since our output will be in utf-8 (and not whatever the original charset was)
            foreach (var attribute in ctx.Attributes) {
                if (attribute.Id == HtmlAttributeId.Charset) {
                    htmlWriter.WriteAttributeName (attribute.Name);
                    htmlWriter.WriteAttributeValue ("utf-8");
                } else if (isContentType && attribute.Id == HtmlAttributeId.Content) {
                    htmlWriter.WriteAttributeName (attribute.Name);
                    htmlWriter.WriteAttributeValue ("text/html; charset=utf-8");
                } else {
                    if (attribute.Id == HtmlAttributeId.HttpEquiv && attribute.Value != null
                        && attribute.Value.Equals ("Content-Type", StringComparison.OrdinalIgnoreCase))
                        isContentType = true;

                    htmlWriter.WriteAttribute (attribute);
                }
            }
        } else if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
            ctx.WriteTag (htmlWriter, false);

            // replace the src attribute with a "data:" URL
            foreach (var attribute in ctx.Attributes) {
                if (attribute.Id == HtmlAttributeId.Src) {
                    if (!TryGetImage (attribute.Value, out var image)) {
                        htmlWriter.WriteAttribute (attribute);
                        continue;
                    }

                    var dataUri = GetDataUri (image);

                    htmlWriter.WriteAttributeName (attribute.Name);
                    htmlWriter.WriteAttributeValue (dataUri);
                } 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.Equals ("oncontextmenu", StringComparison.OrdinalIgnoreCase))
                    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.Equals ("yes", StringComparison.OrdinalIgnoreCase);

            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);
    }
}
See Also