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.Specialized;
30 using System.Reflection;
31 using System.Runtime.Serialization;
32 using System.Runtime.Serialization.Json;
33 using System.ServiceModel;
34 using System.ServiceModel.Channels;
35 using System.ServiceModel.Dispatcher;
36 using System.ServiceModel.Web;
40 namespace System.ServiceModel.Description
42 internal abstract class WebMessageFormatter
44 OperationDescription operation;
45 ServiceEndpoint endpoint;
46 QueryStringConverter converter;
47 WebHttpBehavior behavior;
49 WebAttributeInfo info = null;
51 public WebMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
53 this.operation = operation;
54 this.endpoint = endpoint;
55 this.converter = converter;
56 this.behavior = behavior;
58 // This is a hack for WebScriptEnablingBehavior
59 var jqc = converter as JsonQueryStringConverter;
61 BodyName = jqc.CustomWrapperName;
64 void ApplyWebAttribute ()
66 MethodInfo mi = operation.SyncMethod ?? operation.BeginMethod;
68 object [] atts = mi.GetCustomAttributes (typeof (WebGetAttribute), false);
70 info = ((WebGetAttribute) atts [0]).Info;
71 atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), false);
73 info = ((WebInvokeAttribute) atts [0]).Info;
75 info = new WebAttributeInfo ();
77 template = info.BuildUriTemplate (Operation, GetMessageDescription (MessageDirection.Input));
80 public string BodyName { get; set; }
82 public WebHttpBehavior Behavior {
83 get { return behavior; }
86 public WebAttributeInfo Info {
90 public WebMessageBodyStyle BodyStyle {
91 get { return info.IsBodyStyleSetExplicitly ? info.BodyStyle : behavior.DefaultBodyStyle; }
94 public bool IsResponseBodyWrapped {
97 case WebMessageBodyStyle.Wrapped:
98 case WebMessageBodyStyle.WrappedResponse:
101 return BodyName != null;
105 public OperationDescription Operation {
106 get { return operation; }
109 public QueryStringConverter Converter {
110 get { return converter; }
113 public ServiceEndpoint Endpoint {
114 get { return endpoint; }
117 public UriTemplate UriTemplate {
118 get { return template; }
121 protected WebContentFormat ToContentFormat (WebMessageFormat src)
124 case WebMessageFormat.Xml:
125 return WebContentFormat.Xml;
126 case WebMessageFormat.Json:
127 return WebContentFormat.Json;
129 throw new SystemException ("INTERNAL ERROR: should not happen");
132 protected void CheckMessageVersion (MessageVersion messageVersion)
134 if (messageVersion == null)
135 throw new ArgumentNullException ("messageVersion");
137 if (!MessageVersion.None.Equals (messageVersion))
138 throw new ArgumentException ("Only MessageVersion.None is supported");
141 protected MessageDescription GetMessageDescription (MessageDirection dir)
143 foreach (MessageDescription md in operation.Messages)
144 if (md.Direction == dir)
146 throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir);
149 protected XmlObjectSerializer GetSerializer (WebContentFormat msgfmt)
152 case WebContentFormat.Xml:
153 if (IsResponseBodyWrapped)
154 return GetSerializer (ref xml_serializer, p => new DataContractSerializer (p.Type, p.Name, p.Namespace));
156 return GetSerializer (ref xml_serializer, p => new DataContractSerializer (p.Type));
158 case WebContentFormat.Json:
159 if (IsResponseBodyWrapped)
160 return GetSerializer (ref json_serializer, p => new DataContractJsonSerializer (p.Type, BodyName ?? p.Name));
162 return GetSerializer (ref json_serializer, p => new DataContractJsonSerializer (p.Type));
165 throw new NotImplementedException ();
169 XmlObjectSerializer xml_serializer, json_serializer;
171 XmlObjectSerializer GetSerializer (ref XmlObjectSerializer serializer, Func<MessagePartDescription,XmlObjectSerializer> f)
173 if (serializer == null) {
174 MessageDescription md = GetMessageDescription (MessageDirection.Output);
175 serializer = f (md.Body.ReturnValue);
180 internal class RequestClientFormatter : WebClientMessageFormatter
182 public RequestClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
183 : base (operation, endpoint, converter, behavior)
187 public override object DeserializeReply (Message message, object [] parameters)
189 throw new NotSupportedException ();
193 internal class ReplyClientFormatter : WebClientMessageFormatter
195 public ReplyClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
196 : base (operation, endpoint, converter, behavior)
200 public override Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
202 throw new NotSupportedException ();
206 internal class RequestDispatchFormatter : WebDispatchMessageFormatter
208 public RequestDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
209 : base (operation, endpoint, converter, behavior)
213 public override Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
215 throw new NotSupportedException ();
219 internal class ReplyDispatchFormatter : WebDispatchMessageFormatter
221 public ReplyDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
222 : base (operation, endpoint, converter, behavior)
226 public override void DeserializeRequest (Message message, object [] parameters)
228 throw new NotSupportedException ();
232 internal abstract class WebClientMessageFormatter : WebMessageFormatter, IClientMessageFormatter
234 protected WebClientMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
235 : base (operation, endpoint, converter, behavior)
239 public virtual Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
241 if (parameters == null)
242 throw new ArgumentNullException ("parameters");
243 CheckMessageVersion (messageVersion);
245 var c = new NameValueCollection ();
247 MessageDescription md = GetMessageDescription (MessageDirection.Input);
249 if (parameters.Length != md.Body.Parts.Count)
250 throw new ArgumentException ("Parameter array length does not match the number of message body parts");
252 for (int i = 0; i < parameters.Length; i++) {
253 var p = md.Body.Parts [i];
254 string name = p.Name.ToUpperInvariant ();
255 if (UriTemplate.PathSegmentVariableNames.Contains (name) ||
256 UriTemplate.QueryValueVariableNames.Contains (name))
257 c.Add (name, parameters [i] != null ? Converter.ConvertValueToString (parameters [i], parameters [i].GetType ()) : null);
259 // FIXME: bind as a message part
260 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));
263 Uri to = UriTemplate.BindByName (Endpoint.Address.Uri, c);
265 Message ret = Message.CreateMessage (messageVersion, (string) null);
268 var hp = new HttpRequestMessageProperty ();
269 hp.Method = Info.Method;
271 // FIXME: isn't it always null?
272 if (WebOperationContext.Current != null)
273 WebOperationContext.Current.OutgoingRequest.Apply (hp);
274 // FIXME: set hp.SuppressEntityBody for some cases.
275 ret.Properties.Add (HttpRequestMessageProperty.Name, hp);
277 var wp = new WebBodyFormatMessageProperty (ToContentFormat (Info.IsRequestFormatSetExplicitly ? Info.RequestFormat : Behavior.DefaultOutgoingRequestFormat));
278 ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
283 public virtual object DeserializeReply (Message message, object [] parameters)
285 if (parameters == null)
286 throw new ArgumentNullException ("parameters");
287 CheckMessageVersion (message.Version);
289 string pname = WebBodyFormatMessageProperty.Name;
290 if (!message.Properties.ContainsKey (pname))
291 throw new SystemException ("INTERNAL ERROR: it expects WebBodyFormatMessageProperty existence");
292 var wp = (WebBodyFormatMessageProperty) message.Properties [pname];
294 var serializer = GetSerializer (wp.Format);
296 // FIXME: handle ref/out parameters
298 var md = GetMessageDescription (MessageDirection.Output);
300 var reader = message.GetReaderAtBodyContents ();
302 if (IsResponseBodyWrapped) {
303 if (wp.Format == WebContentFormat.Json)
304 reader.ReadStartElement ("root", String.Empty); // note that the wrapper name is passed to the serializer.
306 reader.ReadStartElement (md.Body.WrapperName, md.Body.WrapperNamespace);
309 var ret = serializer.ReadObject (reader, true);
311 if (IsResponseBodyWrapped)
312 reader.ReadEndElement ();
318 internal class WrappedBodyWriter : BodyWriter
320 public WrappedBodyWriter (object value, XmlObjectSerializer serializer, string name, string ns, bool json)
326 this.serializer = serializer;
333 XmlObjectSerializer serializer;
335 protected override BodyWriter OnCreateBufferedCopy (int maxBufferSize)
337 return new WrappedBodyWriter (value, serializer, name, ns, is_json);
340 protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
343 WriteJsonBodyContents (writer);
345 WriteXmlBodyContents (writer);
348 void WriteJsonBodyContents (XmlDictionaryWriter writer)
351 writer.WriteStartElement ("root");
352 writer.WriteAttributeString ("type", "object");
354 serializer.WriteObject (writer, value);
356 writer.WriteEndElement ();
359 void WriteXmlBodyContents (XmlDictionaryWriter writer)
362 writer.WriteStartElement (name, ns);
363 serializer.WriteObject (writer, value);
365 writer.WriteEndElement ();
369 internal abstract class WebDispatchMessageFormatter : WebMessageFormatter, IDispatchMessageFormatter
371 protected WebDispatchMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
372 : base (operation, endpoint, converter, behavior)
376 public virtual Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
379 return SerializeReplyCore (messageVersion, parameters, result);
381 if (WebOperationContext.Current != null)
382 OperationContext.Current.Extensions.Remove (WebOperationContext.Current);
386 Message SerializeReplyCore (MessageVersion messageVersion, object [] parameters, object result)
388 if (parameters == null)
389 throw new ArgumentNullException ("parameters");
390 CheckMessageVersion (messageVersion);
392 MessageDescription md = GetMessageDescription (MessageDirection.Output);
395 // var dcob = Operation.Behaviors.Find<DataContractSerializerOperationBehavior> ();
396 // XmlObjectSerializer xos = dcob.CreateSerializer (result.GetType (), md.Body.WrapperName, md.Body.WrapperNamespace, null);
397 // var xsob = Operation.Behaviors.Find<XmlSerializerOperationBehavior> ();
398 // XmlSerializer [] serializers = XmlSerializer.FromMappings (xsob.GetXmlMappings ().ToArray ());
400 WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat;
402 string mediaType = null;
403 XmlObjectSerializer serializer = null;
405 // FIXME: serialize ref/out parameters as well.
407 string name = null, ns = null;
410 case WebMessageFormat.Xml:
411 serializer = GetSerializer (WebContentFormat.Xml);
412 mediaType = "application/xml";
413 name = IsResponseBodyWrapped ? md.Body.WrapperName : null;
414 ns = IsResponseBodyWrapped ? md.Body.WrapperNamespace : null;
416 case WebMessageFormat.Json:
417 serializer = GetSerializer (WebContentFormat.Json);
418 mediaType = "application/json";
419 name = IsResponseBodyWrapped ? (BodyName ?? md.Body.ReturnValue.Name) : null;
424 bool json = msgfmt == WebMessageFormat.Json;
425 Message ret = Message.CreateMessage (MessageVersion.None, null, new WrappedBodyWriter (result, serializer, name, ns, json));
427 // Message properties
429 var hp = new HttpResponseMessageProperty ();
430 // FIXME: get encoding from somewhere
431 hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8";
433 // apply user-customized HTTP results via WebOperationContext.
434 WebOperationContext.Current.OutgoingResponse.Apply (hp);
436 // FIXME: fill some properties if required.
437 ret.Properties.Add (HttpResponseMessageProperty.Name, hp);
439 var wp = new WebBodyFormatMessageProperty (ToContentFormat (msgfmt));
440 ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
445 public virtual void DeserializeRequest (Message message, object [] parameters)
447 if (parameters == null)
448 throw new ArgumentNullException ("parameters");
449 CheckMessageVersion (message.Version);
451 OperationContext.Current.Extensions.Add (new WebOperationContext (OperationContext.Current));
453 IncomingWebRequestContext iwc = WebOperationContext.Current.IncomingRequest;
455 Uri to = message.Headers.To;
456 UriTemplateMatch match = UriTemplate.Match (Endpoint.Address.Uri, to);
458 // not sure if it could happen
459 throw new SystemException (String.Format ("INTERNAL ERROR: UriTemplate does not match with the request: {0} / {1}", UriTemplate, to));
460 iwc.UriTemplateMatch = match;
462 MessageDescription md = GetMessageDescription (MessageDirection.Input);
464 for (int i = 0; i < parameters.Length; i++) {
465 var p = md.Body.Parts [i];
466 string name = p.Name.ToUpperInvariant ();
467 var str = match.BoundVariables [name];
468 parameters [i] = Converter.ConvertStringToValue (str, p.Type);