WebService proxy bug?: "Web Service method name is not valid."
Note: this entry has moved.
Let's suppose you create a proxy for a webservice. Assume that you KNOW that only certain
(signed) code should use your proxy, then, it's only natural that you add a
StrongNameIdentityPermission
to its declaration:
[StrongNameIdentityPermission(SecurityAction.LinkDemand,PublicKey="your_trusted_key")]
public class MyWebService : SoapHttpClientProtocol
When you try to call any method with such a proxy, you'll get a pretty strange error:
[your_webmethod] Web Service method name is not valid.
It turns out to be that because of the permission, the internal classes that
reflect your type can't get the methods in the class. And when the method to
call is looked-up in an internal list and not found, that exception is thrown.
However, there's nothing wrong with the method name. The method that throws the
exception is SoapHttpClientProtocol.BeforeSerialize
, which
performs the lookup against a private instance of the type SoapClientType
.
The proxy initialization code involves the following steps before that failure:
-
Proxy constructor initializes a
SoapClientType
: it passes the current proxy type so that it is reflected to get the web methods. -
SoapClientType
constructor callsLogicalMethodInfo.Create
passing all the proxy public methods, retrieved usingType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
. This method checks for asynchronous methods and does some other trivial work. -
SoapClientType
constructor callsSoapReflector.ReflectMethod
with eachLogicalMethodInfo
constructed in the previous step.
-
SoapClientType
constructor: it works as expected, but its internal list of methods available is empty when the constructor is run without the required public key:Note that I access a private field calledType t = ReflexHelper.LoadPrivateType( "System.Web.Services.Protocols.SoapClientType, System.Web.Services"); object sct = Activator.CreateInstance( t, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { typeof(Messaging.MessagingWebService) }, null, null); FieldInfo fi = t.GetField("methods", BindingFlags.Instance | BindingFlags.NonPublic); Hashtable methods = fi.GetValue(sct) as Hashtable;
methods
which contains the list loaded by the constructor code.
I went deeper as themethods
hastable I got was always empty when I run the unsigned version. The following method is called inside this constructor, and is the next hot-spot. -
LogicalMethodInfo.Create
: it correctly returns the list of methods irrespective of the signature of the calling code. Therefore, the bug isn't here:So I went further to the call onMethodInfo[] mis = typeof(MyWebService).GetMethods( BindingFlags.Instance | BindingFlags.Public); LogicalMethodInfo[] li = LogicalMethodInfo.Create(mis, LogicalMethodTypes.Sync);
SoapReflector
. -
SoapReflector.ReflectMethod
: I couldn't go any further than this because the dependecies are too deep with other private and internal types. However, this method IS the one to blame for the strange error. It fails to reflect the methods on the type because of theStrongNameIdentityPermission
, but does not throw any exceptions, thus swallowing the fact that it's a security permission that is failing, instead of the unrelated exception thrown at the moment we actually perform the WebMethod call on the proxy.
Here's the code that proves it:Signing this test console project with the public key required by the proxy results in the correct list of webmethods being loaded into theArrayList list = new ArrayList(); t = ReflexHelper.LoadPrivateType( "System.Web.Services.Protocols.SoapReflector, System.Web.Services"); MethodInfo reflect = t.GetMethod("ReflectMethod", BindingFlags.Static | BindingFlags.NonPublic); foreach (LogicalMethodInfo i in li) { //This always returns null if we're not signed with the key required //(always the case for System.Web.Services.dll) object m = reflect.Invoke( null, BindingFlags.Static | BindingFlags.NonPublic, null, new object[] { i, true, new XmlReflectionImporter(), new SoapReflectionImporter(), "default-namespace" }, null); if (m != null) list.Add(m); } Console.WriteLine(list.Count);
list
object. The object returned by theReflectMethod
is checked in theSoapClientType
constructor, but only for non-null returns. If the returned object is null, nothing is done and the internal list of available methods will silently remain empty.