a Soap invocation.
The result is still not handled though.
2003-05-29 Miguel de Icaza <miguel@ximian.com>
* SoapHttpClientProtocol.cs (CreateMessage): Kill. Move
functionality to Invoke.
(Invoke): Use new TypeStubInfo/MethodStubInfo instead.
* SoapClientMessage.cs: Drop old mechanism, use MethodStubInfo instead.
* Methods.cs: New file. Contains the managed for TypeStubs and
MethodInfoStubs.
A MethodInfoStub contains the serializers we use for the SOAP
request.
* SoapClientMessage.cs: Drop parameters from the clientmessage, it
does not belong here. Drop oneway, we canextract that from the
SoapDocumentMethodAttribute class that we pass.
svn path=/trunk/mcs/; revision=15003
+2003-05-29 Miguel de Icaza <miguel@ximian.com>
+
+ * SoapHttpClientProtocol.cs (CreateMessage): Kill. Move
+ functionality to Invoke.
+
+ (Invoke): Use new TypeStubInfo/MethodStubInfo instead.
+
+ * SoapClientMessage.cs: Drop old mechanism, use MethodStubInfo instead.
+
+ * Methods.cs: New file. Contains the managed for TypeStubs and
+ MethodInfoStubs.
+
+ A MethodInfoStub contains the serializers we use for the SOAP
+ request.
+
+ * SoapClientMessage.cs: Drop parameters from the clientmessage, it
+ does not belong here. Drop oneway, we canextract that from the
+ SoapDocumentMethodAttribute class that we pass.
+
2003-05-28 Miguel de Icaza <miguel@ximian.com>
* LogicalMethodInfo.cs (Create): Implement begin/end method
--- /dev/null
+//
+// Methods.cs: Information about a method and its mapping to a SOAP web service.
+//
+// Author:
+// Miguel de Icaza
+//
+// (C) 2003 Ximian, Inc.
+//
+//
+
+using System.Reflection;
+using System.Collections;
+using System.Xml.Serialization;
+using System.Web.Services;
+using System.Web.Services.Description;
+
+namespace System.Web.Services.Protocols {
+
+ //
+ // This class represents all the information we extract from a MethodInfo
+ // in the SoapHttpClientProtocol derivative stub class
+ //
+ internal class MethodStubInfo {
+ internal LogicalMethodInfo MethodInfo;
+
+ // The name used bythe stub class to reference this method.
+ internal string Name;
+
+ internal string Action;
+ internal string Binding;
+
+ // The name/namespace of the request
+ internal string RequestName;
+ internal string RequestNamespace;
+
+ // The name/namespace of the response.
+ internal string ResponseName;
+ internal string ResponseNamespace;
+
+ internal bool OneWay;
+ internal SoapParameterStyle ParameterStyle;
+ internal SoapBindingUse Use;
+
+ internal XmlSerializer RequestSerializer;
+ internal XmlSerializer ResponseSerializer;
+
+ //
+ // Constructor
+ //
+ MethodStubInfo (TypeStubInfo parent, LogicalMethodInfo source, SoapDocumentMethodAttribute dma, XmlReflectionImporter importer)
+ {
+ MethodInfo = source;
+
+ Use = dma.Use;
+ if (Use == SoapBindingUse.Default)
+ Use = parent.Use;
+ if (Use != SoapBindingUse.Literal)
+ throw new Exception ("Only SoapBindingUse.Literal supported");
+
+ OneWay = dma.OneWay;
+ if (OneWay){
+ if (source.ReturnType != typeof (void))
+ throw new Exception ("OneWay methods should not have a return value");
+ if (source.OutParameters.Length != 0)
+ throw new Exception ("OneWay methods should not have out/ref parameters");
+ }
+
+ Action = dma.Action;
+ Binding = (dma.Binding == "" ? parent.BindingName : dma.Binding);
+ RequestName = dma.RequestElementName;
+ if (RequestName == "")
+ RequestName = source.Name;
+
+ RequestNamespace = dma.RequestNamespace;
+ ResponseName = dma.ResponseElementName;
+ ResponseNamespace = dma.ResponseNamespace;
+ ParameterStyle = dma.ParameterStyle;
+ if (ParameterStyle == SoapParameterStyle.Default)
+ ParameterStyle = parent.ParameterStyle;
+
+ object [] o = source.GetCustomAttributes (typeof (WebMethodAttribute));
+ if (o.Length == 1){
+ WebMethodAttribute wma = (WebMethodAttribute) o [0];
+
+ Name = wma.MessageName;
+ if (Name == "")
+ Name = source.Name;
+ } else
+ Name = source.Name;
+
+ if (ResponseName == "")
+ ResponseName = Name + "Response";
+
+ MakeRequestSerializer (importer);
+ MakeResponseSerializer (importer);
+ }
+
+ static internal MethodStubInfo Create (TypeStubInfo parent, LogicalMethodInfo lmi, XmlReflectionImporter importer)
+ {
+ object [] o = lmi.GetCustomAttributes (typeof (SoapDocumentMethodAttribute));
+ if (o.Length == 0){
+ lmi.GetCustomAttributes (typeof (SoapRpcMethodAttribute));
+ if (o.Length != 0)
+ throw new Exception ("SoapRpcMethod not supported, only SoapDocumentMethod");
+ return null;
+ }
+
+ return new MethodStubInfo (parent, lmi, (SoapDocumentMethodAttribute) o [0], importer);
+ }
+
+ void MakeRequestSerializer (XmlReflectionImporter importer)
+ {
+ ParameterInfo [] input = MethodInfo.InParameters;
+ XmlReflectionMember [] in_members = new XmlReflectionMember [input.Length];
+
+ for (int i = 0; i < input.Length; i++){
+ XmlReflectionMember m = new XmlReflectionMember ();
+ m.IsReturnValue = false;
+ m.MemberName = input [i].Name;
+ m.MemberType = input [i].ParameterType;
+
+ in_members [i] = m;
+ }
+
+ TypeStubInfo.SingleMembersMapping [0] =
+ importer.ImportMembersMapping (RequestName, RequestNamespace, in_members, true);
+ XmlSerializer [] s = XmlSerializer.FromMappings (TypeStubInfo.SingleMembersMapping);
+ RequestSerializer = s [0];
+ }
+
+ void MakeResponseSerializer (XmlReflectionImporter importer)
+ {
+ ParameterInfo [] output = MethodInfo.OutParameters;
+ XmlReflectionMember [] out_members = new XmlReflectionMember [(OneWay ? 0 : 1) + output.Length];
+ XmlReflectionMember m;
+ int idx = 0;
+
+ if (!OneWay){
+ m = new XmlReflectionMember ();
+ m.IsReturnValue = true;
+ m.MemberName = RequestName + "Result";
+ m.MemberType = MethodInfo.ReturnType;
+ idx++;
+ out_members [0] = m;
+ }
+
+ for (int i = 0; i < output.Length; i++){
+ m = new XmlReflectionMember ();
+ m.IsReturnValue = false;
+ m.MemberName = output [i].Name;
+ m.MemberType = output [i].ParameterType;
+
+ if (m.MemberType.IsByRef)
+ m.MemberType = m.MemberType.GetElementType ();
+ out_members [i + idx] = m;
+ }
+
+ TypeStubInfo.SingleMembersMapping [0] =
+ importer.ImportMembersMapping (RequestName, RequestNamespace, out_members, true);
+ XmlSerializer [] s = XmlSerializer.FromMappings (TypeStubInfo.SingleMembersMapping);
+ ResponseSerializer = s [0];
+ }
+ }
+
+ //
+ // Holds the metadata loaded from the type stub, as well as
+ // the metadata for all the methods in the type
+ //
+ internal class TypeStubInfo {
+ Hashtable name_to_method = new Hashtable ();
+
+ // Precomputed
+ internal SoapParameterStyle ParameterStyle;
+ internal SoapServiceRoutingStyle RoutingStyle;
+ internal SoapBindingUse Use;
+ internal string WebServiceName;
+ internal string WebServiceNamespace;
+ internal string BindingLocation;
+ internal string BindingName;
+ internal string BindingNamespace;
+
+ // We only use this to avoid creating tons of arrays that are barely used.
+ internal static XmlMembersMapping [] SingleMembersMapping = new XmlMembersMapping [1];
+
+ void GetTypeAttributes (Type t)
+ {
+ object [] o;
+
+ o = t.GetCustomAttributes (typeof (WebServiceBindingAttribute), false);
+ if (o.Length != 1)
+ throw new Exception ("Expected WebServiceBindingAttribute on "+ t.Name);
+ WebServiceBindingAttribute b = (WebServiceBindingAttribute) o [0];
+ BindingLocation = b.Location;
+ BindingName = b.Name;
+ BindingNamespace = b.Namespace;
+
+ o = t.GetCustomAttributes (typeof (WebService), false);
+ if (o.Length == 1){
+ WebServiceAttribute a = (WebServiceAttribute) o [0];
+
+ WebServiceName = a.Name;
+ WebServiceNamespace = a.Namespace;
+ } else {
+ WebServiceName = t.Name;
+ WebServiceNamespace = WebServiceAttribute.DefaultNamespace;
+ }
+
+ o = t.GetCustomAttributes (typeof (SoapDocumentServiceAttribute), false);
+ if (o.Length == 1){
+ SoapDocumentServiceAttribute a = (SoapDocumentServiceAttribute) o [0];
+
+ ParameterStyle = a.ParameterStyle;
+ RoutingStyle = a.RoutingStyle;
+ Use = a.Use;
+ } else {
+ ParameterStyle = SoapParameterStyle.Wrapped;
+ RoutingStyle = SoapServiceRoutingStyle.SoapAction;
+ Use = SoapBindingUse.Literal;
+ }
+
+ //
+ // Some validation
+ //
+ o = t.GetCustomAttributes (typeof (SoapRpcServiceAttribute), false);
+ if (o.Length != 0)
+ throw new Exception ("We do not support SoapRpcService encoding, Type=" + t.Name);
+ }
+
+ //
+ // Extract all method information
+ //
+ void GetTypeMethods (Type t, XmlReflectionImporter importer)
+ {
+ MethodInfo [] type_methods = t.GetMethods (BindingFlags.Instance | BindingFlags.Public);
+ LogicalMethodInfo [] methods = LogicalMethodInfo.Create (type_methods, LogicalMethodTypes.Sync);
+
+ foreach (LogicalMethodInfo mi in methods){
+ MethodStubInfo msi = MethodStubInfo.Create (this, mi, importer);
+
+ if (msi == null)
+ continue;
+
+ name_to_method [msi.Name] = msi;
+ }
+ }
+
+ internal TypeStubInfo (Type t)
+ {
+ GetTypeAttributes (t);
+
+ XmlReflectionImporter importer = new XmlReflectionImporter ();
+ GetTypeMethods (t, importer);
+ }
+
+ internal MethodStubInfo GetMethod (string name)
+ {
+ return (MethodStubInfo) name_to_method [name];
+ }
+ }
+
+ //
+ // Manages
+ //
+ internal class TypeStubManager {
+ static Hashtable type_to_manager;
+
+ static TypeStubManager ()
+ {
+ type_to_manager = new Hashtable ();
+ }
+
+ //
+ // This needs to be thread safe
+ //
+ static internal TypeStubInfo GetTypeStub (Type t)
+ {
+ TypeStubInfo tm = (TypeStubInfo) type_to_manager [t];
+
+ if (tm != null)
+ return tm;
+
+ lock (typeof (TypeStubInfo)){
+ tm = (TypeStubInfo) type_to_manager [t];
+
+ if (tm != null)
+ return tm;
+
+ tm = new TypeStubInfo (t);
+ type_to_manager [t] = tm;
+
+ return tm;
+ }
+ }
+ }
+}
Url property of it (to point to the right server) and then
invoking the stubs.
- The stub will call SoapHttpClientProtocol.Invoke. Our
- implementation walks the stack to find the invoker and extract
- the attributes that control the processing
-
SoapHttpClientProtocol will create a SoapClientMessage based
on this information. The SoapClientMessage class is used to
pass the information to hooks or extensions in the .NET
* Metadata
- We need to extract various pieces of information from a given
- MethodInfo. Currently this is done in CreateMessage, but it
- should be moved elsewhere as there is plenty more that we will
- need to extract, and we are currently just passing all that
- information to SoapClientMessage as internal data.
-
- This new structure will also keep a cache of the
- XmlSerializers that we need for encoding the arguments.
+ Methods.cs extracts class and method information: it basically
+ pulls all the attributes that can be applied to the class and
+ methods, and stores them into TypeStubInfo and MethodStubInfo.
- This should contain information extracted from the method
- attributes, at least the following play a role:
+ Also, serializers for input and output types are created and
+ stored into the MethodStubInfo.
- SoapDocumentMethodAttribute
- WebMethodAttribute
+ There is a cache managed by TypeStubManager (it has to be
+ threadsafe, as SoapHttpClientProtocol will call this and will
+ require thread safe semantics).
+ The cache tracks types, and types track their methods. This
+ information needs to be computed ahead of time, due to the
+ possible name-clash resolution that VisualStudio uses.
* Current shortcomings and problems.
- * Need to find a list of all attributes that apply to a
- method.
-
* Need a cache that maps (type, method-name) to the
precomputed MethodMetadata. The type has to be a derived
class from SoapHttpClientProtocol.
- * Since we know that WebService names are *unique* we can
- pull the MethodMetadata based on this key, so it would be:
-
- (type, unique-name) -> MethodMetadata
+ * We do not support SoapExtensions.
- * We probably need to compute all the MethodMetadata structs
- in one pass for a type, as the unique-name can not be
- discovered unless we do a query on all the attributes
- attached to methods (see the sample at the end)
+ * We do not support extracting the parameter information and
+ pass the result attribute names to the XmlSerializer, as specified in
+ `Customizing SOAP Messages/Customizing the SOAP Message with XML Serialization'
+ * We do not pass the SoapClientMessage as we should to any
+ extensions
* Other notes.
SoapHttpClientProtocol client;\r
string url;\r
LogicalMethodInfo client_method;\r
- SoapDocumentMethodAttribute sma;\r
- object [] parameters;\r
+ internal MethodStubInfo MethodStubInfo;\r
+\r
+ //\r
+ // Expose this one internally\r
+ //\r
+ internal object [] Parameters;\r
#endregion\r
\r
#region Constructors\r
//\r
// Constructs the SoapClientMessage\r
//\r
- internal SoapClientMessage (SoapHttpClientProtocol client, SoapDocumentMethodAttribute sma,\r
- LogicalMethodInfo client_method, bool one_way, string url, object [] parameters)\r
+ internal SoapClientMessage (SoapHttpClientProtocol client, MethodStubInfo msi, string url, object [] parameters)\r
{\r
- this.sma = sma;\r
+ this.MethodStubInfo = msi;\r
this.client = client;\r
this.client_method = client_method;\r
this.url = url;\r
- this.parameters = parameters;\r
+ Parameters = parameters;\r
}\r
\r
#endregion \r
#region Properties\r
\r
public override string Action {\r
- get { return sma.Action; }\r
+ get { return MethodStubInfo.Action; }\r
}\r
\r
public SoapHttpClientProtocol Client {\r
}\r
\r
public override bool OneWay {\r
- get { return sma.OneWay; }\r
+ get { return MethodStubInfo.OneWay; }\r
}\r
\r
public override string Url {\r
\r
namespace System.Web.Services.Protocols {\r
public class SoapHttpClientProtocol : HttpWebClientProtocol {\r
-\r
+ TypeStubInfo type_info;\r
+ \r
#region Constructors\r
\r
public SoapHttpClientProtocol () \r
{\r
+ type_info = TypeStubManager.GetTypeStub (this.GetType ());\r
}\r
- \r
+\r
#endregion // Constructors\r
\r
#region Methods\r
}\r
}\r
\r
- //\r
- // The `method_name' should be the name of our invoker, this is only used\r
- // for sanity checks, nothing else\r
- //\r
- [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
- MethodInfo GetCallerMethod (string method_name)\r
- {\r
- MethodInfo mi;\r
-#if StackFrameWorks\r
- StackFrame stack_trace = new StackFrame (5, false);\r
- mi = (MethodInfo) stack_frame.GetMethod ();\r
-\r
-#else\r
- //\r
- // Temporary hack: look for a type which is not this type\r
- //\r
- StackTrace st = new StackTrace ();\r
- mi = null;\r
- for (int i = 0; i < st.FrameCount; i++){\r
- StackFrame sf = st.GetFrame (i);\r
- mi = (MethodInfo) sf.GetMethod ();\r
- if (mi.DeclaringType != typeof (SoapHttpClientProtocol))\r
- break;\r
- }\r
-#endif\r
- //\r
- // A few sanity checks, just in case the code moves around later\r
- //\r
- if (!mi.DeclaringType.IsSubclassOf (typeof (System.Web.Services.Protocols.SoapHttpClientProtocol)))\r
- throw new Exception ("We are pointing to the wrong method (T=" + mi.DeclaringType + ")");\r
-\r
- if (mi.DeclaringType == typeof (System.Web.Services.Protocols.SoapHttpClientProtocol))\r
- throw new Exception ("We are pointing to the wrong method (we are pointing to our Invoke)");\r
-\r
- if (mi.Name != method_name)\r
- throw new Exception ("The method we point to is: " + mi.Name);\r
-\r
- return mi;\r
- }\r
- \r
- [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
- SoapClientMessage CreateMessage (string method_name, object [] parameters)\r
- {\r
- MethodInfo mi = GetCallerMethod (method_name);\r
- object [] attributes = mi.GetCustomAttributes (typeof (System.Web.Services.Protocols.SoapDocumentMethodAttribute), false);\r
- SoapDocumentMethodAttribute sma = (SoapDocumentMethodAttribute) attributes [0];\r
-\r
- Console.WriteLine ("SMAA: " + sma.Action);\r
- Console.WriteLine ("Binding: " + sma.Binding);\r
- Console.WriteLine ("OneWay: " + sma.OneWay);\r
- Console.WriteLine ("PStyle: " + sma.ParameterStyle);\r
- Console.WriteLine ("REN: " + sma.RequestElementName);\r
- Console.WriteLine ("REN: " + sma.RequestElementName);\r
-\r
- if (sma.Use != SoapBindingUse.Literal)\r
- throw new Exception ("Soap Section 5 Encoding not supported");\r
- \r
- SoapClientMessage message = new SoapClientMessage (\r
- this, sma, new LogicalMethodInfo (mi), sma.OneWay, Url, parameters);\r
-\r
- return message;\r
- }\r
-\r
const string soap_envelope = "http://schemas.xmlsoap.org/soap/envelope/";\r
\r
void WriteSoapEnvelope (XmlTextWriter xtw)\r
xtw.WriteAttributeString ("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");\r
}\r
\r
- void SendMessage (WebRequest request, SoapClientMessage message)\r
+ void SendRequest (WebRequest request, SoapClientMessage message)\r
{\r
WebHeaderCollection headers = request.Headers;\r
headers.Add ("SOAPAction", message.Action);\r
using (Stream s = request.GetRequestStream ()){\r
// serialize arguments\r
\r
- XmlTextWriter xtw = new XmlTextWriter (s, Encoding.UTF8);\r
+ // What a waste of UTF8encoders, but it has to be thread safe.\r
+ \r
+ XmlTextWriter xtw = new XmlTextWriter (s, new UTF8Encoding (false));\r
+ xtw.Formatting = Formatting.Indented;\r
WriteSoapEnvelope (xtw);\r
xtw.WriteStartElement ("soap", "Body", soap_envelope);\r
- \r
- // Serialize arguments here\r
- \r
+\r
+ // Serialize arguments.\r
+ message.MethodStubInfo.RequestSerializer.Serialize (xtw, message.Parameters);\r
xtw.WriteEndElement ();\r
xtw.WriteEndElement ();\r
+ \r
xtw.Flush ();\r
xtw.Close ();\r
}\r
}\r
+\r
+ void GetContentTypeProperties (string cts, out string encoding, out string content_type)\r
+ {\r
+ encoding = "utf-8";\r
+ int start = 0;\r
+ int idx = cts.IndexOf (';');\r
+ if (idx == -1)\r
+ encoding = cts;\r
+ content_type = cts.Substring (0, idx);\r
+ for (start = idx + 1; idx != -1;){\r
+ idx = cts.IndexOf (";", start);\r
+ string body;\r
+ if (idx == -1)\r
+ body = cts.Substring (start);\r
+ else {\r
+ body = cts.Substring (start, idx);\r
+ start = idx + 1;\r
+ }\r
+ if (body.StartsWith ("charset=")){\r
+ encoding = body.Substring (8);\r
+ }\r
+ }\r
+ }\r
\r
- [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
- protected object[] Invoke (string method_name, object[] parameters)\r
+ //\r
+ // TODO:\r
+ // Handle other web responses (multi-output?)\r
+ // \r
+ object [] ReceiveResponse (WebResponse response, SoapClientMessage message)\r
{\r
- SoapClientMessage message = CreateMessage (method_name, parameters);\r
- WebRequest request = GetWebRequest (uri);\r
+ HttpWebResponse http_response = (HttpWebResponse) response;\r
+ HttpStatusCode code = http_response.StatusCode;\r
+ MethodStubInfo msi = message.MethodStubInfo;\r
\r
- SendMessage (request, message);\r
+ if (!(code == HttpStatusCode.Accepted || code == HttpStatusCode.OK))\r
+ throw new Exception ("Return code was: " + http_response.StatusCode);\r
+\r
+ //\r
+ // Remove optional encoding\r
+ //\r
+ string content_type, encoding_name;\r
+ GetContentTypeProperties (response.ContentType, out encoding_name, out content_type);\r
+\r
+ if (content_type != "text/xml")\r
+ throw new Exception ("Return value is not XML: " + content_type);\r
+\r
+ Encoding encoding = Encoding.GetEncoding (encoding_name);\r
+ Stream stream = response.GetResponseStream ();\r
+ StreamReader reader = new StreamReader (stream, encoding, false);\r
+ XmlTextReader xml_reader = new XmlTextReader (reader);\r
+\r
+ //\r
+ // Handle faults somewhere\r
+ //\r
+ xml_reader.MoveToContent ();\r
+ xml_reader.ReadStartElement ("Envelope", soap_envelope);\r
+ xml_reader.MoveToContent ();\r
+ xml_reader.ReadStartElement ("Body", soap_envelope);\r
+\r
+ xml_reader.MoveToContent ();\r
+ xml_reader.ReadStartElement (msi.ResponseName, msi.ResponseNamespace);\r
+\r
+ object [] ret = (object []) msi.ResponseSerializer.Deserialize (xml_reader);\r
+\r
+ Console.WriteLine ("Returned array elements: " + ret.Length);\r
+ for (int i = 0; i < ret.Length; i++){\r
+ Console.WriteLine ("Value-{0}: {1}", i, ret [i]);\r
+ }\r
+ return (object []) ret;\r
+ }\r
+ \r
+ protected object[] Invoke (string method_name, object[] parameters)\r
+ {\r
+ MethodStubInfo msi = type_info.GetMethod (method_name);\r
\r
- return null;\r
+ SoapClientMessage message = new SoapClientMessage (\r
+ this, msi, Url, parameters);\r
+\r
+ WebRequest request = GetWebRequest (uri);\r
+ SendRequest (request, message);\r
+\r
+ WebResponse response = request.GetResponse ();\r
+ return ReceiveResponse (response, message);\r
}\r
\r
#endregion // Methods\r
System.Web.Services.Description/SoapProtocolReflector.cs
System.Web.Services.Description/SoapTransportImporter.cs
System.Web.Services.Description/Types.cs
+System.Web.Services.Protocols/Methods.cs
System.Web.Services.Protocols/AnyReturnReader.cs
System.Web.Services.Protocols/HtmlFormParameterReader.cs
System.Web.Services.Protocols/HtmlFormParameterWriter.cs