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);
52 static MessageFault CreateFault11 (Message message, int maxBufferSize)
55 FaultReason fr = null;
56 object details = null;
57 XmlDictionaryReader r = message.GetReaderAtBodyContents ();
58 r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
61 while (r.NodeType != XmlNodeType.EndElement) {
62 switch (r.LocalName) {
64 fc = ReadFaultCode11 (r);
67 fr = new FaultReason (r.ReadElementContentAsString());
70 return new XmlReaderDetailMessageFault (message, r, fc, fr, null, null);
73 throw new NotImplementedException ();
80 throw new XmlException ("Reason is missing in the Fault message");
83 return CreateFault (fc, fr);
84 return CreateFault (fc, fr, details);
87 static MessageFault CreateFault12 (Message message, int maxBufferSize)
90 FaultReason fr = null;
92 XmlDictionaryReader r = message.GetReaderAtBodyContents ();
93 r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
95 for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) {
96 if (r.NamespaceURI != message.Version.Envelope.Namespace) {
100 switch (r.LocalName) {
102 fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace);
105 fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace);
108 node = r.ReadElementContentAsString ();
111 r.Skip (); // no corresponding member to store.
114 if (!r.IsEmptyElement)
115 return new XmlReaderDetailMessageFault (message, r, fc, fr, null, node);
119 throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
124 throw new XmlException ("Reason is missing in the Fault message");
128 return new SimpleMessageFault (fc, fr, false, null, null, null, node);
131 static FaultCode ReadFaultCode11 (XmlDictionaryReader r)
133 FaultCode subcode = null;
134 XmlQualifiedName value = XmlQualifiedName.Empty;
136 if (r.IsEmptyElement)
137 throw new ArgumentException ("Fault Code is mandatory in SOAP fault message.");
139 r.ReadStartElement ("faultcode");
141 while (r.NodeType != XmlNodeType.EndElement) {
142 if (r.NodeType == XmlNodeType.Element)
143 subcode = ReadFaultCode11 (r);
145 value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver);
150 return new FaultCode (value.Name, value.Namespace, subcode);
153 static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns)
155 FaultCode subcode = null;
156 XmlQualifiedName value = XmlQualifiedName.Empty;
158 if (r.IsEmptyElement)
159 throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code.");
161 r.ReadStartElement (); // could be either Code or SubCode
163 while (r.NodeType != XmlNodeType.EndElement) {
164 switch (r.LocalName) {
166 subcode = ReadFaultCode12 (r, ns);
169 value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns);
172 throw new ArgumentException (String.Format ("Unexpected Fault Code subelement: '{0}'", r.LocalName));
178 return new FaultCode (value.Name, value.Namespace, subcode);
181 static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns)
183 List<FaultReasonText> l = new List<FaultReasonText> ();
184 if (r.IsEmptyElement)
185 throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text.");
187 r.ReadStartElement ("Reason", ns);
188 for (r.MoveToContent ();
189 r.NodeType != XmlNodeType.EndElement;
190 r.MoveToContent ()) {
191 string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace");
193 throw new XmlException ("xml:lang is mandatory on fault reason Text");
194 l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang));
198 return new FaultReason (l);
201 public static MessageFault CreateFault (FaultCode code,
204 return CreateFault (code, new FaultReason (reason));
207 public static MessageFault CreateFault (FaultCode code,
210 return new SimpleMessageFault (code, reason,
211 false, null, null, null, null);
214 public static MessageFault CreateFault (FaultCode code,
215 FaultReason reason, object detail)
217 return new SimpleMessageFault (code, reason,
218 true, detail, new DataContractSerializer (detail.GetType ()), null, null);
221 public static MessageFault CreateFault (FaultCode code,
222 FaultReason reason, object detail,
223 XmlObjectSerializer formatter)
225 return new SimpleMessageFault (code, reason, true,
226 detail, formatter, String.Empty, String.Empty);
229 public static MessageFault CreateFault (FaultCode code,
230 FaultReason reason, object detail,
231 XmlObjectSerializer formatter, string actor)
233 return new SimpleMessageFault (code, reason,
234 true, detail, formatter, actor, String.Empty);
237 public static MessageFault CreateFault (FaultCode code,
238 FaultReason reason, object detail,
239 XmlObjectSerializer formatter, string actor, string node)
241 return new SimpleMessageFault (code, reason,
242 true, detail, formatter, actor, node);
245 // pretty simple implementation class
246 internal abstract class BaseMessageFault : MessageFault
252 protected BaseMessageFault (FaultCode code, FaultReason reason, string actor, string node)
255 this.reason = reason;
260 public override string Actor {
261 get { return actor; }
264 public override FaultCode Code {
268 public override string Node {
272 public override FaultReason Reason {
273 get { return reason; }
277 internal class SimpleMessageFault : BaseMessageFault
281 XmlObjectSerializer formatter;
283 public SimpleMessageFault (FaultCode code,
284 FaultReason reason, bool has_detail,
285 object detail, XmlObjectSerializer formatter,
286 string actor, string node)
287 : this (code, reason, detail, formatter, actor, node)
289 this.has_detail = has_detail;
292 public SimpleMessageFault (FaultCode code,
294 object detail, XmlObjectSerializer formatter,
295 string actor, string node)
296 : base (code, reason, actor, node)
299 throw new ArgumentNullException ("code");
301 throw new ArgumentNullException ("reason");
303 this.detail = detail;
304 this.formatter = formatter;
307 public override bool HasDetail {
308 // it is not simply "detail != null" since
309 // null detail could become <ms:anyType xsi:nil="true" />
310 get { return has_detail; }
313 protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
315 if (formatter == null && detail != null)
316 formatter = new DataContractSerializer (detail.GetType ());
317 if (formatter != null)
318 formatter.WriteObject (writer, detail);
320 throw new InvalidOperationException ("There is no fault detail to write");
323 public object Detail {
324 get { return detail; }
328 class XmlReaderDetailMessageFault : BaseMessageFault
330 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;
338 if (reader.IsEmptyElement)
340 reader.MoveToContent ();
341 reader.ReadStartElement (); // consume the wrapper
342 reader.MoveToContent ();
343 has_detail = reader.NodeType != XmlNodeType.EndElement;
349 throw new InvalidOperationException ("The fault detail content is already consumed");
353 public override bool HasDetail {
354 get { return has_detail; }
357 protected override XmlDictionaryReader OnGetReaderAtDetailContents ()
363 protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
366 throw new InvalidOperationException ("There is no fault detail to write");
368 while (reader.NodeType != XmlNodeType.EndElement)
369 writer.WriteNode (reader, false);
375 protected MessageFault ()
379 [MonoTODO ("is this true?")]
380 public virtual string Actor {
381 get { return String.Empty; }
384 public abstract FaultCode Code { get; }
386 public abstract bool HasDetail { get; }
388 [MonoTODO ("is this true?")]
389 public virtual string Node {
390 get { return String.Empty; }
393 public abstract FaultReason Reason { get; }
395 public T GetDetail<T> ()
397 return GetDetail<T> (new DataContractSerializer (typeof (T)));
400 public T GetDetail<T> (XmlObjectSerializer formatter)
403 throw new InvalidOperationException ("This message does not have details.");
405 return (T) formatter.ReadObject (GetReaderAtDetailContents ());
408 public XmlDictionaryReader GetReaderAtDetailContents ()
410 return OnGetReaderAtDetailContents ();
413 public void WriteTo (XmlDictionaryWriter writer,
414 EnvelopeVersion version)
416 writer.WriteStartElement ("Fault", version.Namespace);
417 WriteFaultCode (writer, version, Code, false);
418 WriteReason (writer, version);
420 OnWriteDetail (writer, version);
421 writer.WriteEndElement ();
424 private void WriteFaultCode (XmlDictionaryWriter writer,
425 EnvelopeVersion version, FaultCode code, bool sub)
427 if (version == EnvelopeVersion.Soap11) {
428 writer.WriteStartElement ("", "faultcode", String.Empty);
429 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
430 writer.WriteXmlnsAttribute ("a", code.Namespace);
431 writer.WriteQualifiedName (code.Name, code.Namespace);
432 writer.WriteEndElement ();
434 writer.WriteStartElement (sub ? "Subcode" : "Code", version.Namespace);
435 writer.WriteStartElement ("Value", version.Namespace);
436 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
437 writer.WriteXmlnsAttribute ("a", code.Namespace);
438 writer.WriteQualifiedName (code.Name, code.Namespace);
439 writer.WriteEndElement ();
440 if (code.SubCode != null)
441 WriteFaultCode (writer, version, code.SubCode, true);
442 writer.WriteEndElement ();
446 private void WriteReason (XmlDictionaryWriter writer,
447 EnvelopeVersion version)
449 if (version == EnvelopeVersion.Soap11) {
450 foreach (FaultReasonText t in Reason.Translations) {
451 writer.WriteStartElement ("", "faultstring", String.Empty);
452 if (t.XmlLang != null)
453 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
454 writer.WriteString (t.Text);
455 writer.WriteEndElement ();
458 writer.WriteStartElement ("Reason", version.Namespace);
459 foreach (FaultReasonText t in Reason.Translations) {
460 writer.WriteStartElement ("Text", version.Namespace);
461 if (t.XmlLang != null)
462 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
463 writer.WriteString (t.Text);
464 writer.WriteEndElement ();
466 writer.WriteEndElement ();
470 public void WriteTo (XmlWriter writer, EnvelopeVersion version)
472 WriteTo (XmlDictionaryWriter.CreateDictionaryWriter (
476 protected virtual XmlDictionaryReader OnGetReaderAtDetailContents ()
479 throw new InvalidOperationException ("There is no fault detail to read");
480 MemoryStream ms = new MemoryStream ();
481 using (XmlDictionaryWriter dw =
482 XmlDictionaryWriter.CreateDictionaryWriter (
483 XmlWriter.Create (ms))) {
484 OnWriteDetailContents (dw);
486 ms.Seek (0, SeekOrigin.Begin);
487 return XmlDictionaryReader.CreateDictionaryReader (
488 XmlReader.Create (ms));
491 protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
493 OnWriteStartDetail (writer, version);
494 OnWriteDetailContents (writer);
495 writer.WriteEndElement ();
498 protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
500 if (version == EnvelopeVersion.Soap11)
501 writer.WriteStartElement ("detail", String.Empty);
503 writer.WriteStartElement ("Detail", version.Namespace);
506 protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer);