Thursday, April 10, 2008

Validating web service content against an XSD


Rodney has been working with a SOA implementation at one of our clients. As a result, we have changed how we deal with web services, to provide additional clarity and robustness to the WS-I standards that Microsoft utilises.

Recently we decided to split our web service definitions into 3 distinct files:

  1. An XSD to define the structure of the data (the parameters and returned data).
  2. A WSDL to define the operations that the web service will make available.
  3. A second WSDL to define the bindings (such as SOAP) used to communicate with the web service.

We create a C# interface from these three files using wsdl.exe. We create a web service in our web site, add this interface into our library and tweak the code to make it implicitly implement the interface.

This is quite easy and works very effectively. There is however one overlooked problem with this approach. Although the interface and associated classes were generated from an XSD and WSDL, the XSD is not strongly enforced from the web service. For example, the XSD may define a string maximum length:

  
   <xsd:simpleType name="String20">
      <xsd:restriction base="xsd:string">
         <xsd:maxLength value="20"/>
      </xsd:restriction>
   </xsd:simpleType>

The problem is that the web service created in this manner will allow a client to pass a string of length > 20. Not the desired outcome, since the XSD should be doing the work for us.


The reason this happens is that the web service doesn’t reference the XSD, nor does it define any attributes on a property to restrict the length. When you look at the schema automatically generated from the web service (http://myserver/mywebservice.asmx?schema=schema1), it uses the class structure to define the schema and therefore does not have the restrictions.
Validating all the values using code can be quite cumbersome and might not necessarily match the validation of the XSD. If the XSD is changed, then the code will also need to be altered to reflect the changes made to the XSD. Again, not a desired outcome.

Another approach is to validate the input data against the XSD from within the body of the web method. This can cause some problems since every individual parameter needs to be serialized to XML and compared to the XSD. Again, if the XSD changes and a new parameter is added, then the programmer has to remember to add the new parameter and ensure that it is validated against the schema. If this is a really large web service, on a high load site, this can be a significant overhead.

Enter SoapExtensions.

SoapExtensions allow you to perform validation on the incoming XML string before it is de-serialised into CLR objects.

Once Only Setup

SoapExtensions require a once only setup to create custom attribute classes stored in your reusable library.

A number of classes derived from the SoapAttribute abstract class need to be created. Some will be used to decorate the web service class to define the schemas while others are required to decorate the web methods to force validation. Coded correctly, these allow you to pass in a specific XSD or a directory full of XSD files to validate against at runtime.

In addition you will need to create a ValidationExtension class to perform the actual validation using the .NET XML library on the incoming XML message stream and XSD collection.
The full detail of the implementation can be found at http://msdn2.microsoft.com/en-us/magazine/cc164115.aspx.

Web Service Specific Setup

Armed with these new classes, validation on the incoming XML stream is now achieved via the following simple steps:

1. Setup the web.config to use the new ValidationExtension class

<webServices>
    <soapExtensionTypes>
        <add type="MaxSoft.Web.Services.Validation.ValidationExtension, MaxSoftDllpriority="1" group="High" />
    </soapExtensionTypes>
</webServices>

2. Decorate your class to point to the XSD file(s)

If you follow the msdn article above you will be able to point to a specific XSD, or a directory of XSD schema files. Both are shown below.

[ValidationSchemaCache("~/Schemas/")]
[ValidationSchema("~/Schemas/MySchema.xsd")]

3. Decorate your web method to trigger the actual validation

[Validation(CheckAssertions = false)]

4. Optionally you can add more complex validation to compliment the XSD

If CheckAssertions is set to true, you can provide additional validation logic which cannot be expressed in the XSD schema. An example is shown below:

[Assert("(//t:length > //t:width)", "Length must be greater than width")]

Although the capability is there, we don't use this feature. In fact, we have set the default value of CheckAssertions to false.

Not only does CheckAssertions add overhead to the validation, but it stores important business logic in an attribute of the web method rather than in the entity library, obviously poor practice. This additional business logic should be in a library for reuse, testability and for a raft of other reasons.

Note:

This code in Microsoft's was written in 1.1 and you need to make some significant changes for it to run against newer versions of .Net as many of the classes are now marked as obsolete.

No comments: