using System.Text; using System.Xml.Linq; namespace ZB.MOM.WW.ScadaBridge.NotificationService.Ews; /// /// Builds the EWS CreateItem SOAP envelope used to send a notification email /// through Exchange Web Services. /// /// Pure and I/O-free: the HTTP send lives in the sender that consumes this. The envelope is /// composed with LINQ-to-XML rather than string concatenation so operator-authored subject and /// body content is XML-escaped by construction. /// /// public static class EwsSoapEnvelope { private static readonly XNamespace Soap = "http://schemas.xmlsoap.org/soap/envelope/"; private static readonly XNamespace Types = "http://schemas.microsoft.com/exchange/services/2006/types"; private static readonly XNamespace Messages = "http://schemas.microsoft.com/exchange/services/2006/messages"; /// /// Builds a CreateItem request that sends one plain-text message. /// /// MessageDisposition="SendOnly" sends without keeping a Sent Items copy, and every /// recipient is addressed via BccRecipients so recipients cannot see one another — /// both mirror the SMTP delivery path's semantics. /// /// /// The sending mailbox address stamped into t:From. /// /// The recipient addresses; each becomes one t:Mailbox under t:BccRecipients. /// Callers are expected to supply at least one — an empty list yields an envelope Exchange /// rejects as schema-invalid. /// /// The message subject; escaped, not interpreted. /// The plain-text message body (BodyType="Text"); escaped, not interpreted. /// The complete SOAP envelope as XML text, prefixed with a UTF-8 declaration. public static string BuildCreateItem( string fromAddress, IReadOnlyList bccRecipients, string subject, string body) { ArgumentNullException.ThrowIfNull(fromAddress); ArgumentNullException.ThrowIfNull(bccRecipients); ArgumentNullException.ThrowIfNull(subject); ArgumentNullException.ThrowIfNull(body); // EWS MessageType is an xs:sequence: Subject, Body, BccRecipients, From must appear in // this order or the server answers ErrorSchemaValidation. var message = new XElement( Types + "Message", new XElement(Types + "Subject", subject), new XElement(Types + "Body", new XAttribute("BodyType", "Text"), body), new XElement(Types + "BccRecipients", bccRecipients.Select(Mailbox)), new XElement(Types + "From", Mailbox(fromAddress))); var document = new XDocument( new XDeclaration("1.0", "utf-8", null), new XElement( Soap + "Envelope", new XAttribute(XNamespace.Xmlns + "soap", Soap.NamespaceName), new XAttribute(XNamespace.Xmlns + "t", Types.NamespaceName), new XAttribute(XNamespace.Xmlns + "m", Messages.NamespaceName), new XElement( Soap + "Header", new XElement(Types + "RequestServerVersion", new XAttribute("Version", "Exchange2013"))), new XElement( Soap + "Body", new XElement( Messages + "CreateItem", new XAttribute("MessageDisposition", "SendOnly"), new XElement(Messages + "Items", message))))); // XDocument.ToString() drops the declaration; saving through a writer that reports UTF-8 // keeps it, and keeps the declared encoding honest. using var writer = new Utf8StringWriter(); document.Save(writer); return writer.ToString(); } private static XElement Mailbox(string address) => new(Types + "Mailbox", new XElement(Types + "EmailAddress", address)); /// /// A that reports UTF-8 so /// emits encoding="utf-8" instead of the default UTF-16. /// private sealed class Utf8StringWriter : StringWriter { /// public override Encoding Encoding => Encoding.UTF8; } }