// // MessageFault.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2005-2009 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Xml; namespace System.ServiceModel.Channels { public abstract class MessageFault { // type members public static MessageFault CreateFault (Message message, int maxBufferSize) { try { if (message.Version.Envelope == EnvelopeVersion.Soap11) return CreateFault11 (message, maxBufferSize); else // common to None and SOAP12 return CreateFault12 (message, maxBufferSize); } catch (XmlException ex) { throw new CommunicationException ("Received an invalid SOAP Fault message", ex); } } static MessageFault CreateFault11 (Message message, int maxBufferSize) { FaultCode fc = null; FaultReason fr = null; object details = null; XmlDictionaryReader r = message.GetReaderAtBodyContents (); r.ReadStartElement ("Fault", message.Version.Envelope.Namespace); r.MoveToContent (); while (r.NodeType != XmlNodeType.EndElement) { switch (r.LocalName) { case "faultcode": fc = ReadFaultCode11 (r); break; case "faultstring": fr = new FaultReason (r.ReadElementContentAsString()); break; case "detail": return new XmlReaderDetailMessageFault (message, r, fc, fr, null, null); case "faultactor": default: throw new NotImplementedException (); } r.MoveToContent (); } r.ReadEndElement (); if (fr == null) throw new XmlException ("Reason is missing in the Fault message"); if (details == null) return CreateFault (fc, fr); return CreateFault (fc, fr, details); } static MessageFault CreateFault12 (Message message, int maxBufferSize) { FaultCode fc = null; FaultReason fr = null; string node = null; XmlDictionaryReader r = message.GetReaderAtBodyContents (); r.ReadStartElement ("Fault", message.Version.Envelope.Namespace); for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) { if (r.NamespaceURI != message.Version.Envelope.Namespace) { r.Skip (); continue; } switch (r.LocalName) { case "Code": fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace); break; case "Reason": fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace); break; case "Node": node = r.ReadElementContentAsString (); break; case "Role": r.Skip (); // no corresponding member to store. break; case "Detail": if (!r.IsEmptyElement) return new XmlReaderDetailMessageFault (message, r, fc, fr, null, node); r.Read (); break; default: throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name)); } } if (fr == null) throw new XmlException ("Reason is missing in the Fault message"); r.ReadEndElement (); return new SimpleMessageFault (fc, fr, false, null, null, null, node); } static FaultCode ReadFaultCode11 (XmlDictionaryReader r) { FaultCode subcode = null; XmlQualifiedName value = XmlQualifiedName.Empty; if (r.IsEmptyElement) throw new ArgumentException ("Fault Code is mandatory in SOAP fault message."); r.ReadStartElement ("faultcode"); r.MoveToContent (); while (r.NodeType != XmlNodeType.EndElement) { if (r.NodeType == XmlNodeType.Element) subcode = ReadFaultCode11 (r); else value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver); r.MoveToContent (); } r.ReadEndElement (); return new FaultCode (value.Name, value.Namespace, subcode); } static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns) { FaultCode subcode = null; XmlQualifiedName value = XmlQualifiedName.Empty; if (r.IsEmptyElement) throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code."); r.ReadStartElement (); // could be either Code or SubCode r.MoveToContent (); while (r.NodeType != XmlNodeType.EndElement) { switch (r.LocalName) { case "Subcode": subcode = ReadFaultCode12 (r, ns); break; case "Value": value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns); break; default: throw new ArgumentException (String.Format ("Unexpected Fault Code subelement: '{0}'", r.LocalName)); } r.MoveToContent (); } r.ReadEndElement (); return new FaultCode (value.Name, value.Namespace, subcode); } static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns) { List l = new List (); if (r.IsEmptyElement) throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text."); r.ReadStartElement ("Reason", ns); for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) { string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace"); if (lang == null) throw new XmlException ("xml:lang is mandatory on fault reason Text"); l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang)); } r.ReadEndElement (); return new FaultReason (l); } public static MessageFault CreateFault (FaultCode code, string reason) { return CreateFault (code, new FaultReason (reason)); } public static MessageFault CreateFault (FaultCode code, FaultReason reason) { return new SimpleMessageFault (code, reason, false, null, null, null, null); } public static MessageFault CreateFault (FaultCode code, FaultReason reason, object detail) { return new SimpleMessageFault (code, reason, true, detail, new DataContractSerializer (detail.GetType ()), null, null); } public static MessageFault CreateFault (FaultCode code, FaultReason reason, object detail, XmlObjectSerializer formatter) { return new SimpleMessageFault (code, reason, true, detail, formatter, String.Empty, String.Empty); } public static MessageFault CreateFault (FaultCode code, FaultReason reason, object detail, XmlObjectSerializer formatter, string actor) { return new SimpleMessageFault (code, reason, true, detail, formatter, actor, String.Empty); } public static MessageFault CreateFault (FaultCode code, FaultReason reason, object detail, XmlObjectSerializer formatter, string actor, string node) { return new SimpleMessageFault (code, reason, true, detail, formatter, actor, node); } // pretty simple implementation class internal abstract class BaseMessageFault : MessageFault { string actor, node; FaultCode code; FaultReason reason; protected BaseMessageFault (FaultCode code, FaultReason reason, string actor, string node) { this.code = code; this.reason = reason; this.actor = actor; this.node = node; } public override string Actor { get { return actor; } } public override FaultCode Code { get { return code; } } public override string Node { get { return node; } } public override FaultReason Reason { get { return reason; } } } internal class SimpleMessageFault : BaseMessageFault { bool has_detail; object detail; XmlObjectSerializer formatter; public SimpleMessageFault (FaultCode code, FaultReason reason, bool has_detail, object detail, XmlObjectSerializer formatter, string actor, string node) : this (code, reason, detail, formatter, actor, node) { this.has_detail = has_detail; } public SimpleMessageFault (FaultCode code, FaultReason reason, object detail, XmlObjectSerializer formatter, string actor, string node) : base (code, reason, actor, node) { if (code == null) throw new ArgumentNullException ("code"); if (reason == null) throw new ArgumentNullException ("reason"); this.detail = detail; this.formatter = formatter; } public override bool HasDetail { // it is not simply "detail != null" since // null detail could become get { return has_detail; } } protected override void OnWriteDetailContents (XmlDictionaryWriter writer) { if (formatter == null && detail != null) formatter = new DataContractSerializer (detail.GetType ()); if (formatter != null) formatter.WriteObject (writer, detail); else throw new InvalidOperationException ("There is no fault detail to write"); } public object Detail { get { return detail; } } } class XmlReaderDetailMessageFault : BaseMessageFault { XmlDictionaryReader reader; bool consumed; bool has_detail; public XmlReaderDetailMessageFault (Message message, XmlDictionaryReader reader, FaultCode code, FaultReason reason, string actor, string node) : base (code, reason, actor, node) { this.reader = reader; if (reader.IsEmptyElement) has_detail = false; reader.MoveToContent (); reader.ReadStartElement (); // consume the wrapper reader.MoveToContent (); has_detail = reader.NodeType != XmlNodeType.EndElement; } void Consume () { if (consumed) throw new InvalidOperationException ("The fault detail content is already consumed"); consumed = true; } public override bool HasDetail { get { return has_detail; } } protected override XmlDictionaryReader OnGetReaderAtDetailContents () { Consume (); return reader; } protected override void OnWriteDetailContents (XmlDictionaryWriter writer) { if (!HasDetail) throw new InvalidOperationException ("There is no fault detail to write"); Consume (); while (reader.NodeType != XmlNodeType.EndElement) writer.WriteNode (reader, false); } } // instance members protected MessageFault () { } [MonoTODO ("is this true?")] public virtual string Actor { get { return String.Empty; } } public abstract FaultCode Code { get; } public abstract bool HasDetail { get; } [MonoTODO ("is this true?")] public virtual string Node { get { return String.Empty; } } public abstract FaultReason Reason { get; } public T GetDetail () { return GetDetail (new DataContractSerializer (typeof (T))); } public T GetDetail (XmlObjectSerializer formatter) { if (!HasDetail) throw new InvalidOperationException ("This message does not have details."); return (T) formatter.ReadObject (GetReaderAtDetailContents ()); } public XmlDictionaryReader GetReaderAtDetailContents () { return OnGetReaderAtDetailContents (); } public void WriteTo (XmlDictionaryWriter writer, EnvelopeVersion version) { writer.WriteStartElement ("Fault", version.Namespace); WriteFaultCode (writer, version, Code, false); WriteReason (writer, version); if (HasDetail) OnWriteDetail (writer, version); writer.WriteEndElement (); } private void WriteFaultCode (XmlDictionaryWriter writer, EnvelopeVersion version, FaultCode code, bool sub) { if (version == EnvelopeVersion.Soap11) { writer.WriteStartElement ("", "faultcode", String.Empty); if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace))) writer.WriteXmlnsAttribute ("a", code.Namespace); writer.WriteQualifiedName (code.Name, code.Namespace); writer.WriteEndElement (); } else { // Soap12 writer.WriteStartElement (sub ? "Subcode" : "Code", version.Namespace); writer.WriteStartElement ("Value", version.Namespace); if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace))) writer.WriteXmlnsAttribute ("a", code.Namespace); writer.WriteQualifiedName (code.Name, code.Namespace); writer.WriteEndElement (); if (code.SubCode != null) WriteFaultCode (writer, version, code.SubCode, true); writer.WriteEndElement (); } } private void WriteReason (XmlDictionaryWriter writer, EnvelopeVersion version) { if (version == EnvelopeVersion.Soap11) { foreach (FaultReasonText t in Reason.Translations) { writer.WriteStartElement ("", "faultstring", String.Empty); if (t.XmlLang != null) writer.WriteAttributeString ("xml", "lang", null, t.XmlLang); writer.WriteString (t.Text); writer.WriteEndElement (); } } else { // Soap12 writer.WriteStartElement ("Reason", version.Namespace); foreach (FaultReasonText t in Reason.Translations) { writer.WriteStartElement ("Text", version.Namespace); if (t.XmlLang != null) writer.WriteAttributeString ("xml", "lang", null, t.XmlLang); writer.WriteString (t.Text); writer.WriteEndElement (); } writer.WriteEndElement (); } } public void WriteTo (XmlWriter writer, EnvelopeVersion version) { WriteTo (XmlDictionaryWriter.CreateDictionaryWriter ( writer), version); } protected virtual XmlDictionaryReader OnGetReaderAtDetailContents () { if (!HasDetail) throw new InvalidOperationException ("There is no fault detail to read"); MemoryStream ms = new MemoryStream (); using (XmlDictionaryWriter dw = XmlDictionaryWriter.CreateDictionaryWriter ( XmlWriter.Create (ms))) { OnWriteDetailContents (dw); } ms.Seek (0, SeekOrigin.Begin); return XmlDictionaryReader.CreateDictionaryReader ( XmlReader.Create (ms)); } protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version) { OnWriteStartDetail (writer, version); OnWriteDetailContents (writer); writer.WriteEndElement (); } protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version) { if (version == EnvelopeVersion.Soap11) writer.WriteStartElement ("detail", String.Empty); else // Soap12 writer.WriteStartElement ("Detail", version.Namespace); } protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer); } }