Transparent Web Service Proxies
When designing a service contract, developers have the choice of using a code-first or a contract-first approach. Using the contract-first approach for service endpoints requires greater discipline, and might be considered a bit more “enterprisey” than the code-first approach. The ASP.NET web service framework enlists a code-first process that shields the developer from the details of XML schemas and WSDLs. Visual Basic 6.0 did the same with the IDL interfaces in the COM-era, so it isn’t much of a surprise that most .NET developers prefer the code-first approach when developing web services.
Integrating with web services that are evolving can be cumbersome. All most every web service client framework has an inherent contract-first approach. For instance, when you add a new web reference to a Visual Studio .NET project you provide the URL to the service’s WSDL file and the tool generates a proxy for the service. Because the client is coupled with the service, your client breaks whenever a breaking change is made to the service.
One approach to make the development of a client consuming an evolving service is to use a client which is loosely coupled with the service interface. Instead of adding a schema bound web reference to your project, you can resolve this reference at runtime. Christian Weyer has a great example of how this can be achieved.
A shortcoming with using statically typed languages such as C# or Visual Basic .NET to build a more flexible proxy similar to Christian’s demo, is that you need to apply a truckload of reflection to invoke the various operations on the proxy. This clutters the code and makes it hard to read.
Ruby frameworks like the xmlrpc4r leverage Ruby’s special method_missing method to dynamically implement missing methods at runtime. This allows you to pretend that any operation is available on a class instance. The following example shows how you would use the xmlrpc4r framework to invoke an XMLRPC service:
require ‘xmlrpc/client’
math = XMLRPC::Client.new2(“http://www.cookcomputing.com/xmlrpcsamples/math.rem”).proxy(“math”)
result=math.Add(4,4)
puts(result)
Boo supports a similar mechanism through the odd-named IQuackFu interface. With Boo we can write a flexible web service client that supports a similar programming model.
class WebServiceClient(IQuackFu):
private proxy as SoapHttpClientProtocol
public Operations as (LogicalMethodInfo):
get:
methods=proxy.GetType().GetMethods(BindingFlags.Public|BindingFlags.Instance)
baseMethods=typeof(SoapHttpClientProtocol).GetMethods(BindingFlags.Public|BindingFlags.Instance)
logicalMethods=List()
for m in methods:
skip=false
for bm in baseMethods:
if (bm.Name==m.Name):
skip=true
continue
if not skip:
logicalMethods.Add(LogicalMethodInfo(m))
return logicalMethods.ToArray(typeof(LogicalMethodInfo))
def constructor(uri as string):
proxy=ProxyFactory.Create(uri)
def constructor(wsdl as ServiceDescription):
proxy=ProxyFactory.Create(wsdl)
def QuackGet(name as string) as object:
pass
def QuackSet(name as string, value as object):
pass
def QuackInvoke(name as string, args as (object)) as object:
method=proxy.GetType().GetMethod(name,BindingFlags.Instance|BindingFlags.Public)
if (method==null):
raise MissingMethodException(typeof(WebServiceClient).Name,name)
paramTypes as (Type)=array(Type,method.GetParameters().Length)
for i in range(paramTypes.Length):
paramTypes[i]=method.GetParameters()[i].ParameterType
result=method.Invoke(proxy,CoercionHelper.CoerceTypes(cast(IList,args),paramTypes))
return result
The code above shows an implementation of a dynamic web service proxy. Take note of the QuackInvoke method. When a client tries to invoke an non-existing operation on an instance of the type, the invocation will be redirected to the QuackInvoke method. The name of the requested operation will be passed as the name argument, while the arguments for the missing method will be passed as the args array. This allows us to invoke methods on the proxy which are not known at runtime.
Above is an example of how the WebServiceClient class can be used to invoke a currency exchange rate service. Behind the scenes, the web service proxy is generated dynamically and this implementation is used to invoke the service.
Another issue when developing a dynamic client is typing. Since the proxy classes are generated at runtime, you cannot use the generated types in your client code. To work around this limitation the transparent proxy applies simple type coercions to convert from the types used by the client to the types used accepted by the service proxy. For instance the exchange rate service from the previous expects the currency to be of an enumeration type, whilst the client just uses a string.
<s:simpleType name="Currency">
<s:restriction base="s:string">
<s:enumeration value="AFA"/>
<s:enumeration value="ALL"/>
<s:enumeration value="DZD"/>
<s:enumeration value="ARS"/>
<!-- Other currency codes eluded for readability -->
</s:restriction >
</s:simpleType>
Coercing a simple type is straight forward, but there is also some support for complex types. As long as the type used by the client can be serialized to the type the service expects you should not experience any problems. If the service returns a complex type, you must be prepared to consume a type that was not known at compile time.
My key motivation for writing this library was to provide a better way to test web service integrations, particularly in those situations when you need to verify that your assumptions about an evolving service contract still holds true. This allows you to adopt a more evolutionary service contract definition approach by applying just enough validation in the tests, rather than validating then entire WSDL and schema from the client as you would with a hardwired, tool generated proxy.
You can use this library with C# or Visual Basic, but I really recommend using Boo because you’ll write less code and you’ll get a friendlier interface. Below is the exchange rate example in C# and it is not as readable as the Boo version.
WebServiceClient client = new WebServiceClient("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
object[] args= new object[] { "NOK", "USD" };
Console.WriteLine(RuntimeServices.Invoke(client, "ConversionRate", args));
You can download the entire source code for the library here.