.NET to .NET web services without WSDL

We are using web services internally as an RPC mechanism. Our web tier calls our application tier via service interfaces. We are both exposing these interfaces and consuming them. They are not intended for external consumption.

Since we control both ends of the conversation, WSDL is overkill. WSDL is generated by .NET when you publish a web service. Any compliant consumer can import that WSDL and generate a service proxy. When the consumer is another .NET application, you can use either "Import Service Reference" or "svcutil.exe". Publish the service, generate the proxy, and start calling it.

This publish/import workflow causes problems in a homogeneous .NET/.NET environment. The generated proxy classes are all imported into one namespace, even if the published classes come from different namespaces. While this alone is confusing, it can cause real problems if you pass messages among through one service to another, or from multiple clients. Every client defines types of exactly the same shape, but since they are in different namespaces and assemblies, they are not the same type.

Another problem that this causes is version inconsistencies between clients and servers. We use TFS automated builds for continuous integration. After the build, the server is published to the development environment. If we generated client proxies from that development environment, we would have an old version of the client proxy checked in at the same time as a newer version of the service.

Here's my solution
Instead of going through WSDL and generating a proxy, the client can use exactly the same data types that the server publishes. If you look at the code that is generated for you, you can see how. All of the magic is in System.ServiceModel.ClientBase<T>. This class generates a proxy on the fly based on the interface you specify. Rather than importing that interface from WSDL, you can include it from the source.

Put all of your web service contracts into a single project. This includes all of the ServiceContract interfaces that define the service methods, and the DataContract classes that define their parameters and return values. This project will need to reference System.Runtime.Serialization and System.ServiceModel. None of the implementation goes in this project. In the spirit of the Dependency Inversion Principle, it is completely abstract. I like to call it <MySolution>.Contracts.

Next, add a reference from your web service project to <MySolution>.Contracts. Implement the service contract interface, and do the work of the web service here.

Finally, add a reference from your client project to <MySolution>.Contracts. Don't add a service reference. Don't run svcutil.exe. Instead, add this little interface/class pair to your arsenal:

public interface IServiceClientFactory<TServiceInterface>
{
	void CallService(Action<TServiceInterface> action);
	TResult CallService<TResult>(Func<TServiceInterface, TResult> function);
}

public class ServiceClientFactory<TServiceInterface> :
	IServiceClientFactory<TServiceInterface>
	where TServiceInterface : class
{
	class Client : System.ServiceModel.ClientBase<TServiceInterface>
	{
		public TServiceInterface Service
		{
			get { return base.Channel; }
		}
	}

	public void CallService(Action<TServiceInterface> action)
	{
		Client client = new Client();

		try
		{
			action(client.Service);
			client.Close();
		}
		catch (Exception)
		{
			client.Abort();
			throw;
		}
	}

	public TResult CallService<TResult>(Func<TServiceInterface, TResult> function)
	{
		Client client = new Client();

		try
		{
			TResult result = function(client.Service);
			client.Close();
			return result;
		}
		catch (Exception)
		{
			client.Abort();
			throw;
		}
	}
}

With this, you can call a web service in one of two ways. Either you can invoke a method that returns nothing:

_serviceClientFactory.CallService(client =>
{
	client.DoAction(parameters);
})

Or, you can invoke a method with a return:

var something = _serviceClientFactory.CallService(client => client.GetSomething(parameters));

The client calls the service without ever importing the WSDL. The ServiceClientFactory does the proper Close() or Abort() pattern on the service reference. And the interface makes it suitable for IoC and unit testing.

Leave a Reply