Monday 12 January 2009

Take Control of your WSDL

Introduction

Its good practice to design the interface to a web service by applying the same standards that would normally be applied to an XML Schema. It
should be versioned, extensible and be well structured allowing for good reuse.

Background

Within a Web Service all the messages are described using an XML Schema (XSD), so it makes sense to design our interface in terms of an XML Schema, and then embed this into our web service. Liquid XML Studio is an ideal editor for this, as it’s free and easy to use.

The following example shows the messages we would like our simple web service to exchange. Our simple Web Service will provide information about a given product, when provided with a product code, we will also need to provide login details to the service.

The information returned describes the product, pricing, and stock levels.

So we would like to build a web service with a web method that takes a ProductDetailsRequestType and returns a ProductDetailsResponseType.

So that’s what we’re after, now lets look at how to achieve this.

Using the standard .Net Framework Serialisation.

We could create a class for each entity in the schema, give them the appropriate properties and let the standard .Net framework [WebMethod] serialisation do the rest. This is an acceptable approach for small projects, but if you want to pass complex objects or objects from 3rd party
standards (HL7, FpML, ebXML, etc) this starts to become overwhelming.

Another approach I've seen used a lot is just to pass the XML data via a string parameter. This has a number of drawbacks, the XML will all be escaped so it expands (< becomes &lt; etc). Also the web service description (WSDL) now tells you nothing useful about the web service - it takes in a string and returns a string. Not a great deal of help for implementers. Also your web service provides no validation, your web service method will get called regardless of the content of the string - it doesn't even have to be XML.

A better approach is to use an XmlElement as the in/out parameters to your web service. This solves some of the problems, the XML is not escaped, your method only gets called if the XML is valid (well formed), but implementers still have no idea what the web service data is supposed to look like, and you only get minimal validation performed on your XML.

It is however possible to apply the techniques to allow an XmlElement to be passed via a web service and have it fully described in the WSDL. But more about this later.

The XML Data Binding Approach

A really easy way to deal with XML within your application
is XML Data Binding. This involves the automatic creation of a class for every
entity in your schema. These classes are bound to the structure of the schema; so
using our example we would end up with an object called 'Credentials', with
properties called 'Username' and 'Password'. This removes any notion of dealing
with XML from the application, and means the developer just has to deal with
objects, properties and collections, all strongly typed, and enforcing the structure
described in the XML Schema.

These objects can be generated using a Data Binding tool, Liquid
XML Studio has an XML Data Binding tool which will create .Net code for 1.1,
2.0, 3.5 & Silverlight both in C# and VB.Net.

Once we have generated our Data Bound class library, we can
pull together our web service. The first thing to do is declare our web method.

namespace ProductEnquiryWebService
{
[WebService(Namespace = "http://www.liquid-technologies.com/ProductDetailsSample")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService
{
[WebMethod(Description = "Gets the details about a given product.")]
public ProductDetailsResponse RequestProductDetails(ProductDetailsRequest productRequest)
{
ProductDetailsResponse response = new ProductDetailsResponse();
response.ProductDetails.Name = "Widget";
response.ProductDetails.Description =
"The important widget that goes between the flange and the spindle";
response.ProductDetails.Price = 25.36;
response.ProductDetails.StockLevel = 15000;
return response;
}
}
}

Note the web service uses the targetNamespace from the XML Schema we defined at the start of the example. The web method takes in and returns the Data Bound classes generated by Liquid XML Studio.

If we left things here then the .Net framework would use its default serializer on our ProductDetailsRequest and ProductDetailsResponse objects. This works using reflection and would end up serializing a large amount of unwanted properties. Instead we are going to let these objects serialise themselves. In order to do this we must tell the framework about them.

[XmlSchemaProvider("MySchema")]
partial class ProductDetailsRequest
{
// This is the method named by the XmlSchemaProviderAttribute applied to the type.
public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
// This method is called by the framework to get the schema for this type.
// We return an existing schema from disk.
if (xs.Schemas("http://www.liquid-technologies.com/ProductDetailsSample").Count == 0)
{
using (FileStream fs = new FileStream(@"ProductEnquiry.xsd", FileMode.Open))
{
XmlSchema s = XmlSchema.Read(fs, null);
xs.Add(s);
}
}
return new XmlQualifiedName("ProductDetailsQueryType",
"http://www.liquid-technologies.com/ProductDetailsSample");
}
}

In order for the framework to be able to correctly describe the web service we first need to tell it about the schema. This is done by adding the XmlSchemaProvider attribute to all classes that are in or out parameters in the web service. The XmlSchemaProvider attribute tells the framework that information about the schema can be obtained by calling the “MySchema”
method.

Now when the framework is trying to describe the web service it will call the MySchema method. The framework expects our implementation of MySchema to add all the schemas it needs to know about into the schema set collection, so if you are working with elements from external schemas (HL7, FpML etc) then you can add these to the schemas collection as well.

The return parameter from MySchemas is the fully qualified name of the type within our XSD that describes the parameter. Note this must be a complexType, the .Net framework will not allow you to specify an element.

Customising the Serialization Performed by XmlElement

As discussed earlier, it is possible to pass XmlElements as parameters in your web service, and have them fully described in the WSDL.

In order to do this a class wrappering an XmlElement must be passed instead of the XmlElement, this class must implement the IXmlSerializable interface, and have its
own XmlSchemaProvider attribute.

Because the 'MySchema' method is static, one wrapper class must be created foreach type of object being passed, as the return value tells the .Net framework which complexType in the schema this argument represents.

A further improvement on this would be to add validation to the element prior to serialisation, thus ensuring that the XML was actually valid before reading or writing it.

[XmlSchemaProvider("MySchema")]
public class MyXmlElement : IXmlSerializable
{
private XmlElement _element = null;

public MyXmlElement() {} // requires a parameterless constructor
public MyXmlElement(XmlElement xmlElemnt) { _element = xmlElemnt; }

// This is the method named by the XmlSchemaProviderAttribute applied to the type.
public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
// This method is called by the framework to get the schema for this type.
// We return an existing schema from disk.
if (xs.Schemas("http://www.liquid-technologies.com/ProductDetailsSample").Count == 0)
{
using (FileStream fs = new FileStream(@"ProductEnquiry.xsd", FileMode.Open))
{
XmlSchema s = XmlSchema.Read(fs, null);
xs.Add(s);
}
}
return new XmlQualifiedName("ProductDetailsRequestType", "http://www.liquid-technologies.com/ProductDetailsSample");
}


#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}

public void ReadXml(System.Xml.XmlReader reader)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(reader);
_element = xmlDoc.DocumentElement;
}

public void WriteXml(System.Xml.XmlWriter writer)
{
_element.WriteTo(writer);
}
#endregion
}

[WebMethod(Description = "Showing the technique with XmlElement.")]
public MyXmlElement Test()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(@"SampleData.xml");
return new MyXmlElement(xmlDoc.DocumentElement);
}

Testing your Web Service

Because your web service contains complex parameters it is not
possible to call it via the simple Web Service Explorer.

WS_IE.png

So in order to call it you need to either build yourself a test harness or use a test tool. Liquid XML Studio contains a handy tool for calling web services. This will automatically generate the request envelope and message, allowing you to modify can call it.

CallWebService_640x529.png

Download Sample Request
Download Sample Response

Examining the WSDL

Because we have taken control of the serialisation process the WSDL describing the web service contains the contents of the schemas that you added in via the MySchema method.

Download the WSDL - 4.62 KB

It is also possible to explore this graphically using the Web service Call Composer in Liquid XML Studio

Conclusion

If you are building web services that require a hierarchy of objects to be sent or received, especially if those messages contain sections from existing schemas (formal standards or internal data structures) or contain complex constructs (inheritance, substitution groups etc), then you need more control over the WSDL that defines your web service.

XML Data Binding not only simplifies the reading and writing of your XML data, providing a framework of strongly typed objects in which to work, but can provide information to the .Net Web Service Framework that allows it to fully describe even the most complex data
structures accurately within the WSDL.

Using this approach makes it possible to include parts of complex standards within your web service and still have them properly described in the WSDL, enabling other tools to properly understand and use the interface.

Resources

No comments:

Post a Comment