Controlling How Data Is Transferred in Web Services






Controlling How Data Is Transferred in Web Services

When data is returned from a Web Service call, it is serialized into XML, meaning that you can only return native types, or types that know how to serialize themselves (such as the DataSet). If you have a strongly typed business layer, and you wish to return this as the result of a Web method, the first thing you need to do is make sure that the class has a parameterless constructorotherwise it cannot be serialized.

Next, you must make sure that all properties you wish to serialize are read-write, because read-only properties are ignored during serialization, as are methods. Consider Listing 16.8, which shows a simple class for use in a Web Service.

The Shipper Class

public class Shipper
{
  private int _id;
  private string _companyName;
  private string _phone;

  public Shipper()
  {
  }

  public Shipper(int id, string companyName, string phone)
  {
    _id = id;
    _companyName = companyName;
    _phone = phone;
  }

  public int ID
  {
    get { return _id; }
    set { _id = value; }
  }

  public string CompanyName
  {
    get { return _companyName; }
    set { _companyName = value; }
  }

  public string Phone
  {
    get { return _phone; }
    set { _phone = value; }
  }
}

When an instance of this class is returned from a Web Service, the XML contains an element for each property, as seen in Listing 16.9.

The Serialized Shipper Class

<?xml version="1.0" encoding="utf-8"?>
<Shipper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://aspnetillustrated.org/services/">
  <ID>5</ID>
  <CompanyName>Tortoise Inc</CompanyName>
  <Phone>555 123 1234</Phone>
</Shipper>

Customizing Serialization

You can change the way the XML is generated by attributing the class and properties. For example, consider Listing 16.10. The class has an XmlRoot attribute, which defines the name of the root element. The ID property has the XmlAttribute attribute, specifying the AttributeName, which will make the property become an XML attribute on the parent element. The Phone property has the XmlElement attribute, specifying the ElementName and that it can contain a null value.

The Shipper Class Attributed for Serialization

[XmlRoot("RegionalShipper")]
public class Shipper
{
  private int _id;
  private string _companyName;
  private string _phone;

  public Shipper()
  {
  }

  public Shipper(int id, string companyName, string phone)
  {
    _id = id;
    _companyName = companyName;
    _phone = phone;
  }

  [XmlAttribute(AttributeName = "ShipperID")]
  public int ID
  {
    get { return _id; }
    set { _id = value; }
  }

  public string CompanyName
  {
    get { return _companyName; }
    set { _companyName = value; }
  }

  [XmlElement(ElementName = "PhoneNumber",
    IsNullable = true)]
  public string Phone
  {
    get { return _phone; }
    set { _phone = value; }
  }
}

When the Shipper class is returned from a Web Service, the custom attributes are taken into account, resulting in the XML shown in Listing 16.11.

Serializing Collections

Collections are serialized to structured XML, with the parent element having a default name of ArrayOfClass, where Class is the name of the class being serialized. For example, Listing 16.12 shows the results of a Web Service that returns a generic list of Shipper objects (List<Shipper>).

The Serialized Shipper Class with Customized Serialization

<?xml version="1.0" encoding="utf-8"?>
<RegionalShipper
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  ShipperID="5"
  xmlns="http://aspnetillustrated.org/services/">
  <CompanyName>Tortoise Inc</CompanyName>
  <PhoneNumber>555 123 1234</PhoneNumber>
</RegionalShipper>

A Serialized Collection

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfShipper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://aspnetillustrated.org/services/">
  <Shipper ShipperID="1">
    <CompanyName>Speedy Express</CompanyName>
    <PhoneNumber>(503) 555-9831</PhoneNumber>
  </Shipper>
  <Shipper ShipperID="2">
    <CompanyName>United Package</CompanyName>
    <PhoneNumber>(503) 555-3199</PhoneNumber>
  </Shipper>
  <Shipper ShipperID="3">
    <CompanyName>Federal Shipping</CompanyName>
    <PhoneNumber>(503) 555-9931</PhoneNumber>
  </Shipper>
</ArrayOfShipper>

You can control the top element by adding an additional attribute to the Web method. For example, Listing 16.13 shows that you can specify the name of the element returned.

Specifying the Returned Element Name

[WebMethod]
[return: XmlElement("Shippers")]
public List<Shipper> GetShippers()

The top-level element would no longer be ArrayOfShipper, but Shippers. Note that this only seems to affect SOAP requests, and not HTTP POST requests, which the Web Service Help Page uses. You can tell this by examining the WSDL or the sample output on the help page.

You can also use the XmlElement attribute to remove a layer of indirection from within the generated XML. For example, consider the class in Listing 16.14, which is a collection of Shipper objects.

The ShipperCollection Class

public class ShipperCollection
{
  private List<Shipper> _items;

  public ShipperCollection()
  {
    _items = new List<Shipper>();
  }

  public List<Shipper> Items
  {
    get { return _items; }
    set { _items = value; }
  }
}

Listing 16.15 shows that the results of the ShipperCollection class is returned from a Web method, and you can see that there is a redundant Items element.

The ShipperCollection Class Serialized

<?xml version="1.0" encoding="utf-8"?>
<ShipperCollection
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://aspnetillustrated.org/services/">
  <Items>
    <Shipper ShipperID="1">
      <CompanyName>Speedy Express</CompanyName>
      <PhoneNumber>(503) 555-9831</PhoneNumber>
    </Shipper>
    <Shipper ShipperID="2">
      <CompanyName>United Package</CompanyName>
      <PhoneNumber>(503) 555-3199</PhoneNumber>
    </Shipper>
    <Shipper ShipperID="3">
      <CompanyName>Federal Shipping</CompanyName>
      <PhoneNumber>(503) 555-9931</PhoneNumber>
    </Shipper>
    <Shipper ShipperID="14">
      <CompanyName>Tortoise Couriers</CompanyName>
      <PhoneNumber>12555 123 456</PhoneNumber>
    </Shipper>
    <Shipper ShipperID="15">
      <CompanyName>Tortopoise Inc</CompanyName>
      <PhoneNumber>555 123 1234</PhoneNumber>
    </Shipper>
  </Items>
</ShipperCollection>

The ShipperCollection is the root element, with the Items property having an element, and then each Shipper an element of its own. To remove the redundant Items element, you can use the XmlElement attribute on the Items property in the collection (Listing 16.16), resulting in the Shipper elements becoming a child of the ShipperCollection.

Attributing the Collection

[XmlElement(ElementName = "Shipper")]
public List<Shipper> Items

Collections and arrays within classes can also be controlled with a variety of attributes such as XmlArray and XmlArrayItem to dictate how the array and items should be serialized.

Manual Serialization

By default, ASP.NET will perform the serialization and schema creation for you, but you can do this yourself, if necessary, by implementing IXmlSerializable. For example, consider Listing 16.17, which implements this interface. Unlike ASP.NET 1.x, you don't use the GetSchema method to define the schema; instead, you specify the method that supplies the schema with the XmlSchemaProvider attribute on the class. The ReadXml and WriteXml methods perform the actual serialization.

Listing 16.17. Manually Serializing Classes

[XmlSchemaProvider("CustomShipperSchema")]
public class CustomShipper : IXmlSerializable
{
  // standard properties and methods

  public static XmlQualifiedName
    CustomShipperSchema(XmlSchemaSet set)
  {
    XmlSchema s = new XmlSchema();
    s.Id = "Test";
    s.TargetNamespace = "urn:types-nw-com";

    XmlSchemaComplexType t = new XmlSchemaComplexType();

    t.Name = "CustomShipper";
    XmlSchemaElement shipper = new XmlSchemaElement();
    shipper.Name = "shipper";
    XmlSchemaElement id = new XmlSchemaElement();
    id.Name = "id";
    id.Parent = shipper;
    XmlSchemaElement name = new XmlSchemaElement();
    name.Name = "shipperName";
    name.Parent = shipper;
    XmlSchemaElement phone = new XmlSchemaElement();
    phone.Name = "phone";
    phone.Parent = shipper;

    XmlQualifiedName n = new
      XmlQualifiedName(t.Name, s.TargetNamespace);
    shipper.SchemaTypeName = n;

    s.Items.Add(t);
    s.Items.Add(shipper);

    set.Add(s);

    return n;
  }

  // this is not used in v 2.0 of the framework
  // instead the method defined by the attribute sets the schema
  public XmlSchema GetSchema()
  {
      return null;
  }

  public void ReadXml(XmlReader reader)
  {
    XmlNodeType type = reader.MoveToContent();
  if (type == XmlNodeType.Element &&
    reader.LocalName == "shipper")
  {
    reader.ReadToDescendant("id");
    _id = int.Parse(reader.Value);
    reader.ReadToNextSibling("shipperName");
    _companyName = reader.Value;
    reader.ReadToNextSibling("phone");
    _phone = reader.Value;
  }
}

public void WriteXml(XmlWriter writer)
{
  writer.WriteStartElement("shipper", "urn:nw-com");
  writer.WriteElementString("id", _id.ToString());
  writer.WriteElementString("shipperName", _companyName);
  writer.WriteElementString("phone", _phone);
  writer.WriteEndElement();
}

This technique gives you complete control over the schema generated, and exactly what is serialized, and can be useful in interoperability scenarios.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows