Click here to Skip to main content
Licence CPOL
First Posted 12 Jan 2009
Views 4,234
Downloads 0
Bookmarked 22 times

Customising your Web Service's Interface

By | 1 Feb 2009 | Article
Describes how to take control of the interface for a Web Service, allowing complex XML schema elements to form part of the interface.

Introduction

It is 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.

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.

ProductDetailsRequestType

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

ProductDetailsResponseType

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, let's look at how to achieve this.

Using the standard .NET Framework serialization

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 parameter 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.

So wouldn't it be useful to be able to pass an XmlElement via a web service and have them fully described in the WSDL.

Customizing the serialization performed by XmlElement

In order to pass an XmlElement as a web service parameter and have it fully described within the WSDL, we need to create a class to wrapper an XmlElement. This wrapper class will then be passed instead of the XmlElement. Our wrapper class must implement the IXmlSerializable interface in order to allow the framework to read and write XML to/from the object, and have its own XmlSchemaProvider attribute to allow it to describe itself in terms of a XML Schema within the WSDL.

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 the wrapper class. 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 MySchema is the fully qualified name of the type within our XSD that describes the parameter. Note this must be a complex type, the .NET framework will not allow you to specify an element.

Because the 'MySchema' method is static, one wrapper class must be created for each type of object being passed, as the return value tells the .NET framework which complex type 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);
}

The XML data binding approach

Another approach to the creation of Web Services is XML Data Binding. This provides a simple way to deal with XML within your application. 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. The following tools will generate the binding classes from your xml schemas

Once we have generated data binding classes, we can put together our web service. The first thing to do is declare our web method.

namespace ProductEnquiryWebService
{
  [WebService(Namespace = 
    "http://www.MyCompany.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;
    }
  }
}

If we left things here, then the .NET framework would use its default serializer on our ProductDetailsRequest and ProductDetailsResponse objects. This would produce valid XML but would not describe input and output parameters in the WSDL. In order to do this, we must tell the framework about the structure of the ProductDetailsRequest and ProductDetailsResponse objects in terms of an XML Schema (xsd). This is done in the same way as before using the XmlSchemaProvider attribute.

[XmlSchemaProvider("MySchema")]
public 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.MyCompany.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.MyCompany.com/ProductDetailsSample");
  }
  
  
   ...... The rest of the XML Data Binding Class ....
}

Testing your Web Service

Because your web service contains complex parameters, it is not possible to call it via a 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 and call it.

CallWebService_640x529.png

Sample request

<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <tnsa:RequestProductDetails xmlns:tns="http://schemas.xmlsoap.org/soap/encoding/" 
               xmlns:tnsa="http://www.liquid-technologies.com/ProductDetailsSample" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <tnsa:productRequest>
        <tnsa:Credentials>
          <tnsa:Username>Joe</tnsa:Username>
          <tnsa:Password>password</tnsa:Password>
        </tnsa:Credentials>
        <tnsa:ProductSerialNo>0123456798</tnsa:ProductSerialNo>
      </tnsa:productRequest>
    </tnsa:RequestProductDetails>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Sample response

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <RequestProductDetailsResponse 
         xmlns="http://www.liquid-technologies.com/ProductDetailsSample">
      <RequestProductDetailsResult>
        <!--Created by Liquid XML Data Binding Libraries (www.liquid-technologies.com) -->
        <AA:ProductDetailsResponse 
            xmlns:AA="http://www.liquid-technologies.com/ProductDetailsSample">
          <AA:ProductDetails>
            <AA:Name>Widget</AA:Name>
            <AA:Description>The important widget that goes 
                  between the flange and the spindle</AA:Description>
            <AA:Price>25.36</AA:Price>
            <AA:StockLevel>15000</AA:StockLevel>
          </AA:ProductDetails>
        </AA:ProductDetailsResponse>
      </RequestProductDetailsResult>
    </RequestProductDetailsResponse>
  </soap:Body>
</soap:Envelope>

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.

The WSDL Schema shown graphically.

WSDL_Diagram_640x238.png
The Diagram is from the Web Service Call Composer in the free edition of 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.

It is possible to exercise this level of control by passing wrappered XmlElement objects as parameters, but the use of XML data binding classes provides a much better way in which to work, simplifying the reading and writing of your XML data, and providing a framework of strongly typed objects in which to work, while also providing 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

History

  • Created: 12/1/2009.
  • Updated: 1/2/2009.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

ColinMaunder

Software Developer (Senior)

United Kingdom United Kingdom

Member



Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralNice article PinmemberTarabanko Yury8:43 12 Jan '09  
GeneralRe: Nice article PinmemberColinMaunder10:16 12 Jan '09  
GeneralSample code fails to build...you need to do the following Pinmemberbconlon7:28 12 Jan '09  
GeneralRe: Sample code fails to build...you need to do the following PinmemberColinMaunder10:19 12 Jan '09  
Questionxsd.exe Pinmemberdm.996:59 12 Jan '09  
AnswerRe: xsd.exe PinmemberColinMaunder8:55 12 Jan '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120528.1 | Last Updated 1 Feb 2009
Article Copyright 2009 by ColinMaunder
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid