To read the article online, visit http://www.4GuysFromRolla.com/articles/121703-1.aspx

An Extensive Examination of Web Services: Part 5

By Scott Mitchell


Introduction


Welcome to the 5th installment of an Extensive Examination of Web Services. In Part 1 we examined the basics of Web services, looking at what, precisely, standards made up Web services. In this first article we discussed SOAP, which stands for Simple Object Access Protocol, and is the standard used for formatting the messages passed between the client consuming the Web service, and the Web service itself. Recall that invoking a Web service involves two messages:

  1. A request message, in which a client invokes a particular method of the Web service, passing in input parameters, if applicable, and
  2. A response message, in which the Web service indicates that the method has been called, and returns the method's return value, if applicable.

As we looked at in Part 1, a SOAP-formatted request message has the following form:

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <WebServiceName xmlns="Web Service Namespace">
      <inputParameter1>input parameter value</inputParameter1>
      <inputParameter2>input parameter value</inputParameter2>
      ...
      <inputParameterN>input parameter value</inputParameterN>
    </WebServiceName>
  </soap:Body>
</soap:Envelope>

While the SOAP-formatted response message has the following form:

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <WebServiceNameResponse xmlns="Web Service Namespace">
      <WebServiceNameResult>result value</WebServiceNameResult>
    </WebServiceNameResponse>
  </soap:Body>
</soap:Envelope>

Notice that the request message can have a variable number of input parameters, and the response can have an optional result value (the method's return value). Both the input and output parameter values are formatted as XML. For example, a Web service method called Add that took in as input two integers inputs - x and y - and returned the sum as an integer, would have the following request and response messages if the client wished to determine the sum of 5 and 8:

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Add xmlns="Web Service Namespace">
      <x>5</x>
      <y>8</y>
    </Add>
  </soap:Body>
</soap:Envelope>


<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <AddResponse xmlns="Web Service Namespace">
      <AddResult>13</AddResult>
    </AddResponse>
  </soap:Body>
</soap:Envelope>

Notice that the client and the server are sending and receiving the input and output parameter values as XML. However, .NET does not store an integer in memory as XML markup; rather, it stores as integer as an Int32 structure. This implies that at some point the client, when calling the Add() method, must convert the integers 5 and 8 into an XML representation. Similarly, when the Web service receives the XML representation of 5 and 8 it must convert this XML representation back into Int32 structures.

This process is known as message serialization and message deserialization, respectively, and is the focus of this fifth part of the article series. In this article we'll look at the serialization and deserialization process, and see how even complex types - such as custom classes and arrays - can be effortlessly serialized and deserialized.

An Overview of XML Serialization


When working with data in a computer program, typically the data is located in the memory (RAM) of the computer on which the program is running. For example, when you are creating a letter in Microsoft Word, the contents of your letter, the formatting, and much other miscellaneous information is stored in the computer's memory. Now, imagine that you wanted to save the Word document to a file. How is this accomplished?

Clearly there needs to be some way for the data to be persisted. But the format of the data in the computer's memory may not be the ideal format to have it stored on the hard drive. Therefore there needs to be some conversion from the data's representation in memory to a representation suitable for storing it on disk. The process of converting data from one format to another is known as serialization. Once data has been serialized from one format to another, we typically will want to, at some later point in time, convert it back to the original format. This inverse operation is referred to as deserialization.

The .NET Framework provides a number of serialization options. The one that is pertinent to Web services is XML serialization, which involves serializing in-memory data to an XML format. With Web services this XML-formatted version of the data is sent from the client consuming the Web service, to the Web service, or vice-a-versa. The nice thing about the .NET Framework's XML serialization capabilities is that we, the developer, have to do very little, if anything, to benefit from it.

Persisting Data to Disk with XML Serialization
XML serialization is not limited in use to just Web services. For example, you can persist an object to disk using XML serialization. This could be used in a WinForms application to save data to the hard drive in a format that's easily viewable and modifiable by power users. For more information on XML serialization and some code samples of explicitly using the XmlSerializer class, be sure to read Anthony Hart's article XML Serialization in ASP.NET.

XML serialization is preformed by the XmlSerializer class. This class has two important methods: Serialize(stream, object) and Deserialize(stream). The Serialize() method takes in a stream and an object instance; it converts the object into its XML representation and squirts it out to the passed-in stream. Conversely, the Deserialize() method takes in a stream instance with the XML-formatted data, and deserializes it, returning an object instance.

When calling a Web service through the auto-generated proxy class, the data types being passed to the Web service are automatically serialized via XmlSerializer. Similarly, when the serialized data reaches the Web service it is automatically deserialized before being handed off to the called Web method. Inversely, when the Web methods sends its return value, that value is automatically serialized into XML, sent back to the client, and is then deserialized automatically. The figure below illustrates this concept:

A graphical representation depicting XML serialization in a Web service invocation.

Serializing Complex Data Types


As we've seen with some of the simple Web service examples we've examined throughout this article series thus far, it is apparent that the .NET Framework's XML serialization can serialize simple data types, like strings, integers, floats, date/times, and so forth. The XmlSerializer class can, additionally, serialize complex data types, such as collections and custom classes. Since Web services use XmlSerializer this means we can pass complex data types effortlessly to and from Web services!

For example, imagine that we wanted to create a Web service method that accepted an input parameter of type CreditCardInfo, which was a class defined with public properties like CardNumber, ExpirationMonth, and ExpirationYear. The following code shows the Web service class, with its method MakeDebit() method that takes in two parameters: a CreditCardInfo instance and an amount.

public class MyWebService : System.Web.Services.WebService
{
  [WebMethod()]
  public void MakeDebit(CreditCardInfo ccInfo, float amount)
  {
    // ... process the credit card ...
  }
}

public class CreditCardInfo
{
  private string _number;
  private int _expMonth, _expYear;

  public string CardNumber
  {
    get { return _number; }
    set  { _number = value;}
  }

  public int ExpirationMonth
  {
    get  { return _expMonth;  }
    set  { _expMonth = value;}
  }

  public int ExpirationYear
  {
    get { return _expYear; }
    set  { _expYear = value;}
  }
}

Notice that after the Web service class we provide our definition of the CreditCardInfo class. This class is relatively straightforward - it has three public properties as discussed earlier. When creating a client to consume this Web service, you would create the proxy class through Visual Studio .NET or using wsdl.exe as discussed in Part 3 of this article series.

The proxy class created on the client would contain a method called MakeDebit() that took in an object of type CreditCardInfo. Also created on the client would be a class named CreditCardInfo. The client could call MakeDebit() with the following few lines of code:

// create an instance of the proxy class
MyWebService proxy = new MyWebService();

// create an instance of the CreditCardInfo object
CreditCardInfo cc = new CreditCardInfo();
cc.CardNumber = "1111222233334444";
cc.ExpirationMonth = 2;
cc.ExpirationYear = 2005;

// call MakeDebit()
proxy.MakeDebit(cc, 4.95F);

The XML serialization process would serialize the CreditCardInfo class instance in this example to the following XML:

<CreditCardInfo>
  <CardNumber>1111222233334444</CardNumber>
  <ExpirationMonth>2</ExpirationMonth>
  <ExpirationYear>2005</ExpirationYear>
</CreditCardInfo>

This information, along with the second input parameter (amount), would be packaged into a SOAP-formatted message and sent to the Web service via an HTTP request. The Web service, upon receiving this request, would deserialize the input parameters, converting the CreditCardInfo XML content back into an in-memory instance of type CreditCardInfo.

Some Closing Notes on XML Serialization


There are a few subtle details with XML serialization that are important to know about when working with Web services that accept complex data types. Realize that XML serialization:

  • XML serialization can only be applied to classes that contain a public default (parameterless) constructor. If you create a class in the Web service that does not have a public default constructor, the Web service will still compile, but when attempting to generate the proxy class on the client's end you'll receive an error message.
  • Read-only class properties are not serialized. That is, if a property in the class has only a get accessor, and not a set accessor as well, the property will not be serialized.
  • Only public properties and fields are serialized. Private properties are not serialized.
  • To serialize a strongly-typed collection of objects, have the class derived from System.Collections.CollectionBase adding an Add() method and an indexer. (See this article for more information.) Alternatively you can send an array of the specified type.

On the last point, consider the case where you want a Web service method to accept a set of custom class instances. For example, we looked at a custom CreditCardInfo class - imagine you wanted to build a Web method that accepted a number of instances of type CreditCardInfo. One option is to have the method accept an array of type CreditCardInfo, like so:

[WebMethod()]
public void FooBar(CreditCardInfo [] collectionOfCCInfos)
{
   ...
}

Another option is to create a strongly-typed collection class derived from System.Collections.CollectionBase. You'd then pass in an instance of this strongly-typed collection class. For example, you'd first create the class:

public class CreditCardInfoCollection : System.Collections.CollectionBase
{
  public void Add(CreditCardInfo cc)
  {
    this.InnerList.Add(cc);
  }
  
  public CreditCardInfo this[int index]
  {
    get { return (CreditCardInfo) this.InnerList[index]; }
    set { this.InnerList[index] = value; }
  }
}

And then you'd have the Web service method accept an input of this strongly-typed collection class:

[WebMethod()]
public void FooBar(CreditCardInfoCollection collectionOfCCInfos)
{
   ...
}

Conclusion


In this article we examined the process by which input and output parameter values to a Web service method are converted to and from an XML format. This process is known as XML serialization and deserialization, and (with Web services) is handled for us behind the scene with the XmlSerializer class. Along with simple, scalar data types, the XmlSerializer class can also serialize and deserialize complex data types, such as arrays and custom classes. Thanks to this power, we can effortlessly create Web services that accept non-trivial data types.

Happy Programming!

  • By Scott Mitchell


  • Article Information
    Article Title: ASP.NET.An Extensive Examination of Web Services, Part 5
    Article Author: Scott Mitchell
    Published Date: December 17, 2003
    Article URL: http://www.4GuysFromRolla.com/articles/121703-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers