16.4 控制数据在Web服务中如何传输
从Web服务调用返回数据时,数据会串行化为XML,这说明,只能返回基本类型,或者是能自行串行化的类型(如DataSet)。如果有一个强类型的业务层,而且希望把它作为一个Web方法的结果返回,首先要做的是确保这个类有一个无参数构造函数,否则它将无法串行化。
接下来,必须确保希望串行化的所有属性都是可读写的,因为只读属性在串行化过程中会被忽略(方法也是一样)。考虑代码清单16-8,这里显示了一个用于Web服务中的简单类。
代码清单16-8 Shipper类
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; }
}
}
当Web服务返回这个类的一个实例时,XML中对应各属性分别包含一个元素,如代码清单16-9所示。
代码清单16-9 串行化的Shipper类
<?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>
16.4.1 定制串行化
通过对类和属性指定性质,可以改变生成XML的方式。例如,考虑代码清单16-10。这个类有一个XmlRoot性质,它定义了根元素的名。ID属性有XmlAttribute性质,这指定了AttributeName,使得ID属性成为父元素上的一个XML属性。Phone属性有XmlElement性质,指定了ElementName,并指定它可以包含null值。
代码清单16-10 对Shipper类增加性质完成串行化
[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; }
}
}
从Web服务返回Shipper类时,会考虑这些定制性质,从而得到代码清单16-11中所示的XML。
代码清单16-11 定制的串行化Shipper类
<?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>
16.4.2 串行化集合
集合会串行化为结构化XML,其父元素的默认名为ArrayOfClass,这里Class是串行化的类的类名。例如,代码清单16-12 显示了一个Web服务的结果,它返回Shipper对象的一个泛型列表(List<Shipper>)。
代码清单16-12 串行化的集合
<?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>
可以通过向Web方法增加一个额外的性质来控制顶层元素。例如,如代码清单16-13所示,可以指定所返回元素的名。
代码清单16-13 指定返回元素名
[WebMethod]
[return: XmlElement("Shippers")]
public List<Shipper> GetShippers()
顶级元素不再是ArrayOfShipper,而是Shippers。注意,看上去这只会影响SOAP请求,而不影响HTTP POST请求,Web服务帮助页面使用的是后者。可以检查WSDL或帮助页面的示例输出来加以区别。
还可以使用XmlElement性质从生成的XML去除一个间接层。例如,考虑代码清单16-14中的类,这是Shipper对象的一个集合。
代码清单16-14 ShipperCollection类
public class ShipperCollection
{
private List<Shipper> _items;
public ShipperCollection()
{
_items = new List<Shipper>();
}
public List<Shipper> Items
{
get { return _items; }
set { _items = value; }
}
}
代码清单16-15显示了从一个Web方法返回ShipperCollection类的结果,可以看到, 这里有一个多余的Items元素。
代码清单16-15 串行化的ShipperCollection类
<?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>
ShipperCollection是根元素,其Items属性有一个元素,每个Shipper又各有自己的一个元素。要删除多余的Items元素,可以在集合中Items属性上使用XmlElement性质(如代码清单16-16所示),其结果是Shipper元素将成为ShipperCollection的子元素。
代码清单16-16 对集合加性质
[XmlElement(ElementName = "Shipper")]
public List<Shipper> Items
还可以用很多其他性质来控制类中的集合和数组,如使用XmlArray和XmlArrayItem来指示如何串行化数组和数组元素。
16.4.3 手动地串行化
默认情况下,ASP.NET会为你完成串行化和模式创建,不过如果必要,你也可以自行完成,为此要实现IXmlSerializable接口。例如,考虑代码清单16-17,它就实现了这个接口。不同于ASP.NET 1.x,这里没有使用GetSchema方法来定义模式,而是在类上用XmlSchemaProvider性质来指定提供模式的方法。ReadXml和WriteXml方法将完成具体的串行化。
代码清单16-17 手动串行化类
[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();
}
利用这个技术,就能充分控制所生成的模式,还能控制对什么串行化,这对于需要互操作性的情况很有用。







