diff options
author | Jb Evain <jbevain@gmail.com> | 2008-08-21 01:25:05 +0400 |
---|---|---|
committer | Jb Evain <jbevain@gmail.com> | 2008-08-21 01:25:05 +0400 |
commit | 982e3b35a4b715fa86ef2037501bfa1c89dd5038 (patch) | |
tree | 1ba1f442ea12faa4ffbb8fca8c6e429a53f6cfc9 /mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher | |
parent | bdd0809c70020fc9c381c4310c346368e195fe76 (diff) |
move to from olive to mcs
svn path=/trunk/mcs/; revision=111188
Diffstat (limited to 'mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher')
5 files changed, 867 insertions, 0 deletions
diff --git a/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/ChangeLog b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/ChangeLog new file mode 100644 index 00000000000..eb919ace3b2 --- /dev/null +++ b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/ChangeLog @@ -0,0 +1,81 @@ +2008-04-16 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : now that ReadObject() verifies + by default, it broke deserialization. Fixed it. + +2008-02-18 Atsushi Enomoto <atsushi@ximian.com> + + * JsonQueryStringConverter.cs : implemented. + +2008-02-17 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : implement WebOperationContext support. + +2008-02-16 Atsushi Enomoto <atsushi@ximian.com> + + * WebHttpDispatchOperationSelector.cs : some code cleanup. + +2008-02-16 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : fixed content-type typo. + +2008-02-16 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : implemented DeserializeReply(). + It was wrong to have one MessageDescription in a formatter, as + they are different between serialization and deserialization. + +2008-02-16 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : oops oops. not "encoding" but "charset". + +2008-02-15 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : hack SerializeReply(). + Add WebBodyFormat property + +2008-02-15 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : implement DeserializeRequest(). + +2008-02-15 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : attach a HttpRequestMessageProperty when + creating a request Message. + +2008-02-15 Atsushi Enomoto <atsushi@ximian.com> + + * WebHttpDispatchOperationSelector.cs : implemented large part of it. + * WebMessageFormatter.cs : moved BuildUriTemplate to WebAttributeInfo. + +2008-02-14 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : pass WebHttpBehavior too. + +2008-02-14 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : pass QueryStringConverter which is used to + convert parameter objects to the components of the request URI. + +2008-02-14 Atsushi Enomoto <atsushi@ximian.com> + + * WebMessageFormatter.cs : new message formatter that is created + from WebHttpBehavior and handles [WebGet] or [WebInvoke]. Not done. + +2008-02-13 Atsushi Enomoto <atsushi@ximian.com> + + * QueryStringConverter.cs : I could just check null parameter. + +2008-02-13 Atsushi Enomoto <atsushi@ximian.com> + + * QueryStringConverter.cs: CanConvert() supports cpl more types. + Implemented ConvertStringToValue() and ConvertValueToString(). + +2008-02-12 Atsushi Enomoto <atsushi@ximian.com> + + * WebHttpDispatchOperationSelector.cs : stubbed members. + +2008-02-07 Atsushi Enomoto <atsushi@ximian.com> + + * JsonQueryStringConverter.cs, QueryStringConverter.cs, + WebHttpDispatchOperationSelector.cs : mostly stubs. diff --git a/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/JsonQueryStringConverter.cs b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/JsonQueryStringConverter.cs new file mode 100644 index 00000000000..121bc13541c --- /dev/null +++ b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/JsonQueryStringConverter.cs @@ -0,0 +1,213 @@ +// +// JsonQueryStringConverter.cs +// +// Author: +// Atsushi Enomoto <atsushi@ximian.com> +// +// Copyright (C) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization.Json; +using System.ServiceModel; +using System.ServiceModel.Description; +using System.Text; +using System.Xml; + +namespace System.ServiceModel.Dispatcher +{ + public class JsonQueryStringConverter : QueryStringConverter + { + DataContractJsonSerializer serializer = new DataContractJsonSerializer (typeof (object)); + + public override bool CanConvert (Type type) + { + // almost copy from QueryStringConverter, except that DBNull and XmlQualifiedName are supported + switch (Type.GetTypeCode (type)) { + //case TypeCode.DBNull: + case TypeCode.Empty: + return false; + case TypeCode.Object: + if (type == typeof (TimeSpan)) + return true; + if (type == typeof (DateTimeOffset)) + return true; + if (type == typeof (Guid)) + return true; + if (type == typeof (XmlQualifiedName)) + return true; + if (type == typeof (object)) + return true; +// if (type.GetCustomAttributes (typeof (TypeConverterAttribute), true).Length > 0) +// return true; + return false; + default: + return true; + } + } + + public override object ConvertStringToValue (string parameter, Type parameterType) + { + if (parameterType == null) + throw new ArgumentNullException ("parameterType"); + + if (!CanConvert (parameterType)) + throw new NotSupportedException (String.Format ("Conversion from the argument parameterType '{0}' is not supported", parameterType)); + + // In general .NET JSON parser is sloppy. It accepts + // such a string that is actually invalid in terms of + // the target type in JSON context. + + switch (Type.GetTypeCode (parameterType)) { + //case TypeCode.String: + // return parameter; + case TypeCode.Char: + return parameter != null ? Char.Parse (parameter): default (char); + case TypeCode.SByte: + return parameter != null ? SByte.Parse (parameter, CultureInfo.InvariantCulture): default (sbyte); + case TypeCode.Byte: + return parameter != null ? Byte.Parse (parameter, CultureInfo.InvariantCulture): default (byte); + case TypeCode.Int16: + return parameter != null ? Int16.Parse (parameter, CultureInfo.InvariantCulture): default (short); + case TypeCode.Int32: + return parameter != null ? Int32.Parse (parameter, CultureInfo.InvariantCulture): default (int); + case TypeCode.Int64: + return parameter != null ? Int64.Parse (parameter, CultureInfo.InvariantCulture): default (long); + case TypeCode.UInt16: + return parameter != null ? UInt16.Parse (parameter, CultureInfo.InvariantCulture): default (ushort); + case TypeCode.UInt32: + return parameter != null ? UInt32.Parse (parameter, CultureInfo.InvariantCulture): default (uint); + case TypeCode.UInt64: + return parameter != null ? UInt64.Parse (parameter, CultureInfo.InvariantCulture): default (ulong); + case TypeCode.DateTime: + return parameter != null ? DateTime.Parse (parameter, CultureInfo.InvariantCulture): default (DateTime); + case TypeCode.Boolean: + return parameter != null ? Boolean.Parse (parameter): default (bool); + case TypeCode.Single: + return parameter != null ? Single.Parse (parameter, CultureInfo.InvariantCulture): default (float); + case TypeCode.Double: + return parameter != null ? Double.Parse (parameter, CultureInfo.InvariantCulture): default (double); + case TypeCode.Decimal: + return parameter != null ? Decimal.Parse (parameter, CultureInfo.InvariantCulture): default (decimal); + } + + if (parameter == null) + return null; + + + DataContractJsonSerializer serializer = + new DataContractJsonSerializer (parameterType); + // hmm, it costs so silly though. + return serializer.ReadObject (new MemoryStream (Encoding.UTF8.GetBytes (parameter))); + } + + bool IsKnownType (Type t) + { + switch (Type.GetTypeCode (t)) { + case TypeCode.Object: + if (t == typeof (Guid) || + t == typeof (DBNull) || + t == typeof (DateTimeOffset) || + t == typeof (TimeSpan) || + t == typeof (XmlQualifiedName)) + return true; + return false; + default: + return true; + } + } + + public override string ConvertValueToString (object parameter, Type parameterType) + { + if (parameterType == null) + throw new ArgumentNullException ("parameterType"); + + if (!CanConvert (parameterType)) + throw new NotSupportedException (String.Format ("Conversion from the argument parameterType '{0}' is not supported", parameterType)); + + if (parameter == null) + return null; + + if (parameter is DBNull) + return "{}"; + + parameterType = ToActualType (parameterType); + + if (parameter is IConvertible) + parameter = ((IConvertible) parameter).ToType (parameterType, CultureInfo.InvariantCulture); + + switch (Type.GetTypeCode (parameterType)) { + case TypeCode.String: + string s = parameter is IFormattable ? + ((IFormattable) parameter).ToString (null, CultureInfo.InvariantCulture) : + parameter.ToString (); + StringBuilder sb = new StringBuilder (s); + sb.Replace ("\"", "\\\""); + sb.Insert (0, '\"'); + sb.Append ('\"'); + return sb.ToString (); + default: + if (parameterType == typeof (XmlQualifiedName)) { + var qname = (XmlQualifiedName) parameter; + return String.Concat ("\"", qname.Name, ":", qname.Namespace, "\""); + } + return parameter.ToString (); + } + + throw new NotImplementedException (); + } + + Type ToActualType (Type t) + { + switch (Type.GetTypeCode (t)) { + case TypeCode.DBNull: // though DBNull.Value input is converted to "{}". This result is used for String input. + case TypeCode.Char: + case TypeCode.String: + return typeof (string); + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: +// return typeof (long); + return typeof (decimal); + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: +// return typeof (ulong); + return typeof (decimal); + case TypeCode.DateTime: + case TypeCode.Boolean: + return t; + case TypeCode.Single: + case TypeCode.Double: +// return typeof (double); + return typeof (decimal); + case TypeCode.Decimal: + return typeof (decimal); + default: + return t; + } + } + } +} diff --git a/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/QueryStringConverter.cs b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/QueryStringConverter.cs new file mode 100644 index 00000000000..8bc37253976 --- /dev/null +++ b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/QueryStringConverter.cs @@ -0,0 +1,135 @@ +// +// QueryStringConverter.cs +// +// Author: +// Atsushi Enomoto <atsushi@ximian.com> +// +// Copyright (C) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.ComponentModel; +using System.Globalization; +using System.ServiceModel; +using System.ServiceModel.Description; + +namespace System.ServiceModel.Dispatcher +{ + public class QueryStringConverter + { + // "Service Operation Parameters and URLs" + // http://msdn2.microsoft.com/en-us/library/bb412172.aspx + public virtual bool CanConvert (Type type) + { + switch (Type.GetTypeCode (type)) { + case TypeCode.DBNull: + case TypeCode.Empty: + return false; + case TypeCode.Object: + if (type == typeof (TimeSpan)) + return true; + if (type == typeof (DateTimeOffset)) + return true; + if (type == typeof (Guid)) + return true; + if (type == typeof (object)) + return true; +// if (type.GetCustomAttributes (typeof (TypeConverterAttribute), true).Length > 0) +// return true; + return false; + default: + return true; + } + } + + public virtual object ConvertStringToValue (string parameter, Type parameterType) + { + if (parameterType == null) + throw new ArgumentNullException ("parameterType"); + + if (!CanConvert (parameterType)) + throw new NotSupportedException (String.Format ("Conversion from the argument parameterType '{0}' is not supported", parameterType)); + + switch (Type.GetTypeCode (parameterType)) { + case TypeCode.String: + return parameter; + case TypeCode.Char: + return parameter != null ? Char.Parse (parameter): default (char); + case TypeCode.SByte: + return parameter != null ? SByte.Parse (parameter, CultureInfo.InvariantCulture): default (sbyte); + case TypeCode.Byte: + return parameter != null ? Byte.Parse (parameter, CultureInfo.InvariantCulture): default (byte); + case TypeCode.Int16: + return parameter != null ? Int16.Parse (parameter, CultureInfo.InvariantCulture): default (short); + case TypeCode.Int32: + return parameter != null ? Int32.Parse (parameter, CultureInfo.InvariantCulture): default (int); + case TypeCode.Int64: + return parameter != null ? Int64.Parse (parameter, CultureInfo.InvariantCulture): default (long); + case TypeCode.UInt16: + return parameter != null ? UInt16.Parse (parameter, CultureInfo.InvariantCulture): default (ushort); + case TypeCode.UInt32: + return parameter != null ? UInt32.Parse (parameter, CultureInfo.InvariantCulture): default (uint); + case TypeCode.UInt64: + return parameter != null ? UInt64.Parse (parameter, CultureInfo.InvariantCulture): default (ulong); + case TypeCode.DateTime: + return parameter != null ? DateTime.Parse (parameter, CultureInfo.InvariantCulture): default (DateTime); + case TypeCode.Boolean: + return parameter != null ? Boolean.Parse (parameter): default (bool); + case TypeCode.Single: + return parameter != null ? Single.Parse (parameter, CultureInfo.InvariantCulture): default (float); + case TypeCode.Double: + return parameter != null ? Double.Parse (parameter, CultureInfo.InvariantCulture): default (double); + case TypeCode.Decimal: + return parameter != null ? Decimal.Parse (parameter, CultureInfo.InvariantCulture): default (decimal); + case TypeCode.Object: + if (parameterType == typeof (TimeSpan)) + return TimeSpan.Parse (parameter); + if (parameterType == typeof (DateTimeOffset)) + return DateTimeOffset.Parse (parameter, CultureInfo.InvariantCulture); + if (parameterType == typeof (Guid)) + return new Guid (parameter); + break; + } + throw new NotSupportedException (String.Format ("Cannot convert parameter string '{0}' to parameter type '{1}'", parameter, parameterType)); + } + + public virtual string ConvertValueToString (object parameter, Type parameterType) + { + if (parameterType == null) + throw new ArgumentNullException ("parameterType"); + if (parameterType.IsValueType && parameter == null) + throw new ArgumentNullException ("parameter"); + + if (parameter == null) + return null; + + if (parameter.GetType () != parameterType) + throw new InvalidCastException (String.Format ("This QueryStringConverter does not support cast from {0} to {1}", parameter.GetType (), parameterType)); + + if (!CanConvert (parameterType)) + throw new NotSupportedException (String.Format ("Conversion from the argument parameterType '{0}' is not supported", parameterType)); + + if (parameter is IFormattable) + ((IFormattable) parameter).ToString (null, CultureInfo.InvariantCulture); + return parameter.ToString (); + } + } +} diff --git a/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebHttpDispatchOperationSelector.cs b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebHttpDispatchOperationSelector.cs new file mode 100644 index 00000000000..beec9cdd5ad --- /dev/null +++ b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebHttpDispatchOperationSelector.cs @@ -0,0 +1,106 @@ +// +// WebHttpDispatchOperationSelector.cs +// +// Author: +// Atsushi Enomoto <atsushi@ximian.com> +// +// Copyright (C) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Web; + +using TemplateTablePair = System.Collections.Generic.KeyValuePair<System.UriTemplate, object>; + +namespace System.ServiceModel.Dispatcher +{ + public class WebHttpDispatchOperationSelector : IDispatchOperationSelector + { + public const string HttpOperationSelectorUriMatchedPropertyName = "UriMatched"; + + UriTemplateTable table; + + protected WebHttpDispatchOperationSelector () + { + } + + public WebHttpDispatchOperationSelector (ServiceEndpoint endpoint) + { + if (endpoint == null) + throw new ArgumentNullException ("endpoint"); + if (endpoint.Address == null) + throw new InvalidOperationException ("EndpointAddress must be set in the argument ServiceEndpoint"); + + table = new UriTemplateTable (endpoint.Address.Uri); + + foreach (OperationDescription od in endpoint.Contract.Operations) { + WebAttributeInfo info = GetWebAttributeInfo (od); + if (info != null) + table.KeyValuePairs.Add (new TemplateTablePair (info.BuildUriTemplate (od, null), od)); + } + } + + public string SelectOperation (ref Message message) + { + bool dummy; + return SelectOperation (ref message, out dummy); + } + + protected virtual string SelectOperation (ref Message message, out bool uriMatched) + { + if (message == null) + throw new ArgumentNullException ("message"); + if (message.Properties.ContainsKey (WebBodyFormatMessageProperty.Name)) + throw new ArgumentException ("There is already message property for Web body format"); + uriMatched = false; + Uri to = message.Headers.To; + if (to == null) + return String.Empty; + + UriTemplateMatch match = table.MatchSingle (to); + OperationDescription od = null; + if (match != null) { + uriMatched = true; + foreach (TemplateTablePair p in table.KeyValuePairs) + if (p.Key == match.Template) + od = p.Value as OperationDescription; + } + return od != null ? od.Name : String.Empty; + } + + WebAttributeInfo GetWebAttributeInfo (OperationDescription od) + { + foreach (IOperationBehavior ob in od.Behaviors) { + WebAttributeInfo info = null; + var wg = ob as WebGetAttribute; + if (wg != null) + return wg.Info; + var wi = ob as WebInvokeAttribute; + if (wi != null) + return wi.Info; + } + return null; + } + } +} diff --git a/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebMessageFormatter.cs b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebMessageFormatter.cs new file mode 100644 index 00000000000..43461125fe6 --- /dev/null +++ b/mcs/class/System.ServiceModel.Web/System.ServiceModel.Dispatcher/WebMessageFormatter.cs @@ -0,0 +1,332 @@ +// +// WebMessageFormatter.cs +// +// Author: +// Atsushi Enomoto <atsushi@ximian.com> +// +// Copyright (C) 2008 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Collections.Specialized; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.ServiceModel.Dispatcher; +using System.ServiceModel.Web; +using System.Text; + +namespace System.ServiceModel.Description +{ + internal abstract class WebMessageFormatter + { + OperationDescription operation; + ServiceEndpoint endpoint; + QueryStringConverter converter; + WebHttpBehavior behavior; + UriTemplate template; + WebAttributeInfo info = null; + + public WebMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + { + this.operation = operation; + this.endpoint = endpoint; + this.converter = converter; + this.behavior = behavior; + ApplyWebAttribute (); + } + + void ApplyWebAttribute () + { + MethodInfo mi = operation.SyncMethod ?? operation.BeginMethod; + + object [] atts = mi.GetCustomAttributes (typeof (WebGetAttribute), false); + if (atts.Length > 0) + info = ((WebGetAttribute) atts [0]).Info; + atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), false); + if (atts.Length > 0) + info = ((WebInvokeAttribute) atts [0]).Info; + if (info == null) + info = new WebAttributeInfo (); + + template = info.BuildUriTemplate (Operation, GetMessageDescription (MessageDirection.Input)); + } + + public WebHttpBehavior Behavior { + get { return behavior; } + } + + public WebAttributeInfo Info { + get { return info; } + } + + public OperationDescription Operation { + get { return operation; } + } + + public QueryStringConverter Converter { + get { return converter; } + } + + public ServiceEndpoint Endpoint { + get { return endpoint; } + } + + public UriTemplate UriTemplate { + get { return template; } + } + + protected WebContentFormat ToContentFormat (WebMessageFormat src) + { + switch (src) { + case WebMessageFormat.Xml: + return WebContentFormat.Xml; + case WebMessageFormat.Json: + return WebContentFormat.Json; + } + throw new SystemException ("INTERNAL ERROR: should not happen"); + } + + protected void CheckMessageVersion (MessageVersion messageVersion) + { + if (messageVersion == null) + throw new ArgumentNullException ("messageVersion"); + + if (!MessageVersion.None.Equals (messageVersion)) + throw new ArgumentException ("Only MessageVersion.None is supported"); + } + + protected MessageDescription GetMessageDescription (MessageDirection dir) + { + foreach (MessageDescription md in operation.Messages) + if (md.Direction == dir) + return md; + throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir); + } + + internal class RequestClientFormatter : WebClientMessageFormatter + { + public RequestClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + } + + internal class ReplyClientFormatter : WebClientMessageFormatter + { + public ReplyClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + } + + internal class RequestDispatchFormatter : WebDispatchMessageFormatter + { + public RequestDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + } + + internal class ReplyDispatchFormatter : WebDispatchMessageFormatter + { + public ReplyDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + } + + internal abstract class WebClientMessageFormatter : WebMessageFormatter, IClientMessageFormatter + { + protected WebClientMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + + public Message SerializeRequest (MessageVersion messageVersion, object [] parameters) + { + if (parameters == null) + throw new ArgumentNullException ("parameters"); + CheckMessageVersion (messageVersion); + + var c = new NameValueCollection (); + + MessageDescription md = GetMessageDescription (MessageDirection.Input); + + if (parameters.Length != md.Body.Parts.Count) + throw new ArgumentException ("Parameter array length does not match the number of message body parts"); + + for (int i = 0; i < parameters.Length; i++) { + var p = md.Body.Parts [i]; + string name = p.Name.ToUpperInvariant (); + if (UriTemplate.PathSegmentVariableNames.Contains (name) || + UriTemplate.QueryValueVariableNames.Contains (name)) + c.Add (name, parameters [i] != null ? Converter.ConvertValueToString (parameters [i], parameters [i].GetType ()) : null); + else + // FIXME: bind as a message part + 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)); + } + + Uri to = UriTemplate.BindByName (Endpoint.Address.Uri, c); + + Message ret = Message.CreateMessage (messageVersion, (string) null); + ret.Headers.To = to; + + var hp = new HttpRequestMessageProperty (); + hp.Method = Info.Method; + // FIXME: set hp.SuppressEntityBody for some cases. + ret.Properties.Add (HttpRequestMessageProperty.Name, hp); + + var wp = new WebBodyFormatMessageProperty (ToContentFormat (Info.IsRequestFormatSetExplicitly ? Info.RequestFormat : Behavior.DefaultOutgoingRequestFormat)); + ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp); + + return ret; + } + + public object DeserializeReply (Message message, object [] parameters) + { + if (parameters == null) + throw new ArgumentNullException ("parameters"); + CheckMessageVersion (message.Version); + + string pname = WebBodyFormatMessageProperty.Name; + if (!message.Properties.ContainsKey (pname)) + throw new SystemException ("INTERNAL ERROR: it expects WebBodyFormatMessageProperty existence"); + var wp = (WebBodyFormatMessageProperty) message.Properties [pname]; + MessageDescription md = GetMessageDescription (MessageDirection.Output); + + XmlObjectSerializer serializer = null; + switch (wp.Format) { + case WebContentFormat.Xml: + serializer = new DataContractSerializer (md.Body.ReturnValue.Type); + break; + case WebContentFormat.Json: + serializer = new DataContractJsonSerializer (md.Body.ReturnValue.Type); + break; + case WebContentFormat.Raw: + default: + throw new NotImplementedException (); + } + + // FIXME: handle ref/out parameters + + return serializer.ReadObject (message.GetReaderAtBodyContents (), false); + } + } + + internal abstract class WebDispatchMessageFormatter : WebMessageFormatter, IDispatchMessageFormatter + { + protected WebDispatchMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior) + : base (operation, endpoint, converter, behavior) + { + } + + public Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result) + { + try { + return SerializeReplyCore (messageVersion, parameters, result); + } finally { + if (WebOperationContext.Current != null) + OperationContext.Current.Extensions.Remove (WebOperationContext.Current); + } + } + + Message SerializeReplyCore (MessageVersion messageVersion, object [] parameters, object result) + { + if (parameters == null) + throw new ArgumentNullException ("parameters"); + CheckMessageVersion (messageVersion); + + MessageDescription md = GetMessageDescription (MessageDirection.Output); + + // FIXME: use them. + // var dcob = Operation.Behaviors.Find<DataContractSerializerOperationBehavior> (); + // XmlObjectSerializer xos = dcob.CreateSerializer (result.GetType (), md.Body.WrapperName, md.Body.WrapperNamespace, null); + // var xsob = Operation.Behaviors.Find<XmlSerializerOperationBehavior> (); + // XmlSerializer [] serializers = XmlSerializer.FromMappings (xsob.GetXmlMappings ().ToArray ()); + + WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat; + + string mediaType = null; + XmlObjectSerializer serializer = null; + switch (msgfmt) { + case WebMessageFormat.Xml: + serializer = new DataContractSerializer (md.Body.ReturnValue.Type); + mediaType = "application/xml"; + break; + case WebMessageFormat.Json: + serializer = new DataContractJsonSerializer (md.Body.ReturnValue.Type); + mediaType = "application/json"; + break; + } + + // FIXME: serialize ref/out parameters as well. + + Message ret = Message.CreateMessage (MessageVersion.None, null, result, serializer); + + // Message properties + + var hp = new HttpResponseMessageProperty (); + // FIXME: get encoding from somewhere + hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8"; + + // apply user-customized HTTP results via WebOperationContext. + WebOperationContext.Current.OutgoingResponse.Apply (hp); + + // FIXME: fill some properties if required. + ret.Properties.Add (HttpResponseMessageProperty.Name, hp); + + var wp = new WebBodyFormatMessageProperty (ToContentFormat (msgfmt)); + ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp); + + return ret; + } + + public void DeserializeRequest (Message message, object [] parameters) + { + if (parameters == null) + throw new ArgumentNullException ("parameters"); + CheckMessageVersion (message.Version); + + OperationContext.Current.Extensions.Add (new WebOperationContext (OperationContext.Current)); + + IncomingWebRequestContext iwc = WebOperationContext.Current.IncomingRequest; + + Uri to = message.Headers.To; + UriTemplateMatch match = UriTemplate.Match (Endpoint.Address.Uri, to); + if (match == null) + // not sure if it could happen + throw new SystemException (String.Format ("INTERNAL ERROR: UriTemplate does not match with the request: {0} / {1}", UriTemplate, to)); + iwc.UriTemplateMatch = match; + + MessageDescription md = GetMessageDescription (MessageDirection.Input); + + for (int i = 0; i < parameters.Length; i++) { + var p = md.Body.Parts [i]; + string name = p.Name.ToUpperInvariant (); + parameters [i] = match.BoundVariables [name]; + } + } + } + } +} |