File: System\Net\Mail\MailMessage.cs
Web Access
Project: src\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Mime;
using System.Text;
 
namespace System.Net.Mail
{
    [Flags]
    public enum DeliveryNotificationOptions
    {
        None = 0,
        OnSuccess = 1,
        OnFailure = 2,
        Delay = 4,
        Never = (int)0x08000000
    }
 
    public class MailMessage : IDisposable
    {
        private AlternateViewCollection? _views;
        private AttachmentCollection? _attachments;
        private AlternateView? _bodyView;
        private string? _body = string.Empty;
        private Encoding? _bodyEncoding;
        private TransferEncoding _bodyTransferEncoding = TransferEncoding.Unknown;
        private bool _isBodyHtml;
        private bool _disposed;
        private readonly Message _message;
        private DeliveryNotificationOptions _deliveryStatusNotification = DeliveryNotificationOptions.None;
 
        public MailMessage()
        {
            _message = new Message();
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Associate(this, _message);
        }
 
        public MailMessage(string from, string to)
        {
            ArgumentException.ThrowIfNullOrEmpty(from);
            ArgumentException.ThrowIfNullOrEmpty(to);
 
            _message = new Message(from, to);
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Associate(this, _message);
        }
 
 
        public MailMessage(string from, string to, string? subject, string? body) : this(from, to)
        {
            Subject = subject;
            Body = body;
        }
 
 
        public MailMessage(MailAddress from, MailAddress to)
        {
            ArgumentNullException.ThrowIfNull(from);
            ArgumentNullException.ThrowIfNull(to);
 
            _message = new Message(from, to);
        }
 
        [DisallowNull]
        public MailAddress? From
        {
            get
            {
                return _message.From;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _message.From = value;
            }
        }
 
        [DisallowNull]
        public MailAddress? Sender
        {
            get
            {
                return _message.Sender;
            }
            set
            {
                _message.Sender = value;
            }
        }
 
        [Obsolete("ReplyTo has been deprecated. Use ReplyToList instead, which can accept multiple addresses.")]
        public MailAddress? ReplyTo
        {
            get
            {
                return _message.ReplyTo;
            }
            set
            {
                _message.ReplyTo = value;
            }
        }
 
        public MailAddressCollection ReplyToList
        {
            get
            {
                return _message.ReplyToList;
            }
        }
 
        public MailAddressCollection To
        {
            get
            {
                return _message.To;
            }
        }
 
        public MailAddressCollection Bcc
        {
            get
            {
                return _message.Bcc;
            }
        }
 
        public MailAddressCollection CC
        {
            get
            {
                return _message.CC;
            }
        }
 
        public MailPriority Priority
        {
            get
            {
                return _message.Priority;
            }
            set
            {
                _message.Priority = value;
            }
        }
 
        public DeliveryNotificationOptions DeliveryNotificationOptions
        {
            get
            {
                return _deliveryStatusNotification;
            }
            set
            {
                if (7 < (uint)value && value != DeliveryNotificationOptions.Never)
                {
                    throw new ArgumentOutOfRangeException(nameof(value));
                }
                _deliveryStatusNotification = value;
            }
        }
 
        [AllowNull]
        public string Subject
        {
            get
            {
                return _message.Subject ?? string.Empty;
            }
            set
            {
                _message.Subject = value;
            }
        }
 
        public Encoding? SubjectEncoding
        {
            get
            {
                return _message.SubjectEncoding;
            }
            set
            {
                _message.SubjectEncoding = value;
            }
        }
 
        public NameValueCollection Headers
        {
            get
            {
                return _message.Headers;
            }
        }
 
        public Encoding? HeadersEncoding
        {
            get
            {
                return _message.HeadersEncoding;
            }
            set
            {
                _message.HeadersEncoding = value;
            }
        }
 
        [AllowNull]
        public string Body
        {
            get
            {
                return _body ?? string.Empty;
            }
 
            set
            {
                _body = value;
 
                if (_bodyEncoding == null && _body != null)
                {
                    if (MimeBasePart.IsAscii(_body, true))
                    {
                        _bodyEncoding = Text.Encoding.ASCII;
                    }
                    else
                    {
                        _bodyEncoding = Text.Encoding.GetEncoding(MimeBasePart.DefaultCharSet);
                    }
                }
            }
        }
 
        public Encoding? BodyEncoding
        {
            get
            {
                return _bodyEncoding;
            }
            set
            {
                _bodyEncoding = value;
            }
        }
 
        public TransferEncoding BodyTransferEncoding
        {
            get
            {
                return _bodyTransferEncoding;
            }
            set
            {
                _bodyTransferEncoding = value;
            }
        }
 
 
        public bool IsBodyHtml
        {
            get
            {
                return _isBodyHtml;
            }
            set
            {
                _isBodyHtml = value;
            }
        }
 
 
        public AttachmentCollection Attachments
        {
            get
            {
                ObjectDisposedException.ThrowIf(_disposed, this);
 
                return _attachments ??= new AttachmentCollection();
            }
        }
        public AlternateViewCollection AlternateViews
        {
            get
            {
                ObjectDisposedException.ThrowIf(_disposed, this);
 
                return _views ??= new AlternateViewCollection();
            }
        }
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing && !_disposed)
            {
                _disposed = true;
 
                _views?.Dispose();
                _attachments?.Dispose();
                _bodyView?.Dispose();
            }
        }
 
 
        private void SetContent(bool allowUnicode)
        {
            //the attachments may have changed, so we need to reset the message
            if (_bodyView != null)
            {
                _bodyView.Dispose();
                _bodyView = null;
            }
 
            if (AlternateViews.Count == 0 && Attachments.Count == 0)
            {
                if (!string.IsNullOrEmpty(_body))
                {
                    _bodyView = AlternateView.CreateAlternateViewFromString(_body, _bodyEncoding, (_isBodyHtml ? MediaTypeNames.Text.Html : null));
                    _message.Content = _bodyView.MimePart;
                }
            }
            else if (AlternateViews.Count == 0 && Attachments.Count > 0)
            {
                MimeMultiPart part = new MimeMultiPart(MimeMultiPartType.Mixed);
 
                if (!string.IsNullOrEmpty(_body))
                {
                    _bodyView = AlternateView.CreateAlternateViewFromString(_body, _bodyEncoding, (_isBodyHtml ? MediaTypeNames.Text.Html : null));
                }
                else
                {
                    _bodyView = AlternateView.CreateAlternateViewFromString(string.Empty);
                }
 
                part.Parts.Add(_bodyView.MimePart);
 
                foreach (Attachment attachment in Attachments)
                {
                    if (attachment != null)
                    {
                        //ensure we can read from the stream.
                        attachment.PrepareForSending(allowUnicode);
                        part.Parts.Add(attachment.MimePart);
                    }
                }
                _message.Content = part;
            }
            else
            {
                // we should not unnecessarily use Multipart/Mixed
                // When there is no attachement and all the alternative views are of "Alternative" types.
                MimeMultiPart? part;
                MimeMultiPart viewsPart = new MimeMultiPart(MimeMultiPartType.Alternative);
 
                if (!string.IsNullOrEmpty(_body))
                {
                    _bodyView = AlternateView.CreateAlternateViewFromString(_body, _bodyEncoding, null);
                    viewsPart.Parts.Add(_bodyView.MimePart);
                }
 
                foreach (AlternateView view in AlternateViews)
                {
                    //ensure we can read from the stream.
                    if (view != null)
                    {
                        view.PrepareForSending(allowUnicode);
                        if (view.LinkedResources.Count > 0)
                        {
                            MimeMultiPart wholeView = new MimeMultiPart(MimeMultiPartType.Related);
                            wholeView.ContentType.Parameters["type"] = view.ContentType.MediaType;
                            wholeView.ContentLocation = view.MimePart.ContentLocation;
                            wholeView.Parts.Add(view.MimePart);
 
                            foreach (LinkedResource resource in view.LinkedResources)
                            {
                                //ensure we can read from the stream.
                                resource.PrepareForSending(allowUnicode);
 
                                wholeView.Parts.Add(resource.MimePart);
                            }
                            viewsPart.Parts.Add(wholeView);
                        }
                        else
                        {
                            viewsPart.Parts.Add(view.MimePart);
                        }
                    }
                }
 
                if (Attachments.Count > 0)
                {
                    part = new MimeMultiPart(MimeMultiPartType.Mixed);
                    part.Parts.Add(viewsPart);
 
                    foreach (Attachment attachment in Attachments)
                    {
                        if (attachment != null)
                        {
                            //ensure we can read from the stream.
                            attachment.PrepareForSending(allowUnicode);
                            part.Parts.Add(attachment.MimePart);
                        }
                    }
                    _message.Content = part;
                }
                // If there is no Attachement, AND only "1" Alternate View AND !!no body!!
                // then in fact, this is NOT a multipart region.
                else if (viewsPart.Parts.Count == 1 && string.IsNullOrEmpty(_body))
                {
                    _message.Content = viewsPart.Parts[0];
                }
                else
                {
                    _message.Content = viewsPart;
                }
            }
 
            if (_bodyView != null && _bodyTransferEncoding != TransferEncoding.Unknown)
            {
                _bodyView.TransferEncoding = _bodyTransferEncoding;
            }
        }
 
        internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)
        {
            SetContent(allowUnicode);
            _message.Send(writer, sendEnvelope, allowUnicode);
        }
 
        internal IAsyncResult BeginSend(BaseWriter writer, bool allowUnicode,
            AsyncCallback? callback, object? state)
        {
            SetContent(allowUnicode);
            return _message.BeginSend(writer, allowUnicode, callback, state);
        }
 
        internal void EndSend(IAsyncResult asyncResult)
        {
            _message.EndSend(asyncResult);
        }
 
        internal string BuildDeliveryStatusNotificationString()
        {
            if (_deliveryStatusNotification != DeliveryNotificationOptions.None)
            {
                StringBuilder s = new StringBuilder(" NOTIFY=");
 
                bool oneSet = false;
 
                //none
                if (_deliveryStatusNotification == DeliveryNotificationOptions.Never)
                {
                    s.Append("NEVER");
                    return s.ToString();
                }
 
                if ((((int)_deliveryStatusNotification) & (int)DeliveryNotificationOptions.OnSuccess) > 0)
                {
                    s.Append("SUCCESS");
                    oneSet = true;
                }
                if ((((int)_deliveryStatusNotification) & (int)DeliveryNotificationOptions.OnFailure) > 0)
                {
                    if (oneSet)
                    {
                        s.Append(',');
                    }
                    s.Append("FAILURE");
                    oneSet = true;
                }
                if ((((int)_deliveryStatusNotification) & (int)DeliveryNotificationOptions.Delay) > 0)
                {
                    if (oneSet)
                    {
                        s.Append(',');
                    }
                    s.Append("DELAY");
                }
                return s.ToString();
            }
            return string.Empty;
        }
    }
}