2 // WebMessageFormatter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Atsushi Enomoto <atsushi@xamarin.com>
8 // Copyright (C) 2008,2009 Novell, Inc (http://www.novell.com)
9 // Copyright (C) 2011 Xamarin, Inc (http://xamarin.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections.Generic;
32 using System.Globalization;
34 using System.Reflection;
35 using System.Runtime.Serialization;
36 using System.Runtime.Serialization.Json;
37 using System.ServiceModel;
38 using System.ServiceModel.Channels;
39 using System.ServiceModel.Description;
40 using System.ServiceModel.Web;
45 using XmlObjectSerializer = System.Object;
48 namespace System.ServiceModel.Dispatcher
50 // This set of classes is to work as message formatters for
51 // WebHttpBehavior. There are couple of aspects to differentiate
53 // - request/reply and client/server
54 // by WebMessageFormatter hierarchy
55 // - WebClientMessageFormatter - for client
56 // - RequestClientFormatter - for request
57 // - ReplyClientFormatter - for response
58 // - WebDispatchMessageFormatter - for server
59 // - RequestDispatchFormatter - for request
60 // - ReplyDispatchFormatter - for response
62 // FIXME: below items need more work
63 // - HTTP method differences
66 // - output format: Stream, JSON, XML ...
68 internal abstract class WebMessageFormatter
70 OperationDescription operation;
71 ServiceEndpoint endpoint;
72 QueryStringConverter converter;
73 WebHttpBehavior behavior;
75 WebAttributeInfo info = null;
77 public WebMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
79 this.operation = operation;
80 this.endpoint = endpoint;
81 this.converter = converter;
82 this.behavior = behavior;
85 // This is a hack for WebScriptEnablingBehavior
86 var jqc = converter as JsonQueryStringConverter;
88 BodyName = jqc.CustomWrapperName;
92 void ApplyWebAttribute ()
94 MethodInfo mi = operation.SyncMethod ?? operation.BeginMethod;
96 object [] atts = mi.GetCustomAttributes (typeof (WebGetAttribute), false);
98 info = ((WebGetAttribute) atts [0]).Info;
99 atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), false);
101 info = ((WebInvokeAttribute) atts [0]).Info;
103 info = new WebAttributeInfo ();
105 template = info.BuildUriTemplate (Operation, GetMessageDescription (MessageDirection.Input));
108 public string BodyName { get; set; }
110 public WebHttpBehavior Behavior {
111 get { return behavior; }
114 public WebAttributeInfo Info {
118 public WebMessageBodyStyle BodyStyle {
119 get { return info.IsBodyStyleSetExplicitly ? info.BodyStyle : behavior.DefaultBodyStyle; }
122 public bool IsRequestBodyWrapped {
125 case WebMessageBodyStyle.Wrapped:
126 case WebMessageBodyStyle.WrappedRequest:
129 return BodyName != null;
133 public bool IsResponseBodyWrapped {
136 case WebMessageBodyStyle.Wrapped:
137 case WebMessageBodyStyle.WrappedResponse:
140 return BodyName != null;
144 public OperationDescription Operation {
145 get { return operation; }
148 public QueryStringConverter Converter {
149 get { return converter; }
152 public ServiceEndpoint Endpoint {
153 get { return endpoint; }
156 public UriTemplate UriTemplate {
157 get { return template; }
160 protected WebContentFormat ToContentFormat (WebMessageFormat src, object result)
162 if (result is Stream)
163 return WebContentFormat.Raw;
165 case WebMessageFormat.Xml:
166 return WebContentFormat.Xml;
167 case WebMessageFormat.Json:
168 return WebContentFormat.Json;
170 throw new SystemException ("INTERNAL ERROR: should not happen");
173 protected string GetMediaTypeString (WebContentFormat fmt)
176 case WebContentFormat.Raw:
177 return "application/octet-stream";
178 case WebContentFormat.Json:
179 return "application/json";
180 case WebContentFormat.Xml:
182 return "application/xml";
186 protected void CheckMessageVersion (MessageVersion messageVersion)
188 if (messageVersion == null)
189 throw new ArgumentNullException ("messageVersion");
191 if (!MessageVersion.None.Equals (messageVersion))
192 throw new ArgumentException (String.Format ("Only MessageVersion.None is supported. {0} is not.", messageVersion));
195 protected MessageDescription GetMessageDescription (MessageDirection dir)
197 foreach (MessageDescription md in operation.Messages)
198 if (md.Direction == dir)
200 throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir);
203 protected XmlObjectSerializer GetSerializer (WebContentFormat msgfmt, bool isWrapped, MessagePartDescription part)
205 if (part.Type == typeof (void))
206 return null; // no serialization should be done.
209 case WebContentFormat.Xml:
210 if (xml_serializer == null)
211 xml_serializer = isWrapped ? new DataContractSerializer (part.Type, part.Name, part.Namespace) : new DataContractSerializer (part.Type);
212 return xml_serializer;
213 case WebContentFormat.Json:
214 // FIXME: after name argument they are hack
215 if (json_serializer == null)
217 json_serializer = new DataContractJsonSerializer (part.Type);
219 json_serializer = isWrapped ? new DataContractJsonSerializer (part.Type, BodyName ?? part.Name, null, 0x100000, false, null, true) : new DataContractJsonSerializer (part.Type);
221 return json_serializer;
223 throw new NotImplementedException (msgfmt.ToString ());
227 XmlObjectSerializer xml_serializer, json_serializer;
229 protected object DeserializeObject (XmlObjectSerializer serializer, Message message, MessageDescription md, bool isWrapped, WebContentFormat fmt)
231 // FIXME: handle ref/out parameters
233 var reader = message.GetReaderAtBodyContents ();
234 reader.MoveToContent ();
236 bool wasEmptyElement = reader.IsEmptyElement;
239 if (fmt == WebContentFormat.Json)
240 reader.ReadStartElement ("root", String.Empty); // note that the wrapper name is passed to the serializer.
242 reader.ReadStartElement (md.Body.WrapperName, md.Body.WrapperNamespace);
245 var ret = (serializer == null) ? null : ReadObjectBody (serializer, reader);
247 if (isWrapped && !wasEmptyElement)
248 reader.ReadEndElement ();
253 protected object ReadObjectBody (XmlObjectSerializer serializer, XmlReader reader)
256 return (serializer is DataContractJsonSerializer) ?
257 ((DataContractJsonSerializer) serializer).ReadObject (reader) :
258 ((DataContractSerializer) serializer).ReadObject (reader, true);
260 return serializer.ReadObject (reader, true);
264 internal class RequestClientFormatter : WebClientMessageFormatter
266 public RequestClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
267 : base (operation, endpoint, converter, behavior)
271 public override object DeserializeReply (Message message, object [] parameters)
273 throw new NotSupportedException ();
277 internal class ReplyClientFormatter : WebClientMessageFormatter
279 public ReplyClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
280 : base (operation, endpoint, converter, behavior)
284 public override Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
286 throw new NotSupportedException ();
291 internal class RequestDispatchFormatter : WebDispatchMessageFormatter
293 public RequestDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
294 : base (operation, endpoint, converter, behavior)
298 public override Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
300 throw new NotSupportedException ();
304 internal class ReplyDispatchFormatter : WebDispatchMessageFormatter
306 public ReplyDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
307 : base (operation, endpoint, converter, behavior)
311 public override void DeserializeRequest (Message message, object [] parameters)
313 throw new NotSupportedException ();
318 internal abstract class WebClientMessageFormatter : WebMessageFormatter, IClientMessageFormatter
320 IClientMessageFormatter default_formatter;
322 protected WebClientMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
323 : base (operation, endpoint, converter, behavior)
327 public virtual Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
329 if (parameters == null)
330 throw new ArgumentNullException ("parameters");
331 CheckMessageVersion (messageVersion);
333 var c = new Dictionary<string,string> ();
335 MessageDescription md = GetMessageDescription (MessageDirection.Input);
339 object msgpart = null;
342 for (int i = 0; i < parameters.Length; i++) {
343 var p = md.Body.Parts [i];
344 string name = p.Name.ToUpper (CultureInfo.InvariantCulture);
345 if (UriTemplate.PathSegmentVariableNames.Contains (name) ||
346 UriTemplate.QueryValueVariableNames.Contains (name))
347 c.Add (name, parameters [i] != null ? Converter.ConvertValueToString (parameters [i], parameters [i].GetType ()) : null);
349 // FIXME: bind as a message part
351 msgpart = parameters [i];
353 throw new NotImplementedException (String.Format ("More than one parameters including {0} that are not contained in the URI template {1} was found.", p.Name, UriTemplate));
356 ret = Message.CreateMessage (messageVersion, (string) null, msgpart);
358 to = UriTemplate.BindByName (Endpoint.Address.Uri, c);
361 var hp = new HttpRequestMessageProperty ();
362 hp.Method = Info.Method;
364 WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat;
365 var contentFormat = ToContentFormat (msgfmt, msgpart);
366 string mediaType = GetMediaTypeString (contentFormat);
367 // FIXME: get encoding from somewhere
368 hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8";
371 if (WebOperationContext.Current != null)
372 WebOperationContext.Current.OutgoingRequest.Apply (hp);
374 // FIXME: set hp.SuppressEntityBody for some cases.
375 ret.Properties.Add (HttpRequestMessageProperty.Name, hp);
377 var wp = new WebBodyFormatMessageProperty (ToContentFormat (Info.IsRequestFormatSetExplicitly ? Info.RequestFormat : Behavior.DefaultOutgoingRequestFormat, null));
378 ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
383 public virtual object DeserializeReply (Message message, object [] parameters)
385 if (parameters == null)
386 throw new ArgumentNullException ("parameters");
387 CheckMessageVersion (message.Version);
390 if (OperationContext.Current != null) {
391 // Set response in the context
392 OperationContext.Current.IncomingMessage = message;
397 return null; // empty message, could be returned by HttpReplyChannel.
399 string pname = WebBodyFormatMessageProperty.Name;
400 if (!message.Properties.ContainsKey (pname))
401 throw new SystemException ("INTERNAL ERROR: it expects WebBodyFormatMessageProperty existence");
402 var wp = (WebBodyFormatMessageProperty) message.Properties [pname];
403 var fmt = wp != null ? wp.Format : WebContentFormat.Xml;
405 var md = GetMessageDescription (MessageDirection.Output);
406 var serializer = GetSerializer (wp.Format, IsResponseBodyWrapped, md.Body.ReturnValue);
407 var ret = DeserializeObject (serializer, message, md, IsResponseBodyWrapped, fmt);
413 internal class WrappedBodyWriter : BodyWriter
415 public WrappedBodyWriter (object value, XmlObjectSerializer serializer, string name, string ns, WebContentFormat fmt)
421 this.serializer = serializer;
425 WebContentFormat fmt;
428 XmlObjectSerializer serializer;
431 protected override BodyWriter OnCreateBufferedCopy (int maxBufferSize)
433 return new WrappedBodyWriter (value, serializer, name, ns, fmt);
437 protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
440 case WebContentFormat.Raw:
441 WriteRawContents (writer);
443 case WebContentFormat.Json:
444 WriteJsonBodyContents (writer);
446 case WebContentFormat.Xml:
447 WriteXmlBodyContents (writer);
452 void WriteRawContents (XmlDictionaryWriter writer)
454 throw new NotSupportedException ("Some unsupported sequence of writing operation occured. It is likely a missing feature.");
457 void WriteJsonBodyContents (XmlDictionaryWriter writer)
460 writer.WriteStartElement ("root");
461 writer.WriteAttributeString ("type", "object");
463 WriteObject (serializer, writer, value);
465 writer.WriteEndElement ();
468 void WriteXmlBodyContents (XmlDictionaryWriter writer)
471 writer.WriteStartElement (name, ns);
472 WriteObject (serializer, writer, value);
474 writer.WriteEndElement ();
477 void WriteObject (XmlObjectSerializer serializer, XmlDictionaryWriter writer, object value)
480 if (serializer is DataContractJsonSerializer)
481 ((DataContractJsonSerializer) serializer).WriteObject (writer, value);
483 ((DataContractSerializer) serializer).WriteObject (writer, value);
485 serializer.WriteObject (writer, value);
491 internal abstract class WebDispatchMessageFormatter : WebMessageFormatter, IDispatchMessageFormatter
493 protected WebDispatchMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
494 : base (operation, endpoint, converter, behavior)
498 public virtual Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
501 return SerializeReplyCore (messageVersion, parameters, result);
503 if (WebOperationContext.Current != null)
504 OperationContext.Current.Extensions.Remove (WebOperationContext.Current);
508 Message SerializeReplyCore (MessageVersion messageVersion, object [] parameters, object result)
510 // parameters could be null.
511 // result could be null. For Raw output, it becomes no output.
513 CheckMessageVersion (messageVersion);
515 MessageDescription md = GetMessageDescription (MessageDirection.Output);
518 // var dcob = Operation.Behaviors.Find<DataContractSerializerOperationBehavior> ();
519 // XmlObjectSerializer xos = dcob.CreateSerializer (result.GetType (), md.Body.WrapperName, md.Body.WrapperNamespace, null);
520 // var xsob = Operation.Behaviors.Find<XmlSerializerOperationBehavior> ();
521 // XmlSerializer [] serializers = XmlSerializer.FromMappings (xsob.GetXmlMappings ().ToArray ());
523 WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat;
525 XmlObjectSerializer serializer = null;
527 // FIXME: serialize ref/out parameters as well.
529 string name = null, ns = null;
532 case WebMessageFormat.Xml:
533 serializer = GetSerializer (WebContentFormat.Xml, IsResponseBodyWrapped, md.Body.ReturnValue);
534 name = IsResponseBodyWrapped ? md.Body.WrapperName : null;
535 ns = IsResponseBodyWrapped ? md.Body.WrapperNamespace : null;
537 case WebMessageFormat.Json:
538 serializer = GetSerializer (WebContentFormat.Json, IsResponseBodyWrapped, md.Body.ReturnValue);
539 name = IsResponseBodyWrapped ? (BodyName ?? md.Body.ReturnValue.Name) : null;
544 var contentFormat = ToContentFormat (msgfmt, result);
545 string mediaType = GetMediaTypeString (contentFormat);
546 Message ret = contentFormat == WebContentFormat.Raw ? new RawMessage ((Stream) result) : Message.CreateMessage (MessageVersion.None, null, new WrappedBodyWriter (result, serializer, name, ns, contentFormat));
548 // Message properties
550 var hp = new HttpResponseMessageProperty ();
551 // FIXME: get encoding from somewhere
552 hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8";
554 // apply user-customized HTTP results via WebOperationContext.
555 if (WebOperationContext.Current != null) // this formatter must be available outside ServiceHost.
556 WebOperationContext.Current.OutgoingResponse.Apply (hp);
558 // FIXME: fill some properties if required.
559 ret.Properties.Add (HttpResponseMessageProperty.Name, hp);
561 var wp = new WebBodyFormatMessageProperty (contentFormat);
562 ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
567 public virtual void DeserializeRequest (Message message, object [] parameters)
569 if (parameters == null)
570 throw new ArgumentNullException ("parameters");
571 CheckMessageVersion (message.Version);
573 IncomingWebRequestContext iwc = null;
574 if (OperationContext.Current != null) {
575 OperationContext.Current.Extensions.Add (new WebOperationContext (OperationContext.Current));
576 iwc = WebOperationContext.Current.IncomingRequest;
579 var wp = message.Properties [WebBodyFormatMessageProperty.Name] as WebBodyFormatMessageProperty;
580 var fmt = wp != null ? wp.Format : WebContentFormat.Xml;
582 Uri to = message.Headers.To;
583 UriTemplateMatch match = to == null ? null : UriTemplate.Match (Endpoint.Address.Uri, to);
584 if (match != null && iwc != null)
585 iwc.UriTemplateMatch = match;
587 MessageDescription md = GetMessageDescription (MessageDirection.Input);
589 for (int i = 0; i < parameters.Length; i++) {
590 var p = md.Body.Parts [i];
591 string name = p.Name.ToUpperInvariant ();
592 if (fmt == WebContentFormat.Raw && p.Type == typeof (Stream)) {
593 var rmsg = (RawMessage) message;
594 parameters [i] = rmsg.Stream;
596 var str = match.BoundVariables [name];
598 parameters [i] = Converter.ConvertStringToValue (str, p.Type);
600 if (info.Method != "GET") {
601 var serializer = GetSerializer (fmt, IsRequestBodyWrapped, p);
602 parameters [i] = DeserializeObject (serializer, message, md, IsRequestBodyWrapped, fmt);
604 // for GET Uri template parameters, there is no <anyType xsi:nil='true' />. So just skip the member.
612 internal class RawMessage : Message
614 public RawMessage (Stream stream)
616 this.Stream = stream;
617 headers = new MessageHeaders (MessageVersion.None);
618 properties = new MessageProperties ();
621 public override MessageVersion Version {
622 get { return MessageVersion.None; }
625 MessageHeaders headers;
627 public override MessageHeaders Headers {
628 get { return headers; }
631 MessageProperties properties;
633 public override MessageProperties Properties {
634 get { return properties; }
637 public Stream Stream { get; private set; }
639 protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
641 writer.WriteString ("-- message body is raw binary --");
644 protected override MessageBuffer OnCreateBufferedCopy (int maxBufferSize)
646 var ms = Stream as MemoryStream;
648 ms = new MemoryStream ();
649 #if NET_4_0 || NET_2_1
652 byte [] tmp = new byte [0x1000];
655 size = Stream.Read (tmp, 0, tmp.Length);
656 ms.Write (tmp, 0, size);
661 return new RawMessageBuffer (ms.ToArray (), headers, properties);
665 internal class RawMessageBuffer : MessageBuffer
668 MessageHeaders headers;
669 MessageProperties properties;
671 public RawMessageBuffer (byte [] buffer, MessageHeaders headers, MessageProperties properties)
673 this.buffer = buffer;
674 this.headers = new MessageHeaders (headers);
675 this.properties = new MessageProperties (properties);
678 public override int BufferSize {
679 get { return buffer.Length; }
682 public override void Close ()
686 public override Message CreateMessage ()
688 var msg = new RawMessage (new MemoryStream (buffer));
689 msg.Headers.CopyHeadersFrom (headers);
690 msg.Properties.CopyProperties (properties);