How to Divine What Your Generated ASMX Web Service Proxy Will Be Named

Not long ago, I posted "Why Should You Care About Interfaces in ASMX? Because It Provides Versioning." In that post, I highlighted that the generated proxy will use the binding name.

Ron Young emailed me to point out that this does not occur as expected when the web service contains only a single interface. Thanks for the feedback, Ron.  You're right, the behavior is inconsistent. 

Let's start with a simple ASMX service.

 using System;
using System.Web.Services;

[WebService(Namespace = "https://demo")]
public class ClassName : WebService
{    
    [WebMethod]
    public string HelloWorld() 
    {
        return "Hello World";
    }
}

If you use the Add Web Reference dialog or wsdl.exe to generate a proxy, the resulting SoapHttpClientProtocol proxy class will use the same name as the System.Web.Services.WebService class itself. This is because both the Add Web Reference dialog and the wsdl.exe tool infer the name from the wsdl:service name.

 <wsdl:service name="ClassName">
    <wsdl:port name="ClassNameSoap" binding="tns:ClassNameSoap">
        <soap:address location="https://localhost:1384/WebSite1/Service.asmx" />
    </wsdl:port>
</wsdl:service>

We can affect the WSDL by modifying the Name property of the System.Web.Services.WebServiceAttribute.  We make a slight change to the service.

 using System;
using System.Web.Services;

[WebService(Namespace = "https://demo", 
    Name="WebServiceAttributeName")]
public class ClassName : WebService
{    
    [WebMethod]
    public string HelloWorld() 
    {
        return "Hello World";
    }
}

The generated proxy now uses the name we specified in the WebServiceAttribute.

 localhost.WebServiceAtrributeName proxy = new localhost.WebServiceAttributeName();

This makes sense: we don't want a way to control the service name in the WSDL, and WebServiceAttribute provides this mechanism.  However, let's get back to my previous the use of interfaces. In my post, I noted the obvious benefit of naming the service is that it will use the binding name if you implement 2 interfaces on the service.

Let's look at another example to clarify that last point.

 using System;
using System.Web.Services;

[WebServiceBinding(
    Name = "BindingName",
    Namespace = "https://demo")]
interface InterfaceName
{
    [WebMethod]
    string HelloWorld();
}

[WebService(Namespace = "https://demo", 
    Name="WebServiceAttributeName")]
public class ClassName : WebService, InterfaceName
{        
    public string HelloWorld() 
    {
        return "Hello World";
    }
}

When you generate a proxy from this service, the proxy type name will be "WebServiceAttributeName". Again, the proxy generator inspects the WSDL and chooses the wsdl:service name.

 <wsdl:service name="WebServiceAttributeName">
    <wsdl:port name="BindingName" binding="tns:BindingName">
      <soap:address location="https://localhost:1384/WebSite1/Service.asmx" />
    </wsdl:port>
  </wsdl:service>

But what about if we implement a second interface? That is, we change the code to implement two interfaces.

 using System;
using System.Web.Services;

[WebServiceBinding(
    Name = "BindingName",
    Namespace = "https://demo")]
interface InterfaceName
{
    [WebMethod]
    string HelloWorld();
}

[WebServiceBinding(
    Name = "Binding2Name",
    Namespace = "https://demo")]
interface InterfaceName2
{
    [WebMethod]
    string GoodbyeWorld();
}

[WebService(Namespace = "https://demo", 
    Name="WebServiceAttributeName")]
public class ClassName : WebService, InterfaceName, InterfaceName2
{        
    public string HelloWorld() 
    {
        return "Hello World";
    }

    public string GoodbyeWorld()
    {
        return "Goodbye World";
    }
}

The generation code now has to make a choice because we now have two bindings to choose from.

   <wsdl:service name="WebServiceAttributeName">
    <wsdl:port name="Binding2Name" binding="tns:Binding2Name">
      <soap:address location="https://localhost:1384/WebSite1/Service.asmx" />
    </wsdl:port>
    <wsdl:port name="BindingName" binding="tns:BindingName">
      <soap:address location="https://localhost:1384/WebSite1/Service.asmx" />
    </wsdl:port>
  </wsdl:service>

The result is that now our code will use the binding names for the two generated proxy types. Our code will now look like:

 localhost.BindingName s = new localhost.BindingName();
Console.WriteLine(s.HelloWorld());

The impact here should be obvious. When you implement a single interface, the WebServiceAttribute.Name is used for the proxy type name, but when you implement more than one interface, the WebServiceBindingAttribute.Name is used.  This name becomes important so that you do not break existing clients, as does the namespace.  But you also need to consider the change to the wsdl:port, which changes when you implement a class, a single interface, or multiple interfaces. 

I will post a follow-up eventually of how WCF manages a similar scenario.