1 // created on 07/04/2003 at 17:56
\r
3 // System.Runtime.Serialization.Formatters.Soap.SoapWriter
\r
6 // Jean-Marc Andre (jean-marc.andre@polymtl.ca)
\r
10 // Permission is hereby granted, free of charge, to any person obtaining
\r
11 // a copy of this software and associated documentation files (the
\r
12 // "Software"), to deal in the Software without restriction, including
\r
13 // without limitation the rights to use, copy, modify, merge, publish,
\r
14 // distribute, sublicense, and/or sell copies of the Software, and to
\r
15 // permit persons to whom the Software is furnished to do so, subject to
\r
16 // the following conditions:
\r
18 // The above copyright notice and this permission notice shall be
\r
19 // included in all copies or substantial portions of the Software.
\r
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\r
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\r
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\r
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
32 using System.Threading;
\r
33 using System.Reflection;
\r
34 using System.Collections;
\r
35 using System.Runtime.Remoting;
\r
36 using System.Runtime.Remoting.Metadata;
\r
37 using System.Runtime.Remoting.Messaging;
\r
38 using System.Runtime.Serialization;
\r
39 using System.Runtime.Serialization.Formatters;
\r
41 using System.Xml.Schema;
\r
42 using System.Xml.Serialization;
\r
43 using System.Globalization;
\r
46 namespace System.Runtime.Serialization.Formatters.Soap {
\r
48 internal class SoapWriter: IComparer {
\r
49 private struct EnqueuedObject {
\r
51 public object _object;
\r
53 public EnqueuedObject(object currentObject, long id) {
\r
55 _object = currentObject;
\r
66 public object Object
\r
77 private XmlTextWriter _xmlWriter;
\r
78 private Queue _objectQueue = new Queue();
\r
79 private Hashtable _objectToIdTable = new Hashtable();
\r
80 private ISurrogateSelector _surrogateSelector;
\r
81 private SoapTypeMapper _mapper;
\r
82 private StreamingContext _context;
\r
83 private ObjectIDGenerator idGen = new ObjectIDGenerator();
\r
84 private FormatterAssemblyStyle _assemblyFormat = FormatterAssemblyStyle.Full;
\r
85 private FormatterTypeStyle _typeFormat = FormatterTypeStyle.TypesWhenNeeded;
\r
86 private static string defaultMessageNamespace;
\r
88 SerializationObjectManager _manager;
\r
97 #region Constructors
\r
99 internal SoapWriter(
\r
101 ISurrogateSelector selector,
\r
102 StreamingContext context,
\r
103 ISoapMessage soapMessage)
\r
105 _xmlWriter = new XmlTextWriter(outStream, null);
\r
106 _xmlWriter.Formatting = Formatting.Indented;
\r
107 _surrogateSelector = selector;
\r
108 _context = context;
\r
110 _manager = new SerializationObjectManager (_context);
\r
114 static SoapWriter()
\r
116 defaultMessageNamespace = typeof(SoapWriter).Assembly.GetName().FullName;
\r
121 public SoapTypeMapper Mapper {
\r
122 get { return _mapper; }
\r
125 public XmlTextWriter XmlWriter {
\r
126 get { return _xmlWriter; }
\r
129 #region Internal Properties
\r
131 internal FormatterAssemblyStyle AssemblyFormat
\r
135 return _assemblyFormat;
\r
139 _assemblyFormat = value;
\r
143 internal FormatterTypeStyle TypeFormat
\r
147 return _typeFormat;
\r
151 _typeFormat = value;
\r
157 private void Id(long id)
\r
159 _xmlWriter.WriteAttributeString(null, "id", null, "ref-" + id.ToString());
\r
162 private void Href(long href)
\r
164 _xmlWriter.WriteAttributeString(null, "href", null, "#ref-" + href.ToString());
\r
168 private void Null()
\r
170 _xmlWriter.WriteAttributeString("xsi", "null", XmlSchema.InstanceNamespace, "1");
\r
173 private bool IsEncodingNeeded(
\r
174 object componentObject,
\r
175 Type componentType)
\r
177 if(componentObject == null)
\r
179 if(_typeFormat == FormatterTypeStyle.TypesAlways)
\r
181 if(componentType == null)
\r
183 componentType = componentObject.GetType();
\r
184 if(componentType.IsPrimitive || componentType == typeof(string))
\r
192 if(componentType == typeof(object) || componentType != componentObject.GetType())
\r
200 internal void Serialize (object objGraph, Header[] headers, FormatterTypeStyle typeFormat, FormatterAssemblyStyle assemblyFormat)
\r
202 CultureInfo savedCi = CultureInfo.CurrentCulture;
\r
204 Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
\r
205 Serialize_inner (objGraph, headers, typeFormat, assemblyFormat);
\r
207 Thread.CurrentThread.CurrentCulture = savedCi;
\r
211 _manager.RaiseOnSerializedEvent ();
\r
215 void Serialize_inner (object objGraph, Header[] headers, FormatterTypeStyle typeFormat, FormatterAssemblyStyle assemblyFormat)
\r
217 _typeFormat = typeFormat;
\r
218 _assemblyFormat = assemblyFormat;
\r
219 // Create the XmlDocument with the
\r
220 // Envelope and Body elements
\r
221 _mapper = new SoapTypeMapper(_xmlWriter, assemblyFormat, typeFormat);
\r
223 // The root element
\r
224 _xmlWriter.WriteStartElement(
\r
225 SoapTypeMapper.SoapEnvelopePrefix,
\r
227 SoapTypeMapper.SoapEnvelopeNamespace);
\r
229 // adding namespaces
\r
230 _xmlWriter.WriteAttributeString(
\r
233 "http://www.w3.org/2000/xmlns/",
\r
234 "http://www.w3.org/2001/XMLSchema-instance");
\r
236 _xmlWriter.WriteAttributeString(
\r
239 "http://www.w3.org/2000/xmlns/",
\r
240 XmlSchema.Namespace);
\r
242 _xmlWriter.WriteAttributeString(
\r
244 SoapTypeMapper.SoapEncodingPrefix,
\r
245 "http://www.w3.org/2000/xmlns/",
\r
246 SoapTypeMapper.SoapEncodingNamespace);
\r
248 _xmlWriter.WriteAttributeString(
\r
250 SoapTypeMapper.SoapEnvelopePrefix,
\r
251 "http://www.w3.org/2000/xmlns/",
\r
252 SoapTypeMapper.SoapEnvelopeNamespace);
\r
254 _xmlWriter.WriteAttributeString(
\r
257 "http://www.w3.org/2000/xmlns/",
\r
258 SoapServices.XmlNsForClrType);
\r
260 _xmlWriter.WriteAttributeString(
\r
261 SoapTypeMapper.SoapEnvelopePrefix,
\r
263 SoapTypeMapper.SoapEnvelopeNamespace,
\r
264 "http://schemas.xmlsoap.org/soap/encoding/");
\r
266 ISoapMessage msg = objGraph as ISoapMessage;
\r
268 headers = msg.Headers;
\r
270 if (headers != null && headers.Length > 0)
\r
272 _xmlWriter.WriteStartElement (SoapTypeMapper.SoapEnvelopePrefix, "Header", SoapTypeMapper.SoapEnvelopeNamespace);
\r
273 foreach (Header h in headers)
\r
274 SerializeHeader (h);
\r
276 WriteObjectQueue ();
\r
277 _xmlWriter.WriteEndElement ();
\r
280 // The body element
\r
281 _xmlWriter.WriteStartElement(
\r
282 SoapTypeMapper.SoapEnvelopePrefix,
\r
284 SoapTypeMapper.SoapEnvelopeNamespace);
\r
287 bool firstTime = false;
\r
290 SerializeMessage(msg);
\r
292 _objectQueue.Enqueue(new EnqueuedObject( objGraph, idGen.GetId(objGraph, out firstTime)));
\r
294 WriteObjectQueue ();
\r
296 _xmlWriter.WriteFullEndElement(); // the body element
\r
297 _xmlWriter.WriteFullEndElement(); // the envelope element
\r
298 _xmlWriter.Flush();
\r
301 private void WriteObjectQueue ()
\r
303 while(_objectQueue.Count > 0)
\r
305 EnqueuedObject currentEnqueuedObject;
\r
306 currentEnqueuedObject = (EnqueuedObject) _objectQueue.Dequeue();
\r
307 object currentObject = currentEnqueuedObject.Object;
\r
308 Type currentType = currentObject.GetType();
\r
310 if(!currentType.IsValueType) _objectToIdTable[currentObject] = currentEnqueuedObject.Id;
\r
312 if(currentType.IsArray)
\r
313 SerializeArray((Array) currentObject, currentEnqueuedObject.Id);
\r
315 SerializeObject(currentObject, currentEnqueuedObject.Id);
\r
319 private void SerializeMessage(ISoapMessage message)
\r
322 string ns = message.XmlNameSpace != null ? message.XmlNameSpace : defaultMessageNamespace;
\r
324 _xmlWriter.WriteStartElement("i2", message.MethodName, ns);
\r
325 Id(idGen.GetId(message, out firstTime));
\r
327 string[] paramNames = message.ParamNames;
\r
328 object[] paramValues = message.ParamValues;
\r
329 int length = (paramNames != null)?paramNames.Length:0;
\r
330 for(int i = 0; i < length; i++)
\r
332 _xmlWriter.WriteStartElement(paramNames[i]);
\r
333 SerializeComponent(paramValues[i], true);
\r
334 _xmlWriter.WriteEndElement();
\r
338 _xmlWriter.WriteFullEndElement();
\r
341 private void SerializeHeader (Header header)
\r
343 string ns = header.HeaderNamespace != null ? header.HeaderNamespace : "http://schemas.microsoft.com/clr/soap";
\r
344 _xmlWriter.WriteStartElement ("h4", header.Name, ns);
\r
345 if (header.MustUnderstand)
\r
346 _xmlWriter.WriteAttributeString ("mustUnderstand", SoapTypeMapper.SoapEnvelopeNamespace, "1");
\r
347 _xmlWriter.WriteAttributeString ("root", SoapTypeMapper.SoapEncodingNamespace, "1");
\r
349 if (header.Name == "__MethodSignature") {
\r
350 // This is a lame way of identifying the signature header, but looks like it is
\r
351 // what MS.NET does.
\r
352 Type[] val = header.Value as Type[];
\r
354 throw new SerializationException ("Invalid method signature.");
\r
355 SerializeComponent (new MethodSignature (val), true);
\r
357 SerializeComponent (header.Value, true);
\r
359 _xmlWriter.WriteEndElement();
\r
362 private void SerializeObject(object currentObject, long currentObjectId)
\r
364 bool needsSerializationInfo = false;
\r
365 ISurrogateSelector selector;
\r
366 ISerializationSurrogate surrogate = null;
\r
367 if(_surrogateSelector != null)
\r
369 surrogate = _surrogateSelector.GetSurrogate(
\r
370 currentObject.GetType(),
\r
374 if(currentObject is ISerializable || surrogate != null) needsSerializationInfo = true;
\r
377 _manager.RegisterObject (currentObject);
\r
380 if(needsSerializationInfo)
\r
382 SerializeISerializableObject(currentObject, currentObjectId, surrogate);
\r
386 if(!currentObject.GetType().IsSerializable)
\r
387 throw new SerializationException(String.Format("Type {0} in assembly {1} is not marked as serializable.", currentObject.GetType(), currentObject.GetType().Assembly.FullName));
\r
388 SerializeSimpleObject(currentObject, currentObjectId);
\r
392 // implement IComparer
\r
393 public int Compare(object x, object y)
\r
395 MemberInfo a = x as MemberInfo;
\r
396 MemberInfo b = y as MemberInfo;
\r
398 return String.Compare(a.Name, b.Name);
\r
401 private void SerializeSimpleObject(
\r
402 object currentObject,
\r
403 long currentObjectId)
\r
405 Type currentType = currentObject.GetType();
\r
407 // Value type have to be serialized "on the fly" so
\r
408 // SerializeComponent calls SerializeObject when
\r
409 // a field of another object is a struct. A node with the field
\r
410 // name has already be written so WriteStartElement must not be called
\r
411 // again. Fields that are structs are passed to SerializeObject
\r
413 if(currentObjectId > 0)
\r
415 Element element = _mapper.GetXmlElement (currentType);
\r
416 _xmlWriter.WriteStartElement(element.Prefix, element.LocalName, element.NamespaceURI);
\r
417 Id(currentObjectId);
\r
420 if (currentType == typeof(TimeSpan))
\r
422 _xmlWriter.WriteString(SoapTypeMapper.GetXsdValue(currentObject));
\r
424 else if(currentType == typeof(string))
\r
426 _xmlWriter.WriteString(currentObject.ToString());
\r
430 MemberInfo[] memberInfos = FormatterServices.GetSerializableMembers(currentType, _context);
\r
431 object[] objectData = FormatterServices.GetObjectData(currentObject, memberInfos);
\r
433 for(int i = 0; i < memberInfos.Length; i++)
\r
435 FieldInfo fieldInfo = (FieldInfo) memberInfos[i];
\r
436 SoapFieldAttribute at = (SoapFieldAttribute) InternalRemotingServices.GetCachedSoapAttribute (fieldInfo);
\r
437 _xmlWriter.WriteStartElement (XmlConvert.EncodeLocalName (at.XmlElementName));
\r
438 SerializeComponent(
\r
440 IsEncodingNeeded(objectData[i], fieldInfo.FieldType));
\r
441 _xmlWriter.WriteEndElement();
\r
444 if(currentObjectId > 0)
\r
445 _xmlWriter.WriteFullEndElement();
\r
450 private void SerializeISerializableObject(
\r
451 object currentObject,
\r
452 long currentObjectId,
\r
453 ISerializationSurrogate surrogate)
\r
455 Type currentType = currentObject.GetType();
\r
456 SerializationInfo info = new SerializationInfo(currentType, new FormatterConverter());
\r
459 ISerializable objISerializable = currentObject as ISerializable;
\r
460 if(surrogate != null) surrogate.GetObjectData(currentObject, info, _context);
\r
463 objISerializable.GetObjectData(info, _context);
\r
467 if(currentObjectId > 0L)
\r
469 Element element = _mapper. GetXmlElement (info.FullTypeName, info.AssemblyName);
\r
470 _xmlWriter.WriteStartElement(element.Prefix, element.LocalName, element.NamespaceURI);
\r
471 Id(currentObjectId);
\r
474 foreach(SerializationEntry entry in info)
\r
476 _xmlWriter.WriteStartElement(XmlConvert.EncodeLocalName (entry.Name));
\r
477 SerializeComponent(entry.Value, IsEncodingNeeded(entry.Value, null));
\r
478 _xmlWriter.WriteEndElement();
\r
480 if(currentObjectId > 0)
\r
481 _xmlWriter.WriteFullEndElement();
\r
485 private void SerializeArray(Array currentArray, long currentArrayId)
\r
487 Element element = _mapper.GetXmlElement (typeof(System.Array));
\r
490 // Set the arrayType attribute
\r
491 Type arrayType = currentArray.GetType().GetElementType();
\r
492 Element xmlArrayType = _mapper.GetXmlElement (arrayType);
\r
493 _xmlWriter.WriteStartElement(element.Prefix, element.LocalName, element.NamespaceURI);
\r
494 if(currentArrayId > 0) Id(currentArrayId);
\r
496 if (arrayType == typeof(byte)) {
\r
497 EncodeType (currentArray.GetType());
\r
498 _xmlWriter.WriteString (Convert.ToBase64String ((byte[])currentArray));
\r
499 _xmlWriter.WriteFullEndElement();
\r
503 string prefix = GetNamespacePrefix (xmlArrayType);
\r
505 StringBuilder str = new StringBuilder();
\r
506 str.AppendFormat("{0}:{1}[", prefix, xmlArrayType.LocalName);
\r
507 for(int i = 0; i < currentArray.Rank; i++)
\r
509 str.AppendFormat("{0},", currentArray.GetUpperBound(i) + 1);
\r
511 str.Replace(',', ']', str.Length - 1, 1);
\r
512 _xmlWriter.WriteAttributeString(
\r
513 SoapTypeMapper.SoapEncodingPrefix,
\r
515 SoapTypeMapper.SoapEncodingNamespace,
\r
519 // Get the array items
\r
520 int lastNonNullItem = 0;
\r
521 int currentIndex = 0;
\r
522 // bool specifyEncoding = false;
\r
523 foreach(object item in currentArray)
\r
527 for(int j = lastNonNullItem; j < currentIndex; j++)
\r
529 _xmlWriter.WriteStartElement("item");
\r
531 _xmlWriter.WriteEndElement();
\r
533 lastNonNullItem = currentIndex + 1;
\r
534 // specifyEncoding |= (arrayType != item.GetType());
\r
535 _xmlWriter.WriteStartElement("item");
\r
536 SerializeComponent(item, IsEncodingNeeded(item, arrayType));
\r
537 _xmlWriter.WriteEndElement();
\r
541 _xmlWriter.WriteFullEndElement();
\r
545 private void SerializeComponent(
\r
547 bool specifyEncoding)
\r
549 if(_typeFormat == FormatterTypeStyle.TypesAlways)
\r
550 specifyEncoding = true;
\r
552 // A null component
\r
558 Type objType = obj.GetType();
\r
559 bool canBeValue = _mapper.IsInternalSoapType (objType);
\r
563 // An object already serialized
\r
564 if((id = idGen.HasId(obj, out firstTime)) != 0L)
\r
566 Href((long)idGen.GetId(obj, out firstTime));
\r
573 if(objType == typeof(string))
\r
575 if(_typeFormat != FormatterTypeStyle.XsdString)
\r
577 id = idGen.GetId(obj, out firstTime);
\r
580 // specifyEncoding = false;
\r
583 // This component has to be
\r
584 // serialized later
\r
585 if(!canBeValue && !objType.IsValueType)
\r
587 long href = idGen.GetId(obj, out firstTime);
\r
589 _objectQueue.Enqueue(new EnqueuedObject(obj, href));
\r
593 if(specifyEncoding)
\r
595 EncodeType(objType);
\r
599 if(!canBeValue && objType.IsValueType)
\r
601 SerializeObject(obj, 0);
\r
605 _xmlWriter.WriteString (_mapper.GetInternalSoapValue (this, obj));
\r
608 private void EncodeType(Type type)
\r
611 throw new SerializationException("Oooops");
\r
613 Element xmlType = _mapper.GetXmlElement (type);
\r
615 string prefix = GetNamespacePrefix (xmlType);
\r
617 _xmlWriter.WriteAttributeString (
\r
620 "http://www.w3.org/2001/XMLSchema-instance",
\r
621 prefix + ":" + xmlType.LocalName);
\r
624 public string GetNamespacePrefix (Element xmlType)
\r
626 string prefix = _xmlWriter.LookupPrefix (xmlType.NamespaceURI);
\r
627 if(prefix == null || prefix == string.Empty)
\r
629 _xmlWriter.WriteAttributeString(
\r
632 "http://www.w3.org/2000/xmlns/",
\r
633 xmlType.NamespaceURI);
\r
634 return xmlType.Prefix;
\r