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 if (message.Version.Envelope == EnvelopeVersion.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 //BUGBUG: Handle children of type other than ExceptionDetail, in order to comply with
72 // FaultContractAttribute.
73 r.ReadStartElement ();
75 details = new DataContractSerializer (typeof (ExceptionDetail)).ReadObject (r);
79 throw new NotImplementedException ();
86 throw new XmlException ("Reason is missing in the Fault message");
89 return CreateFault (fc, fr);
90 return CreateFault (fc, fr, details);
93 static MessageFault CreateFault12 (Message message, int maxBufferSize)
96 FaultReason fr = null;
97 XmlDictionaryReader r = message.GetReaderAtBodyContents ();
98 r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
101 while (r.NodeType != XmlNodeType.EndElement) {
102 switch (r.LocalName) {
104 fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace);
107 fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace);
110 throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
116 throw new XmlException ("Reason is missing in the Fault message");
120 return CreateFault (fc, fr);
123 static FaultCode ReadFaultCode11 (XmlDictionaryReader r)
125 FaultCode subcode = null;
126 XmlQualifiedName value = XmlQualifiedName.Empty;
128 if (r.IsEmptyElement)
129 throw new ArgumentException ("Fault Code is mandatory in SOAP fault message.");
131 r.ReadStartElement ("faultcode");
133 while (r.NodeType != XmlNodeType.EndElement) {
134 if (r.NodeType == XmlNodeType.Element)
135 subcode = ReadFaultCode11 (r);
137 value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver);
142 return new FaultCode (value.Name, value.Namespace, subcode);
145 static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns)
147 FaultCode subcode = null;
148 XmlQualifiedName value = XmlQualifiedName.Empty;
150 if (r.IsEmptyElement)
151 throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code.");
153 r.ReadStartElement (); // could be either Code or SubCode
155 while (r.NodeType != XmlNodeType.EndElement) {
156 switch (r.LocalName) {
158 subcode = ReadFaultCode12 (r, ns);
161 value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns);
164 throw new ArgumentException ();
170 return new FaultCode (value.Name, value.Namespace, subcode);
173 static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns)
175 List<FaultReasonText> l = new List<FaultReasonText> ();
176 if (r.IsEmptyElement)
177 throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text.");
179 r.ReadStartElement ("Reason", ns);
180 for (r.MoveToContent ();
181 r.NodeType != XmlNodeType.EndElement;
182 r.MoveToContent ()) {
183 string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace");
185 throw new XmlException ("xml:lang is mandatory on fault reason Text");
186 l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang));
188 return new FaultReason (l);
191 public static MessageFault CreateFault (FaultCode code,
194 return CreateFault (code, new FaultReason (reason));
197 public static MessageFault CreateFault (FaultCode code,
200 return new SimpleMessageFault (code, reason,
201 false, null, null, null, null);
204 public static MessageFault CreateFault (FaultCode code,
205 FaultReason reason, object detail)
207 return new SimpleMessageFault (code, reason,
208 true, detail, new DataContractSerializer (detail.GetType ()), null, null);
211 public static MessageFault CreateFault (FaultCode code,
212 FaultReason reason, object detail,
213 XmlObjectSerializer formatter)
215 return new SimpleMessageFault (code, reason, true,
216 detail, formatter, String.Empty, String.Empty);
219 public static MessageFault CreateFault (FaultCode code,
220 FaultReason reason, object detail,
221 XmlObjectSerializer formatter, string actor)
223 return new SimpleMessageFault (code, reason,
224 true, detail, formatter, actor, String.Empty);
227 public static MessageFault CreateFault (FaultCode code,
228 FaultReason reason, object detail,
229 XmlObjectSerializer formatter, string actor, string node)
231 return new SimpleMessageFault (code, reason,
232 true, detail, formatter, actor, node);
235 // pretty simple implementation class
236 internal class SimpleMessageFault : MessageFault
243 XmlObjectSerializer formatter;
245 public SimpleMessageFault (FaultCode code,
246 FaultReason reason, bool has_detail,
247 object detail, XmlObjectSerializer formatter,
248 string actor, string node)
249 : this (code, reason, detail, formatter, actor, node)
251 this.has_detail = has_detail;
254 public SimpleMessageFault (FaultCode code,
256 object detail, XmlObjectSerializer formatter,
257 string actor, string node)
260 throw new ArgumentNullException ("code");
262 throw new ArgumentNullException ("reason");
265 this.reason = reason;
266 this.detail = detail;
267 this.formatter = formatter;
272 public override string Actor {
273 get { return actor; }
276 public override FaultCode Code {
280 public override bool HasDetail {
281 // it is not simply "detail != null" since
282 // null detail could become <ms:anyType xsi:nil="true" />
283 get { return has_detail; }
286 public override string Node {
290 public override FaultReason Reason {
291 get { return reason; }
294 protected override XmlDictionaryReader OnGetReaderAtDetailContents ()
296 // FIXME: use XmlObjectSerializer
297 return base.OnGetReaderAtDetailContents ();
300 protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
302 formatter.WriteObject (writer, detail);
305 public object Detail {
306 get { return detail; }
312 protected MessageFault ()
316 [MonoTODO ("is this true?")]
317 public virtual string Actor {
318 get { return String.Empty; }
321 public abstract FaultCode Code { get; }
323 public abstract bool HasDetail { get; }
325 [MonoTODO ("is this true?")]
326 public virtual string Node {
327 get { return String.Empty; }
330 public abstract FaultReason Reason { get; }
332 public T GetDetail<T> ()
334 return GetDetail<T> (new DataContractSerializer (typeof (T)));
337 public T GetDetail<T> (XmlObjectSerializer formatter)
340 throw new InvalidOperationException ("This message does not have details.");
342 return (T) formatter.ReadObject (GetReaderAtDetailContents ());
345 public XmlDictionaryReader GetReaderAtDetailContents ()
347 return OnGetReaderAtDetailContents ();
350 public void WriteTo (XmlDictionaryWriter writer,
351 EnvelopeVersion version)
353 writer.WriteStartElement ("Fault", version.Namespace);
354 WriteFaultCode (writer, version, Code);
355 WriteReason (writer, version);
357 OnWriteDetail (writer, version);
358 writer.WriteEndElement ();
361 private void WriteFaultCode (XmlDictionaryWriter writer,
362 EnvelopeVersion version, FaultCode code)
364 if (version == EnvelopeVersion.Soap11) {
365 writer.WriteStartElement ("", "faultcode", version.Namespace);
366 if (code.Namespace.Length > 0)
367 writer.WriteXmlnsAttribute ("a", code.Namespace);
368 writer.WriteQualifiedName (code.Name, code.Namespace);
369 writer.WriteEndElement ();
371 writer.WriteStartElement ("Code", version.Namespace);
372 writer.WriteStartElement ("Value", version.Namespace);
373 if (code.Namespace.Length > 0)
374 writer.WriteXmlnsAttribute ("a", code.Namespace);
375 writer.WriteQualifiedName (code.Name, code.Namespace);
376 if (code.SubCode != null)
377 WriteFaultCode (writer, version, code.SubCode);
378 writer.WriteEndElement ();
379 writer.WriteEndElement ();
383 private void WriteReason (XmlDictionaryWriter writer,
384 EnvelopeVersion version)
386 if (version == EnvelopeVersion.Soap11) {
387 foreach (FaultReasonText t in Reason.Translations) {
388 writer.WriteStartElement ("", "faultstring", version.Namespace);
389 if (t.XmlLang != null)
390 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
391 writer.WriteString (t.Text);
392 writer.WriteEndElement ();
395 writer.WriteStartElement ("Reason", version.Namespace);
396 foreach (FaultReasonText t in Reason.Translations) {
397 writer.WriteStartElement ("Text", version.Namespace);
398 if (t.XmlLang != null)
399 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
400 writer.WriteString (t.Text);
401 writer.WriteEndElement ();
403 writer.WriteEndElement ();
407 public void WriteTo (XmlWriter writer, EnvelopeVersion version)
409 WriteTo (XmlDictionaryWriter.CreateDictionaryWriter (
413 protected virtual XmlDictionaryReader OnGetReaderAtDetailContents ()
415 MemoryStream ms = new MemoryStream ();
416 using (XmlDictionaryWriter dw =
417 XmlDictionaryWriter.CreateDictionaryWriter (
418 XmlWriter.Create (ms))) {
419 OnWriteDetailContents (dw);
421 ms.Seek (0, SeekOrigin.Begin);
422 return XmlDictionaryReader.CreateDictionaryReader (
423 XmlReader.Create (ms));
426 protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
428 OnWriteStartDetail (writer, version);
429 OnWriteDetailContents (writer);
430 writer.WriteEndElement ();
433 protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
435 if (version == EnvelopeVersion.Soap11)
436 writer.WriteStartElement ("detail", String.Empty);
438 writer.WriteStartElement ("Detail", version.Namespace);
441 protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer);