5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2005-2009 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections.Generic;
31 using System.Runtime.Serialization;
34 namespace System.ServiceModel.Channels
36 public abstract class MessageFault
40 public static MessageFault CreateFault (Message message, int maxBufferSize)
43 if (message.Version.Envelope == EnvelopeVersion.Soap11)
44 return CreateFault11 (message, maxBufferSize);
45 else // common to None and SOAP12
46 return CreateFault12 (message, maxBufferSize);
47 } catch (XmlException ex) {
48 throw new CommunicationException ("Received an invalid SOAP Fault message", ex);
50 throw new InvalidOperationException ("The input message is not a SOAP envelope.");
53 static MessageFault CreateFault11 (Message message, int maxBufferSize)
56 FaultReason fr = null;
57 object details = null;
58 XmlDictionaryReader r = message.GetReaderAtBodyContents ();
59 r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
62 while (r.NodeType != XmlNodeType.EndElement) {
63 switch (r.LocalName) {
65 fc = ReadFaultCode11 (r);
68 fr = new FaultReason (r.ReadElementContentAsString());
71 return new XmlReaderDetailMessageFault (message, r, fc, fr, null, null);
74 throw new NotImplementedException ();
81 throw new XmlException ("Reason is missing in the Fault message");
84 return CreateFault (fc, fr);
85 return CreateFault (fc, fr, details);
88 static MessageFault CreateFault12 (Message message, int maxBufferSize)
91 FaultReason fr = null;
93 XmlDictionaryReader r = message.GetReaderAtBodyContents ();
94 r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
96 for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) {
97 if (r.NamespaceURI != message.Version.Envelope.Namespace) {
101 switch (r.LocalName) {
103 fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace);
106 fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace);
109 node = r.ReadElementContentAsString ();
112 r.Skip (); // no corresponding member to store.
115 if (!r.IsEmptyElement)
116 return new XmlReaderDetailMessageFault (message, r, fc, fr, null, node);
120 throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
125 throw new XmlException ("Reason is missing in the Fault message");
129 return CreateFault (fc, fr, null, null, null, node);
132 static FaultCode ReadFaultCode11 (XmlDictionaryReader r)
134 FaultCode subcode = null;
135 XmlQualifiedName value = XmlQualifiedName.Empty;
137 if (r.IsEmptyElement)
138 throw new ArgumentException ("Fault Code is mandatory in SOAP fault message.");
140 r.ReadStartElement ("faultcode");
142 while (r.NodeType != XmlNodeType.EndElement) {
143 if (r.NodeType == XmlNodeType.Element)
144 subcode = ReadFaultCode11 (r);
146 value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver);
151 return new FaultCode (value.Name, value.Namespace, subcode);
154 static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns)
156 FaultCode subcode = null;
157 XmlQualifiedName value = XmlQualifiedName.Empty;
159 if (r.IsEmptyElement)
160 throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code.");
162 r.ReadStartElement (); // could be either Code or SubCode
164 while (r.NodeType != XmlNodeType.EndElement) {
165 switch (r.LocalName) {
167 subcode = ReadFaultCode12 (r, ns);
170 value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns);
173 throw new ArgumentException (String.Format ("Unexpected Fault Code subelement: '{0}'", r.LocalName));
179 return new FaultCode (value.Name, value.Namespace, subcode);
182 static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns)
184 List<FaultReasonText> l = new List<FaultReasonText> ();
185 if (r.IsEmptyElement)
186 throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text.");
188 r.ReadStartElement ("Reason", ns);
189 for (r.MoveToContent ();
190 r.NodeType != XmlNodeType.EndElement;
191 r.MoveToContent ()) {
192 string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace");
194 throw new XmlException ("xml:lang is mandatory on fault reason Text");
195 l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang));
199 return new FaultReason (l);
202 public static MessageFault CreateFault (FaultCode code,
205 return CreateFault (code, new FaultReason (reason));
208 public static MessageFault CreateFault (FaultCode code,
211 return new SimpleMessageFault (code, reason,
212 false, null, null, null, null);
215 public static MessageFault CreateFault (FaultCode code,
216 FaultReason reason, object detail)
218 return new SimpleMessageFault (code, reason,
219 true, detail, new DataContractSerializer (detail.GetType ()), null, null);
222 public static MessageFault CreateFault (FaultCode code,
223 FaultReason reason, object detail,
224 XmlObjectSerializer formatter)
226 return new SimpleMessageFault (code, reason, true,
227 detail, formatter, String.Empty, String.Empty);
230 public static MessageFault CreateFault (FaultCode code,
231 FaultReason reason, object detail,
232 XmlObjectSerializer formatter, string actor)
234 return new SimpleMessageFault (code, reason,
235 true, detail, formatter, actor, String.Empty);
238 public static MessageFault CreateFault (FaultCode code,
239 FaultReason reason, object detail,
240 XmlObjectSerializer formatter, string actor, string node)
242 return new SimpleMessageFault (code, reason,
243 true, detail, formatter, actor, node);
246 // pretty simple implementation class
247 internal abstract class BaseMessageFault : MessageFault
253 protected BaseMessageFault (FaultCode code, FaultReason reason, string actor, string node)
256 this.reason = reason;
261 public override string Actor {
262 get { return actor; }
265 public override FaultCode Code {
269 public override string Node {
273 public override FaultReason Reason {
274 get { return reason; }
278 internal class SimpleMessageFault : BaseMessageFault
282 XmlObjectSerializer formatter;
284 public SimpleMessageFault (FaultCode code,
285 FaultReason reason, bool has_detail,
286 object detail, XmlObjectSerializer formatter,
287 string actor, string node)
288 : this (code, reason, detail, formatter, actor, node)
290 this.has_detail = has_detail;
293 public SimpleMessageFault (FaultCode code,
295 object detail, XmlObjectSerializer formatter,
296 string actor, string node)
297 : base (code, reason, actor, node)
300 throw new ArgumentNullException ("code");
302 throw new ArgumentNullException ("reason");
304 this.detail = detail;
305 this.formatter = formatter;
308 public override bool HasDetail {
309 // it is not simply "detail != null" since
310 // null detail could become <ms:anyType xsi:nil="true" />
311 get { return has_detail; }
314 protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
316 if (formatter == null && detail != null)
317 formatter = new DataContractSerializer (detail.GetType ());
318 if (formatter != null)
319 formatter.WriteObject (writer, detail);
321 throw new InvalidOperationException ("There is no fault detail to write");
324 public object Detail {
325 get { return detail; }
329 class XmlReaderDetailMessageFault : BaseMessageFault
331 XmlDictionaryReader reader;
334 public XmlReaderDetailMessageFault (Message message, XmlDictionaryReader reader, FaultCode code, FaultReason reason, string actor, string node)
335 : base (code, reason, actor, node)
337 this.reader = reader;
343 throw new InvalidOperationException ("The fault detail content is already consumed");
345 reader.ReadStartElement (); // consume the wrapper
346 reader.MoveToContent ();
349 public override bool HasDetail {
353 protected override XmlDictionaryReader OnGetReaderAtDetailContents ()
359 protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
362 throw new InvalidOperationException ("There is no fault detail to write");
364 while (reader.NodeType != XmlNodeType.EndElement)
365 writer.WriteNode (reader, false);
371 protected MessageFault ()
375 [MonoTODO ("is this true?")]
376 public virtual string Actor {
377 get { return String.Empty; }
380 public abstract FaultCode Code { get; }
382 public abstract bool HasDetail { get; }
384 [MonoTODO ("is this true?")]
385 public virtual string Node {
386 get { return String.Empty; }
389 public abstract FaultReason Reason { get; }
391 public T GetDetail<T> ()
393 return GetDetail<T> (new DataContractSerializer (typeof (T)));
396 public T GetDetail<T> (XmlObjectSerializer formatter)
399 throw new InvalidOperationException ("This message does not have details.");
401 return (T) formatter.ReadObject (GetReaderAtDetailContents ());
404 public XmlDictionaryReader GetReaderAtDetailContents ()
406 return OnGetReaderAtDetailContents ();
409 public void WriteTo (XmlDictionaryWriter writer,
410 EnvelopeVersion version)
412 writer.WriteStartElement ("Fault", version.Namespace);
413 WriteFaultCode (writer, version, Code, false);
414 WriteReason (writer, version);
416 OnWriteDetail (writer, version);
417 writer.WriteEndElement ();
420 private void WriteFaultCode (XmlDictionaryWriter writer,
421 EnvelopeVersion version, FaultCode code, bool sub)
423 if (version == EnvelopeVersion.Soap11) {
424 writer.WriteStartElement ("", "faultcode", String.Empty);
425 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
426 writer.WriteXmlnsAttribute ("a", code.Namespace);
427 writer.WriteQualifiedName (code.Name, code.Namespace);
428 writer.WriteEndElement ();
430 writer.WriteStartElement (sub ? "Subcode" : "Code", version.Namespace);
431 writer.WriteStartElement ("Value", version.Namespace);
432 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
433 writer.WriteXmlnsAttribute ("a", code.Namespace);
434 writer.WriteQualifiedName (code.Name, code.Namespace);
435 writer.WriteEndElement ();
436 if (code.SubCode != null)
437 WriteFaultCode (writer, version, code.SubCode, true);
438 writer.WriteEndElement ();
442 private void WriteReason (XmlDictionaryWriter writer,
443 EnvelopeVersion version)
445 if (version == EnvelopeVersion.Soap11) {
446 foreach (FaultReasonText t in Reason.Translations) {
447 writer.WriteStartElement ("", "faultstring", String.Empty);
448 if (t.XmlLang != null)
449 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
450 writer.WriteString (t.Text);
451 writer.WriteEndElement ();
454 writer.WriteStartElement ("Reason", version.Namespace);
455 foreach (FaultReasonText t in Reason.Translations) {
456 writer.WriteStartElement ("Text", version.Namespace);
457 if (t.XmlLang != null)
458 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
459 writer.WriteString (t.Text);
460 writer.WriteEndElement ();
462 writer.WriteEndElement ();
466 public void WriteTo (XmlWriter writer, EnvelopeVersion version)
468 WriteTo (XmlDictionaryWriter.CreateDictionaryWriter (
472 protected virtual XmlDictionaryReader OnGetReaderAtDetailContents ()
475 throw new InvalidOperationException ("There is no fault detail to read");
476 MemoryStream ms = new MemoryStream ();
477 using (XmlDictionaryWriter dw =
478 XmlDictionaryWriter.CreateDictionaryWriter (
479 XmlWriter.Create (ms))) {
480 OnWriteDetailContents (dw);
482 ms.Seek (0, SeekOrigin.Begin);
483 return XmlDictionaryReader.CreateDictionaryReader (
484 XmlReader.Create (ms));
487 protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
489 OnWriteStartDetail (writer, version);
490 OnWriteDetailContents (writer);
491 writer.WriteEndElement ();
494 protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
496 if (version == EnvelopeVersion.Soap11)
497 writer.WriteStartElement ("detail", String.Empty);
499 writer.WriteStartElement ("Detail", version.Namespace);
502 protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer);