A little while ago a new build of Indigo found its way onto my desk. On thing that’s interesting about this particular interim milestone (don’t hope for juicy details here on the blog) is that it doesn’t support WSDL. No. Calm down. It’s not what you think. It just happens that the WSDL support wasn’t included in this particular version; it’ll be there, no worries.

Such interim binary drops that the Microsoft product groups give out to very early adopters are really only meant for the bravest of the brave with too much time on their hands. Therefore, the documentation is not entirely in synch with the feature set – and that’s a stretch. (Hey, I am not complaining.) So until someone told me “yeah, there’s no WSDL or Metadata exchange worth talking about in this build” I tried to find how contract works in this build and of course I couldn’t find it. Well, not really true. It turns out that contract works just like with ASP.NET 1.0 or ASP.NET 1.1 Web Services. At runtime, WSDL usually doesn’t play any role, at all. Unless you use some funky dynamic binding logic, WSDL is just a design-time metadata container and that’s the basis for generating CLR metadata and code. Although there is an implementation aspect to it when you generate proxies or server skeletons, the most important job of WSDL.EXE is to perform a conversion of the WSDL rendering of the message contract into a format that the ASMX infrastructure can readily understand. That format happens to be classes with methods and attribute annotations. Here is a client and a server I just typed up with Notepad:

Contract.asmx

Client.cs

<% @WebService class="MyService" language="C#"%>

using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace="http://tempuri.org")]
public class MyService
{
    [WebMethod]
    public string Hello( string Test )
    {
       return "Hello "+Test;
    }
}

using System;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebServiceBinding(Namespace="http://tempuri.org")]
public class MyService : SoapHttpClientProtocol
{
    [SoapDocumentMethod]
    public string Hello( string Test )
    {
       return (string)Invoke("Hello", new object[]{Test})[0];
    }
}

public class MainApp
{
   static void Main()
   {
       MyService m = new MyService();
       m.Url = "http://localhost/contract.asmx";
       Console.WriteLine("Result: "+ m.Hello("Test"));
   }
}

Drop the contract.asmx into x:\inetpub\wwwroot, compile the client with “csc client.cs” and run it. No WSDL ever changed hands, no “Add Web Reference”, it just works. Ok, ok, it’s the year ‘04 now and there’s really no magic there anymore. Now here’s a tiny bit of refactoring:

Contract.asmx

Client.cs

<% @WebService class="MyService" language="C#"%>

using System.Web.Services;
using System.Web.Services.Protocols;


public interface IMyService
{
    string Hello( string Test );
}


[WebService(Namespace="http://tempuri.org")]
public class MyService : IMyService
{
    [WebMethod]
    public string Hello( string Test )
    {
       return "Hello "+Test;
    }
}

using System;
using System.Web.Services;
using System.Web.Services.Protocols;


public interface IMyService
{
    string Hello( string Test );
}


[WebServiceBinding(Namespace="http://tempuri.org")]
public class MyService : SoapHttpClientProtocol, IMyService
{
    [SoapDocumentMethod]
    public string Hello( string Test )
    {
       return (string)Invoke("Hello", new object[]{Test})[0];
    }
}

public class MainApp
{
   static void Main()
   {
       MyService m = new MyService();
       m.Url = "http://localhost:8080/contract.asmx";
       Console.WriteLine("Result: "+ m.Hello("Test"));
   }
}

Extracting the interface from server and proxy makes it very, very clear that we’re dealing with the very same message contract. Mind that we only have source-code level contract equivalence between client and server here. The compiled code on either side yields distinct IMyService types and that’s supposed to be that way. In the case I am illustrating here, the language C# serves as the metadata language (and the clipboard is the mechanism) for sharing contract between client and server (or endpoints).

There are two things I find (mildly) annoying about ASP.Net Web Services 1.x and they also become quite apparent in this example: #1 the server side and the client side have a different minimum set of required attributes and #2 ASP.NET’s ASMX support doesn’t look at inherited method-level attributes. The following does not even compile:

Contract.asmx

Client.cs

<% @WebService class="MyService" language="C#"%>

using System.Web.Services;
using System.Web.Services.Protocols;

[WebServiceBinding(Namespace="http://tempuri.org")]
[WebService(Namespace="http://tempuri.org")]
public interface IMyService
{
    [SoapDocumentMethod][WebService]
    string Hello( string Test );
}

public class MyService : IMyService
{
    public string Hello( string Test )
    {
       return "Hello "+Test;
    }
}

using System;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebServiceBinding(Namespace="http://tempuri.org")]
[WebService(Namespace="http://tempuri.org")]
public interface IMyService
{
    [SoapDocumentMethod][WebService]   
    string Hello( string Test );
}

public class MyService : SoapHttpClientProtocol, IMyService
{
    public string Hello( string Test )
    {
       return (string)Invoke("Hello", new object[]{Test})[0];
    }
}

public class MainApp
{
   static void Main()
   {
       MyService m = new MyService();
       m.Url = "http://localhost:8080/contract.asmx";
       Console.WriteLine("Result: "+ m.Hello("Test"));
   }
}

And, frankly, it’s probably not bad that it doesn’t compile, because the half of the attributes on the resulting interface declaration are useless on either side.

Indigo pulls both sides together into the notion of a service contract that’s good for either side (I am using a simple variant of the “publicly known” notation here)

[ServiceContract]
public interface IMyService
{
    [ServiceMethod]
    string Hello( string Test );
}

If you want to see it that way, this is Indigo’s IDL (Interface Definition Language). There’s an equivalence transformation from and to WSDL, if you want to share the contract with folks who program on other platforms or who use another programming language. There are just no angle brackets here and it’s much easier to read, too.

So if you see code that I wrote and you find (even today) seemingly unnecessary interface declarations that are implemented on a single web service class within a project and nowhere else and are also not referenced from anywhere – they might not be unnecessary as they seem. It’s simply IDL!

More confusingly, you might find those declarations replicated! in source code! in another service! Yes, because services are designed to be evolved independent of each other. So while the target service is already in V4, the consumer may still be on the level of V2. Moving up to the V4 interface version is a conscious choice by the developer of the service consumer – and at that point in time he/she imports the most recent contract. Whether that’s copy/paste of a C#/VB/C++ declaration or clicking “Update” on some menu in Visual Studio that turns WSDL into proxy code is not very important; it’s just a matter of tool preference.

Using explicit interface declarations with Web Services is strictly a “contract first” model. It’s just not using WSDL, that’s all.

Updated: