WCF Services—XML Serialization
| CSharp-Online.NET:Articles |
| .NET Articles |
| © 2007 Chris Peiris, Dennis Mulder |
XML Serialization
WCF supports two primary means of XML serialization. For a majority of scenarios, the DataContract attribute and its corresponding DataContractSerializer type are the preferred means of providing this requirement. However, the secondary method, the XmlSerializerFormat attribute, provides finer control over the XML serialization process. Additionally, by providing your own implementation of IXmlSerializable, effectively overriding .NET default serialization, you can control serialization entirely.
We will stress that you can use the data contract capabilities most of the time when developing enterprise applications. This is especially true when you control, or at least influence, both sides of the wire. Even if you don’t have influence on both sides of the wire, you probably can gain enough control to emit the XML as required by leveraging data contracts.
In Listing 4-13, the solution (Example07) has been expanded to include a concrete Trade class. This class represents the object (or message) that is presented to the exchange for requesting execution on a market order.
Listing 4-13. TradeService Interface with Trade Parameter
[ServiceContract( Namespace = "http://PracticalWcf/Exchange/TradeService", Name = "TradeService", SessionMode = SessionMode.Required) ] public interface ITradeService { [OperationContract( IsOneWay = false, Name = "TradeSecurityAtMarket" )] decimal TradeSecurity( Trade trade ); }
The TradeSecurity interface is updated to take a Trade object and return a decimal result. Also recognize that the Name parameter on the operation is TradeSecurityAtMarket. We chose the name of the operation to override the default of TradeSecurity and provide a distinction of a market order vs. limit orders on the metadata.
The Trade class looks like Listing 4-14 (notice the absence of either a Serializable attribute or a DataContract attribute at the top of the class).
Listing 4-14. First Few Lines of Trade Class
namespace ExchangeService { public class Trade { string _ticker; char _type; string _publisher; string _participant; decimal _quotedPrice; int _quantity; DateTime _tradeTime; decimal _executionAmount; /// <summary> /// Primary exchange security identifier /// </summary> public string Ticker { get { return _ticker; } set { _ticker = value; } }
If you launch the ASP.NET development server and view TradeService.svc in the browser, you’ll see the error shown in Figure 4-8.
![]()
Figure 4-8. Error page for nonserializable or missing data contract
At this point, WCF doesn’t know what to do. Therefore, let’s apply the Serializable attribute to the Trade type and take a look at the generated schema, as shown in Listing 4-15.
Listing 4-15. Trade Type with the Serializable Attribute
namespace ExchangeService { [Serializable] public class Trade { string _ticker; char _type; string _publisher; ...
To view the generated schema for the modified contract, first navigate to the following page: http://localhost:8888/ExchangeWeb/TradeService.svc?wsdl. Once at that page, if you locate the schema import, using the XPath /wsdl:definitions/wsdl:import, you’ll see another reference to a schema. You need to load that schema as well. That location should be, depending upon your host and IP port, as follows:
http://localhost:8888/ExchangeWeb/TradeService.svc?wsdl=wsdl0.
Note Again, you need to first open the base WSDL and search for the <wsdl:import> element, which will provide the correct location for the imported WSDL.
Notice the addition of the wsdl0 parameter to the original WSDL request. Viewing that page, you should see something that contains XML and is similar to Listing 4-16.
Listing 4-16. TradeService WSDL Definition
<xsd:import
schemaLocation="http://localhost:8888/ExchangeWeb/TradeService.svc?xsd=xsd2"
namespace="http://schemas.datacontract.org/2004/07/ExchangeService" />
You need to go a little deeper, opening the schemaLocation URL from Listing 4-16 to get to the type’s schema. If you browse to the schemaLocation from Listing 4-16, the code in Listing 4-17 appears.
Listing 4-17. Trade Schema Contract-Only Serializable (Trade.cs)
<?xml version="1.0" encoding="utf-8"?> <xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/ExchangeService" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/ExchangeService" xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/"> <xs:import schemaLocation="http://localhost:8888/ExchangeWeb/TradeService.svc?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/> <xs:complexType name="Trade"> <xs:sequence> <xs:element name="_executionAmount" type="xs:decimal"/> <xs:element name="_participant" nillable="true" type="xs:string"/> <xs:element name="_publisher" nillable="true" type="xs:string"/> <xs:element name="_quantity" type="xs:int"/> <xs:element name="_quotedPrice" type="xs:decimal"/> <xs:element name="_ticker" nillable="true" type="xs:string"/> <xs:element name="_tradeTime" type="xs:dateTime"/> <xs:element name="_type" type="ser:char"/> </xs:sequence> </xs:complexType> <xs:element name="Trade" nillable="true" type="tns:Trade"/> </xs:schema>
First, note the targetNamespace that was used. Since you didn’t override the namespace using .NET XML serialization support, you get what DataContractSerializer defaults to—http://schemas.data.coontract.org/2004/07/<serviceName>. This is probably not desired. We’ll get to this issue in a moment.
Second, the elements chosen by DataContractSerializer aren’t the public properties but the fields (private or public) along with the underscore as part of the name; this is also an undesirable result. This is the default behavior, and fortunately you can control this by utilizing the XML serialization support that’s part of the .NET Framework.
Finally, note the order of the elements—they’re in alphabetical order, which is the default processing rule for DataContractSerializer.
Note This code is provided in the Begin folder as part of Example07 on the Apress website (www.apress.com).
To control the WSDL generation, you need to switch from using DataContractSerializer to instead leveraging XmlSerializer; you can do this by decorating the service contract, at the interface level, with the XmlSerializerFormat attribute, as shown in Listing 4-18.
Listing 4-18. TradeService with XmlSerializer Support (TradeService.cs)
namespace ExchangeService { [ServiceContract( Namespace = "http://PracticalWcf/Exchange/TradeService", Name = "TradeService", SessionMode = SessionMode.Required) ] [XmlSerializerFormat( Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)] public interface ITradeService { [OperationContract( IsOneWay = false, Name = "TradeSecurityAtMarket" )] decimal TradeSecurity( Trade trade ); }
Now, if you rerequest the imported namespace using the following URL, you’ll see the schema updated with your targetNamespace attribute (check the schema import in the generated WSDL for the correct location):
http://localhost:8888/ExchangeWeb/TradeService.svc?xsd=xsd0
Note Again, we need to emphasize that to find the nested WSDL, you must search the base WSDL for the <wsdl:import> element and then the nested import of the type schema shown in this step.
Listing 4-19 shows the new schema.
Listing 4-19. New Schema with XmlSerializer Support
<?xml version="1.0" encoding="utf-8"?> <xs:schema elementFormDefault="qualified" targetNamespace="http://PracticalWcf/Exchange/TradeService" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://PracticalWcf/Exchange/TradeService"> <xs:import schemaLocation="http://localhost:8888/ExchangeWeb/TradeService.svc?xsd=xsd1" namespace="http://microsoft.com/wsdl/types/"/> <xs:element name="TradeSecurityAtMarket"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="trade" type="tns:Trade"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="Trade"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="Ticker" type="xs:string"/> <xs:element minOccurs="1" maxOccurs="1" name="Type" type="q1:char" xmlns:q1="http://microsoft.com/wsdl/types/"/> <xs:element minOccurs="0" maxOccurs="1" name="Publisher" type="xs:string"/> <xs:element minOccurs="0" maxOccurs="1" name="Participant" type="xs:string"/> <xs:element minOccurs="1" maxOccurs="1" name="QuotedPrice" type="xs:decimal"/> <xs:element minOccurs="1" maxOccurs="1" name="Quantity" type="xs:int"/> <xs:element minOccurs="1" maxOccurs="1" name="TradeTime" type="xs:dateTime"/> <xs:element minOccurs="1" maxOccurs="1" name="ExecutionAmount" type="xs:decimal"/> </xs:sequence> </xs:complexType> <xs:element name="TradeSecurityAtMarketResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="1" maxOccurs="1" name="TradeSecurityAtMarketResult" type="xs:decimal"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
You now have a targetNamespace that reflects the namespace requirement; additionally, the elements within the Trade complexType are all the public property names and types. The XmlSerializer includes only the public properties or fields; additionally, they are serialized in the order presented in the class as requested through reflection.
Note This code is provided in the accompanying code as Step1 in Example07.
It’s possible to gain further control over the generated schema by continuing to leverage the capabilities of .NET XML serialization. If you want to modify or exclude public properties or fields and control the order and nullability, you can use the various attributes as described in the MSDN documentation under the topic "Attributes That Control XML Serialization." As a quick example just for fun, let’s exclude the participant, modify the element name for TradeTime, and cause Ticker to be an attribute instead of an XML element. The example code now generates a schema, as shown in Listing 4-20.
Tip To get a full understanding of the capabilities of XML serialization in .NET, please refer to MSDN and search for attributes that control XML serialization.
Listing 4-20. Trade Schema Using XML Serialization Control Attributes
<xs:complexType name="Trade"> <xs:sequence> <xs:element minOccurs="1" maxOccurs="1" name="Type" type="q1:char" xmlns:q1="http://microsoft.com/wsdl/types/"/> <xs:element minOccurs="0" maxOccurs="1" name="Publisher" type="xs:string"/> <xs:element minOccurs="1" maxOccurs="1" name="QuotedPrice" type="xs:decimal"/> <xs:element minOccurs="1" maxOccurs="1" name="Quantity" type="xs:int"/> <xs:element minOccurs="1" maxOccurs="1" name="ExecutionTime" type="xs:dateTime"/> <xs:element minOccurs="1" maxOccurs="1" name="ExecutionAmount" type="xs:decimal"/> </xs:sequence> <xs:attribute name="Ticker" type="xs:string"/> </xs:complexType>
You’ll notice now that the Ticker property appears as an XML Schema attribute instead of an element, the property TradeTime is now ExecutionTime, and the element Participant no longer appears in the schema.
Note The complete solution is provided in the accompanying code in the folder End as part of Example07.
So, with XmlSerialization support through the use of the XmlSerializer attribute on the service contract, it is possible to gain control over the XML Schema generation. Along with the ultimate extensibility by the implementation of the .NET interface IXmlSerializable on your .NET class, the capabilities to support just about any format required are present.
|

