Why Should You Care About Interfaces in ASMX? Because It Provides Versioning

My last set of posts have gotten some interesting feedback via email. People are asking why this is something that is useful or why they should care about interface support in services.

Think about why and how you use interfaces for classes. You use interfaces to group behaviors together within an impl class and provide versioning semantics for your classes. In V1 of my class, I support the HelloWorld method, then some time later in V2, I support the GoodbyeWorld method. By providing interfaces, I can simultaneously support V1 and V2 clients without breaking the V1 clients.

Let’s say you have a service that contains a single method, HelloWorld. The service has a namespace "urn:foo-com:services:2006:v1". At some later point, you want to add a GoodbyeWorld method to that service. You shouldn’t simply add "GoodbyeWorld" to the service, because that means you are changing the contract for V1 of the service and you have not provided a means to your clients to know about this change. If you change the namespace of the service to "urn:foo-com:services:2006:v2", you have broken the V1 clients because the namespace is different. Standing up a new service endpoint is complete overkill for simply adding a new behavior to an existing service... so how do you support adding a method to a service without breaking the V1 clients?

This is why you should use interfaces in services. Interfaces in ASMX v2 solves this problem adding a new WSDL binding to the existing service and indicate through this new interface that this provides V2 behavior while keeping V1 intact.

 
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Services.Description;
using System.Xml.Serialization;

[WebServiceBinding(  
    Name="MyServiceBinding", 
    Namespace="urn:foo:bar:2006:v1", 
    ConformsTo=WsiProfiles.BasicProfile1_1, 
    EmitConformanceClaims=true)]
public interface IService
{
    [WebMethod]
    HelloResponse HelloWorld(HelloRequest request);
}

[WebServiceBinding(
    Name = "MyServiceBinding2",
    Namespace = "urn:foo:bar:2006:v2",
    ConformsTo = WsiProfiles.BasicProfile1_1,
    EmitConformanceClaims = true)]
public interface IService2
{
    [WebMethod]
    HelloResponse GoodbyeWorld(HelloRequest request);
}

[WebService(Namespace = "urn:foo:bar", Name="My Soap Service")]
[SoapDocumentService(ParameterStyle = SoapParameterStyle.Wrapped)]
public class ServiceBinding : IService, IService2
{    
    public HelloResponse HelloWorld(HelloRequest request)
    {
        HelloResponse response = new HelloResponse();
        response.Message = "Hello";
        return response;
    }

    public HelloResponse GoodbyeWorld(HelloRequest request)
    {
        HelloResponse response = new HelloResponse();
        response.Message = "Goodbye!";
        return response;
    }
}

[XmlType(TypeName = "HelloResponse", Namespace = "urn:foo:bar")]
public class HelloResponse
{    
    private string _message;

    [XmlElement(ElementName = "ResponseMessage")]
    public string Message
    {
        get { return _message; }
        set { _message = value; }
    }
    
}

[XmlType(TypeName = "HelloRequest", Namespace = "urn:foo:bar")]
public class HelloRequest
{
}

If you look closely, you will see that the service has a namespace "urn:foo:bar", but it has 2 bindings:  MyServiceBinding, and MyServiceBinding2 (noting that I removed SOAP 1.2 support from my service to keep it simple).

   <wsdl:service name="My_x0020_Soap_x0020_Service">
    <wsdl:port name="MyServiceBinding2" binding="i0:MyServiceBinding2">
      <soap:address location="https://localhost:1473/WebSite6/Service.asmx" />
    </wsdl:port>
    <wsdl:port name="MyServiceBinding" binding="i1:MyServiceBinding">
      <soap:address location="https://localhost:1473/WebSite6/Service.asmx" />
    </wsdl:port>
  </wsdl:service>

The really interesting part about this is that ASMX will separate the WSDL into three WSDL documents.  It generates a unique WSDL for each of the interfaces, and a main WSDL document that contains wsdl:includes to the other 2 WSDLs representing the bindings, making the main WSDL much cleaner.

 
  <wsdl:import namespace="urn:foo:bar:2006:v2" location="https://localhost:1473/WebSite6/Service.asmx?wsdl=wsdl1" />
  <wsdl:import namespace="urn:foo:bar:2006:v1" location="https://localhost:1473/WebSite6/Service.asmx?wsdl=wsdl2" />

From the client proxy generation experience, implementing service interfaces makes things cleaner on them as well... they can simply implement the new client proxy type to gain the new behavior.  Here we have a J# client application calling our web service... notice that there are 2 proxy classes being used, "MyServiceBinding" and "MyServiceBinding2", according to the names that we provided when we created our interfaces for our services.

 package ConsoleApplication1;
import ConsoleApplication1.localhost.*;

public class Program
{
    public static void main(String[] args)
    {
        MyServiceBinding binding = new MyServiceBinding();
        HelloRequest request = new HelloRequest();
        HelloResponse response = binding.HelloWorld(request);
        System.Console.WriteLine(response.get_ResponseMessage());

        MyServiceBinding2 binding2 = new MyServiceBinding2();
        response = binding2.GoodbyeWorld(request);
        System.Console.WriteLine(response.get_ResponseMessage());
        
    }
}