2 // WebMessageFormatter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2008,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
;
30 using System
.Globalization
;
31 using System
.Reflection
;
32 using System
.Runtime
.Serialization
;
33 using System
.Runtime
.Serialization
.Json
;
34 using System
.ServiceModel
;
35 using System
.ServiceModel
.Channels
;
36 using System
.ServiceModel
.Dispatcher
;
37 using System
.ServiceModel
.Web
;
41 namespace System
.ServiceModel
.Description
43 internal abstract class WebMessageFormatter
45 OperationDescription operation
;
46 ServiceEndpoint endpoint
;
47 QueryStringConverter converter
;
48 WebHttpBehavior behavior
;
50 WebAttributeInfo info
= null;
52 public WebMessageFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
54 this.operation
= operation
;
55 this.endpoint
= endpoint
;
56 this.converter
= converter
;
57 this.behavior
= behavior
;
59 // This is a hack for WebScriptEnablingBehavior
60 var jqc
= converter
as JsonQueryStringConverter
;
62 BodyName
= jqc
.CustomWrapperName
;
65 void ApplyWebAttribute ()
67 MethodInfo mi
= operation
.SyncMethod
?? operation
.BeginMethod
;
69 object [] atts
= mi
.GetCustomAttributes (typeof (WebGetAttribute
), false);
71 info
= ((WebGetAttribute
) atts
[0]).Info
;
72 atts
= mi
.GetCustomAttributes (typeof (WebInvokeAttribute
), false);
74 info
= ((WebInvokeAttribute
) atts
[0]).Info
;
76 info
= new WebAttributeInfo ();
78 template
= info
.BuildUriTemplate (Operation
, GetMessageDescription (MessageDirection
.Input
));
81 public string BodyName { get; set; }
83 public WebHttpBehavior Behavior
{
84 get { return behavior; }
87 public WebAttributeInfo Info
{
91 public WebMessageBodyStyle BodyStyle
{
92 get { return info.IsBodyStyleSetExplicitly ? info.BodyStyle : behavior.DefaultBodyStyle; }
95 public bool IsResponseBodyWrapped
{
98 case WebMessageBodyStyle
.Wrapped
:
99 case WebMessageBodyStyle
.WrappedResponse
:
102 return BodyName
!= null;
106 public OperationDescription Operation
{
107 get { return operation; }
110 public QueryStringConverter Converter
{
111 get { return converter; }
114 public ServiceEndpoint Endpoint
{
115 get { return endpoint; }
118 public UriTemplate UriTemplate
{
119 get { return template; }
122 protected WebContentFormat
ToContentFormat (WebMessageFormat src
)
125 case WebMessageFormat
.Xml
:
126 return WebContentFormat
.Xml
;
127 case WebMessageFormat
.Json
:
128 return WebContentFormat
.Json
;
130 throw new SystemException ("INTERNAL ERROR: should not happen");
133 protected void CheckMessageVersion (MessageVersion messageVersion
)
135 if (messageVersion
== null)
136 throw new ArgumentNullException ("messageVersion");
138 if (!MessageVersion
.None
.Equals (messageVersion
))
139 throw new ArgumentException ("Only MessageVersion.None is supported");
142 protected MessageDescription
GetMessageDescription (MessageDirection dir
)
144 foreach (MessageDescription md
in operation
.Messages
)
145 if (md
.Direction
== dir
)
147 throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir
);
150 protected XmlObjectSerializer
GetSerializer (WebContentFormat msgfmt
)
153 case WebContentFormat
.Xml
:
154 if (IsResponseBodyWrapped
)
155 return GetSerializer (ref xml_serializer
, p
=> new DataContractSerializer (p
.Type
, p
.Name
, p
.Namespace
));
157 return GetSerializer (ref xml_serializer
, p
=> new DataContractSerializer (p
.Type
));
159 case WebContentFormat
.Json
:
160 // FIXME: after name argument they are hack
162 if (IsResponseBodyWrapped
)
163 return GetSerializer (ref json_serializer
, p
=> new DataContractJsonSerializer (p
.Type
, BodyName
?? p
.Name
, null, 0x100000, false, null, true));
166 return GetSerializer (ref json_serializer
, p
=> new DataContractJsonSerializer (p
.Type
));
169 throw new NotImplementedException ();
173 XmlObjectSerializer xml_serializer
, json_serializer
;
175 XmlObjectSerializer
GetSerializer (ref XmlObjectSerializer serializer
, Func
<MessagePartDescription
,XmlObjectSerializer
> f
)
177 if (serializer
== null) {
178 MessageDescription md
= GetMessageDescription (MessageDirection
.Output
);
179 serializer
= f (md
.Body
.ReturnValue
);
184 internal class RequestClientFormatter
: WebClientMessageFormatter
186 public RequestClientFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
187 : base (operation
, endpoint
, converter
, behavior
)
191 public override object DeserializeReply (Message message
, object [] parameters
)
193 throw new NotSupportedException ();
197 internal class ReplyClientFormatter
: WebClientMessageFormatter
199 public ReplyClientFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
200 : base (operation
, endpoint
, converter
, behavior
)
204 public override Message
SerializeRequest (MessageVersion messageVersion
, object [] parameters
)
206 throw new NotSupportedException ();
211 internal class RequestDispatchFormatter
: WebDispatchMessageFormatter
213 public RequestDispatchFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
214 : base (operation
, endpoint
, converter
, behavior
)
218 public override Message
SerializeReply (MessageVersion messageVersion
, object [] parameters
, object result
)
220 throw new NotSupportedException ();
224 internal class ReplyDispatchFormatter
: WebDispatchMessageFormatter
226 public ReplyDispatchFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
227 : base (operation
, endpoint
, converter
, behavior
)
231 public override void DeserializeRequest (Message message
, object [] parameters
)
233 throw new NotSupportedException ();
238 internal abstract class WebClientMessageFormatter
: WebMessageFormatter
, IClientMessageFormatter
240 protected WebClientMessageFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
241 : base (operation
, endpoint
, converter
, behavior
)
245 public virtual Message
SerializeRequest (MessageVersion messageVersion
, object [] parameters
)
247 if (parameters
== null)
248 throw new ArgumentNullException ("parameters");
249 CheckMessageVersion (messageVersion
);
251 var c
= new Dictionary
<string,string> ();
253 MessageDescription md
= GetMessageDescription (MessageDirection
.Input
);
255 if (parameters
.Length
!= md
.Body
.Parts
.Count
)
256 throw new ArgumentException ("Parameter array length does not match the number of message body parts");
258 for (int i
= 0; i
< parameters
.Length
; i
++) {
259 var p
= md
.Body
.Parts
[i
];
260 string name
= p
.Name
.ToUpper (CultureInfo
.InvariantCulture
);
261 if (UriTemplate
.PathSegmentVariableNames
.Contains (name
) ||
262 UriTemplate
.QueryValueVariableNames
.Contains (name
))
263 c
.Add (name
, parameters
[i
] != null ? Converter
.ConvertValueToString (parameters
[i
], parameters
[i
].GetType ()) : null);
265 // FIXME: bind as a message part
266 throw new NotImplementedException (String
.Format ("parameter {0} is not contained in the URI template {1} {2} {3}", p
.Name
, UriTemplate
, UriTemplate
.PathSegmentVariableNames
.Count
, UriTemplate
.QueryValueVariableNames
.Count
));
269 Uri to
= UriTemplate
.BindByName (Endpoint
.Address
.Uri
, c
);
271 Message ret
= Message
.CreateMessage (messageVersion
, (string) null);
274 var hp
= new HttpRequestMessageProperty ();
275 hp
.Method
= Info
.Method
;
278 if (WebOperationContext
.Current
!= null)
279 WebOperationContext
.Current
.OutgoingRequest
.Apply (hp
);
281 // FIXME: set hp.SuppressEntityBody for some cases.
282 ret
.Properties
.Add (HttpRequestMessageProperty
.Name
, hp
);
284 var wp
= new WebBodyFormatMessageProperty (ToContentFormat (Info
.IsRequestFormatSetExplicitly
? Info
.RequestFormat
: Behavior
.DefaultOutgoingRequestFormat
));
285 ret
.Properties
.Add (WebBodyFormatMessageProperty
.Name
, wp
);
290 public virtual object DeserializeReply (Message message
, object [] parameters
)
292 if (parameters
== null)
293 throw new ArgumentNullException ("parameters");
294 CheckMessageVersion (message
.Version
);
296 string pname
= WebBodyFormatMessageProperty
.Name
;
297 if (!message
.Properties
.ContainsKey (pname
))
298 throw new SystemException ("INTERNAL ERROR: it expects WebBodyFormatMessageProperty existence");
299 var wp
= (WebBodyFormatMessageProperty
) message
.Properties
[pname
];
301 var serializer
= GetSerializer (wp
.Format
);
303 // FIXME: handle ref/out parameters
305 var md
= GetMessageDescription (MessageDirection
.Output
);
307 var reader
= message
.GetReaderAtBodyContents ();
309 if (IsResponseBodyWrapped
) {
310 if (wp
.Format
== WebContentFormat
.Json
)
311 reader
.ReadStartElement ("root", String
.Empty
); // note that the wrapper name is passed to the serializer.
313 reader
.ReadStartElement (md
.Body
.WrapperName
, md
.Body
.WrapperNamespace
);
316 var ret
= serializer
.ReadObject (reader
, true);
318 if (IsResponseBodyWrapped
)
319 reader
.ReadEndElement ();
325 internal class WrappedBodyWriter
: BodyWriter
327 public WrappedBodyWriter (object value, XmlObjectSerializer serializer
, string name
, string ns
, bool json
)
333 this.serializer
= serializer
;
340 XmlObjectSerializer serializer
;
343 protected override BodyWriter
OnCreateBufferedCopy (int maxBufferSize
)
345 return new WrappedBodyWriter (value, serializer
, name
, ns
, is_json
);
349 protected override void OnWriteBodyContents (XmlDictionaryWriter writer
)
352 WriteJsonBodyContents (writer
);
354 WriteXmlBodyContents (writer
);
357 void WriteJsonBodyContents (XmlDictionaryWriter writer
)
360 writer
.WriteStartElement ("root");
361 writer
.WriteAttributeString ("type", "object");
363 serializer
.WriteObject (writer
, value);
365 writer
.WriteEndElement ();
368 void WriteXmlBodyContents (XmlDictionaryWriter writer
)
371 writer
.WriteStartElement (name
, ns
);
372 serializer
.WriteObject (writer
, value);
374 writer
.WriteEndElement ();
379 internal abstract class WebDispatchMessageFormatter
: WebMessageFormatter
, IDispatchMessageFormatter
381 protected WebDispatchMessageFormatter (OperationDescription operation
, ServiceEndpoint endpoint
, QueryStringConverter converter
, WebHttpBehavior behavior
)
382 : base (operation
, endpoint
, converter
, behavior
)
386 public virtual Message
SerializeReply (MessageVersion messageVersion
, object [] parameters
, object result
)
389 return SerializeReplyCore (messageVersion
, parameters
, result
);
391 if (WebOperationContext
.Current
!= null)
392 OperationContext
.Current
.Extensions
.Remove (WebOperationContext
.Current
);
396 Message
SerializeReplyCore (MessageVersion messageVersion
, object [] parameters
, object result
)
398 if (parameters
== null)
399 throw new ArgumentNullException ("parameters");
400 CheckMessageVersion (messageVersion
);
402 MessageDescription md
= GetMessageDescription (MessageDirection
.Output
);
405 // var dcob = Operation.Behaviors.Find<DataContractSerializerOperationBehavior> ();
406 // XmlObjectSerializer xos = dcob.CreateSerializer (result.GetType (), md.Body.WrapperName, md.Body.WrapperNamespace, null);
407 // var xsob = Operation.Behaviors.Find<XmlSerializerOperationBehavior> ();
408 // XmlSerializer [] serializers = XmlSerializer.FromMappings (xsob.GetXmlMappings ().ToArray ());
410 WebMessageFormat msgfmt
= Info
.IsResponseFormatSetExplicitly
? Info
.ResponseFormat
: Behavior
.DefaultOutgoingResponseFormat
;
412 string mediaType
= null;
413 XmlObjectSerializer serializer
= null;
415 // FIXME: serialize ref/out parameters as well.
417 string name
= null, ns
= null;
420 case WebMessageFormat
.Xml
:
421 serializer
= GetSerializer (WebContentFormat
.Xml
);
422 mediaType
= "application/xml";
423 name
= IsResponseBodyWrapped
? md
.Body
.WrapperName
: null;
424 ns
= IsResponseBodyWrapped
? md
.Body
.WrapperNamespace
: null;
426 case WebMessageFormat
.Json
:
427 serializer
= GetSerializer (WebContentFormat
.Json
);
428 mediaType
= "application/json";
429 name
= IsResponseBodyWrapped
? (BodyName
?? md
.Body
.ReturnValue
.Name
) : null;
434 bool json
= msgfmt
== WebMessageFormat
.Json
;
435 Message ret
= Message
.CreateMessage (MessageVersion
.None
, null, new WrappedBodyWriter (result
, serializer
, name
, ns
, json
));
437 // Message properties
439 var hp
= new HttpResponseMessageProperty ();
440 // FIXME: get encoding from somewhere
441 hp
.Headers
["Content-Type"] = mediaType
+ "; charset=utf-8";
443 // apply user-customized HTTP results via WebOperationContext.
444 WebOperationContext
.Current
.OutgoingResponse
.Apply (hp
);
446 // FIXME: fill some properties if required.
447 ret
.Properties
.Add (HttpResponseMessageProperty
.Name
, hp
);
449 var wp
= new WebBodyFormatMessageProperty (ToContentFormat (msgfmt
));
450 ret
.Properties
.Add (WebBodyFormatMessageProperty
.Name
, wp
);
455 public virtual void DeserializeRequest (Message message
, object [] parameters
)
457 if (parameters
== null)
458 throw new ArgumentNullException ("parameters");
459 CheckMessageVersion (message
.Version
);
461 OperationContext
.Current
.Extensions
.Add (new WebOperationContext (OperationContext
.Current
));
463 IncomingWebRequestContext iwc
= WebOperationContext
.Current
.IncomingRequest
;
465 Uri to
= message
.Headers
.To
;
466 UriTemplateMatch match
= UriTemplate
.Match (Endpoint
.Address
.Uri
, to
);
468 // not sure if it could happen
469 throw new SystemException (String
.Format ("INTERNAL ERROR: UriTemplate does not match with the request: {0} / {1}", UriTemplate
, to
));
470 iwc
.UriTemplateMatch
= match
;
472 MessageDescription md
= GetMessageDescription (MessageDirection
.Input
);
474 for (int i
= 0; i
< parameters
.Length
; i
++) {
475 var p
= md
.Body
.Parts
[i
];
476 string name
= p
.Name
.ToUpperInvariant ();
477 var str
= match
.BoundVariables
[name
];
478 parameters
[i
] = Converter
.ConvertStringToValue (str
, p
.Type
);