2010-04-06 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.ServiceModel.Web / System.ServiceModel.Dispatcher / WebMessageFormatter.cs
blobba802e34c03f25d2d796988115b5910655f8fc9a
1 //
2 // WebMessageFormatter.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008,2009 Novell, Inc (http://www.novell.com)
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
28 using System;
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;
38 using System.Text;
39 using System.Xml;
41 namespace System.ServiceModel.Description
43 internal abstract class WebMessageFormatter
45 OperationDescription operation;
46 ServiceEndpoint endpoint;
47 QueryStringConverter converter;
48 WebHttpBehavior behavior;
49 UriTemplate template;
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;
58 ApplyWebAttribute ();
59 // This is a hack for WebScriptEnablingBehavior
60 var jqc = converter as JsonQueryStringConverter;
61 if (jqc != null)
62 BodyName = jqc.CustomWrapperName;
65 void ApplyWebAttribute ()
67 MethodInfo mi = operation.SyncMethod ?? operation.BeginMethod;
69 object [] atts = mi.GetCustomAttributes (typeof (WebGetAttribute), false);
70 if (atts.Length > 0)
71 info = ((WebGetAttribute) atts [0]).Info;
72 atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), false);
73 if (atts.Length > 0)
74 info = ((WebInvokeAttribute) atts [0]).Info;
75 if (info == null)
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 {
88 get { return info; }
91 public WebMessageBodyStyle BodyStyle {
92 get { return info.IsBodyStyleSetExplicitly ? info.BodyStyle : behavior.DefaultBodyStyle; }
95 public bool IsResponseBodyWrapped {
96 get {
97 switch (BodyStyle) {
98 case WebMessageBodyStyle.Wrapped:
99 case WebMessageBodyStyle.WrappedResponse:
100 return true;
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)
124 switch (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)
146 return md;
147 throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir);
150 protected XmlObjectSerializer GetSerializer (WebContentFormat msgfmt)
152 switch (msgfmt) {
153 case WebContentFormat.Xml:
154 if (IsResponseBodyWrapped)
155 return GetSerializer (ref xml_serializer, p => new DataContractSerializer (p.Type, p.Name, p.Namespace));
156 else
157 return GetSerializer (ref xml_serializer, p => new DataContractSerializer (p.Type));
158 break;
159 case WebContentFormat.Json:
160 // FIXME: after name argument they are hack
161 #if !MOONLIGHT
162 if (IsResponseBodyWrapped)
163 return GetSerializer (ref json_serializer, p => new DataContractJsonSerializer (p.Type, BodyName ?? p.Name, null, 0x100000, false, null, true));
164 else
165 #endif
166 return GetSerializer (ref json_serializer, p => new DataContractJsonSerializer (p.Type));
167 break;
168 default:
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);
181 return serializer;
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 ();
210 #if !NET_2_1
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 ();
236 #endif
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);
264 else
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);
272 ret.Headers.To = to;
274 var hp = new HttpRequestMessageProperty ();
275 hp.Method = Info.Method;
277 #if !NET_2_1
278 if (WebOperationContext.Current != null)
279 WebOperationContext.Current.OutgoingRequest.Apply (hp);
280 #endif
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);
287 return ret;
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.
312 else
313 reader.ReadStartElement (md.Body.WrapperName, md.Body.WrapperNamespace);
316 var ret = serializer.ReadObject (reader, true);
318 if (IsResponseBodyWrapped)
319 reader.ReadEndElement ();
321 return ret;
325 internal class WrappedBodyWriter : BodyWriter
327 public WrappedBodyWriter (object value, XmlObjectSerializer serializer, string name, string ns, bool json)
328 : base (true)
330 this.name = name;
331 this.ns = ns;
332 this.value = value;
333 this.serializer = serializer;
334 this.is_json = json;
337 bool is_json;
338 string name, ns;
339 object value;
340 XmlObjectSerializer serializer;
342 #if !NET_2_1
343 protected override BodyWriter OnCreateBufferedCopy (int maxBufferSize)
345 return new WrappedBodyWriter (value, serializer, name, ns, is_json);
347 #endif
349 protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
351 if (is_json)
352 WriteJsonBodyContents (writer);
353 else
354 WriteXmlBodyContents (writer);
357 void WriteJsonBodyContents (XmlDictionaryWriter writer)
359 if (name != null) {
360 writer.WriteStartElement ("root");
361 writer.WriteAttributeString ("type", "object");
363 serializer.WriteObject (writer, value);
364 if (name != null)
365 writer.WriteEndElement ();
368 void WriteXmlBodyContents (XmlDictionaryWriter writer)
370 if (name != null)
371 writer.WriteStartElement (name, ns);
372 serializer.WriteObject (writer, value);
373 if (name != null)
374 writer.WriteEndElement ();
378 #if !NET_2_1
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)
388 try {
389 return SerializeReplyCore (messageVersion, parameters, result);
390 } finally {
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);
404 // FIXME: use them.
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;
419 switch (msgfmt) {
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;
425 break;
426 case WebMessageFormat.Json:
427 serializer = GetSerializer (WebContentFormat.Json);
428 mediaType = "application/json";
429 name = IsResponseBodyWrapped ? (BodyName ?? md.Body.ReturnValue.Name) : null;
430 ns = String.Empty;
431 break;
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);
452 return ret;
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);
467 if (match == null)
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);
482 #endif