The XML Schema Definition Language is a highly
successful industry standard that has received broad cross-platform
support within, and well beyond, the SOA industry. With this language
you can use industry standard markup syntax to not only express the
structure and validation rules of business documents, but you can also
use a series of built-in data types to represent the actual data. This
allows you to define complete data models in a manner that is
independent of any proprietary database or data representation
technology.
Canonical Schema establishes standardized XML Schema definitions (Figure 1), which makes this a pattern that can be applied in direct support of Standardized Service Contract .
Schema Centralization ,
a related pattern that mandates that schema definitions be shared among
service contracts, can be applied together with Canonical Schema
to help create a flexible and streamlined data architecture that acts
as a foundation layer for a set of federated service contracts (Figure 2).
The abstraction achieved by such a data architecture allows you to
build underlying services using .NET, Java, or any other back-end
implementation platform that supports XML Schema processing.
Creating Schemas with Visual Studio
To
build canonical XML schemas that we will eventually want to centralize,
we need to master the XML Schema Definition Language and customize
these schemas using an editor, such as the one provided by Visual
Studio.
Let’s now put together some simple schemas. Figure 3
shows a preview of the person schema displayed in the Visual Studio
2010 Schema View. We can also refer to the person schema as the person type, because it essentially establishes a complex type for the person entity.
We’ll begin with putting together the gender type first:
Example 1.
<xs:schema xmlns:xs=http://www.w3.org/2001/XMLSchema targetNamespace="http://schemas.example.org/ enterprise/models/v1" xmlns="http://schemas.example.org/ enterprise/models/v1" xmlns:mstns="http://schemas.example.org/ enterprise/models/v1" version="1.0.1" elementFormDefault="qualified"> <xs:simpleType name="gender"> <xs:restriction base="xs:string"> <xs:enumeration value="male" /> </xs:enumeration value="female" /> </xs:restriction> </xs:simpleType> </xs:schema>
|
The
validation logic in this schema only accepts string values that are
“male” and “female.” The gender type is saved as a separate XML schema
file called gender.xsd.
Next, let’s create the address type:
Example 2.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://schemas.example.org/ enterprise/models/v1" xmlns="http://schemas.example.org/ enterprise/models/v1" xmlns:mstns="http://schemas.example.org/ enterprise/models/v1" version="1.0.1" elementFormDefault="qualified"> <xs:complexType name="address"> <xs:sequence> <xs:element name="address" type="xs:string" /> <xs:element name="city" type="xs:string" /> <xs:element name="state" type="xs:string" /> <xs:element name="zip" type="xs:string" /> <xs:element name="phone" type="xs:string" /> <xs:element name="country" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema>
|
The address type accepts strings
for all of its child elements. This type is also saved in a separate
schema file (called address.xsd).
Finally, here’s the content for the person type, which is stored in the person.xsd file:
Example 3.
<xs:schema xmlns:xs= "http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.example.org/ enterprise/models/v1" targetNamespace="http://schemas.example.org/ enterprise/models/v1" xmlns:mstns="http://schemas.example.org/ enterprise/models/v1" version="1.1.20" elementFormDefault="qualified"> <xs:include schemaLocation="Gender.xsd"/> <xs:include schemaLocation="Address.xsd"/> <xs:complexType name="person"> <xs:sequence> <xs:element name="ID" type="xs:positiveInteger" /> <xs:element name="foreName" type="xs:string" minOccurs="1"/> <xs:element name="middleName" type="xs:string" /> <xs:element name="surName" type="xs:string" minOccurs="1"/> <xs:element name="socSecNr" type="xs:unsignedLong" minOccurs="1" maxOccurs="1"/> <xs:element name="jobTitle" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="address" type="tns:address" /> <xs:element name="gender" type="tns:gender" /> </xs:sequence> </xs:complexType> <xs:element name="person" type="tns:person" /> </xs:schema>
|
Note how the types of elements address and gender refer to the types created in the address.xsd and gender.xsd respectively. Two statements are necessary in order to make the address element refer to the type defined in the address.xsd file. The first refers to the previously created schema:
<xs:include schemaLocation="address.xsd"/>
The second specifies that the element address should be of this type:
<xs:element name="address" type="tns:address"/>
Also note that there is an
element in the person XML schema that has the type of person. This makes
it possible to send the person type as a message. The gender and
address types do not have such an element because we are not planning to
use them on their own to define individual messages.
At this point our catalog of XML schemas looks like this:
Generating .NET Types
The types we have created
can be used “as is” for message definitions in BizTalk, but to use them
for WCF (or ASMX) services, we need to generate .NET types using a
utility program called svcutil that is shipped with Visual Studio. To
generate these types, use the following statement at the Visual Studio
command prompt:
svcutil /dcOnly /l:cs person.xsd address.xsd gender.xsd
Apart from referring to the previously created XML schema files, we are also using these two switches:
The code that scvutil generates based upon our types looks like this:
Example 4.
namespace schemas.example.org.enterprise.models.v1 { using System.Runtime.Serialization; [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Runtime.Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute (Name = "person", Namespace = "http://schemas.example.org/enterprise/models/v1")] public partial class person : object, System.Runtime.Serialization.IExtensibleDataObject { private System.Runtime.Serialization. ExtensionDataObject extensionDataField; private long idField; private string foreNameField; private string middleNameField; private string surNameField; private ulong socSecNrField; private string jobTitleField; private schemas.example.org.enterprise.models.v1. address addressField; private schemas.example.org.enterprise.models.v1. gender genderField; public System.Runtime.Serialization. ExtensionDataObject ExtensionData { get { return this.extensionDataField; } set { this.extensionDataField = value; } } [System.Runtime.Serialization. DataMemberAttribute(IsRequired = true)] public long id { get { return this.idField; } set { this.idField = value; } } [System.Runtime.Serialization. DataMemberAttribute(IsRequired = true, EmitDefaultValue = false, Order = 1)] public string foreName { get { return this.foreNameField; } set { this.foreNameField = value; } } [System.Runtime.Serialization. DataMemberAttribute(IsRequired = true, EmitDefaultValue = false, Order = 2)] public string middleName { get { return this.middleNameField; } set { this.middleNameField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false, Order = 3)] public string surName { get { return this.surNameField; } set { this.surNameField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, Order = 4)] public ulong socSecNr { get { return this.socSecNrField; } set { this.socSecNrField = value; } } [System.Runtime.Serialization.DataMemberAttribute (EmitDefaultValue = false, Order = 5)] public string jobTitle { get { return this.jobTitleField; } set { this.jobTitleField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false, Order = 6)] public schemas.example.org.enterprise.models.v1.address address { get { return this.addressField; } set { this.addressField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, Order = 7)] public schemas.example.org.enterprise.models.v1.gender gender { get { return this.genderField; } set { this.genderField = value; } } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Runtime.Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute (Name = "address", Namespace = "http://schemas.example.org/ enterprise/models/v1")] public partial class address : object, System.Runtime.Serialization.IExtensibleDataObject { private System.Runtime.Serialization. ExtensionDataObject extensionDataField; private string addressMemberField; private string cityField; private string stateField; private string zipField; private string phoneField; private string countryField; public System.Runtime.Serialization. ExtensionDataObject ExtensionData { get { return this.extensionDataField; } set { this.extensionDataField = value; } } [System.Runtime.Serialization.DataMemberAttribute (Name = "address", IsRequired = true, EmitDefaultValue = false)] public string addressMember { get { return this.addressMemberField; } set { this.addressMemberField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false)] public string city { get { return this.cityField; } set { this.cityField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false)] public string state { get { return this.stateField; } set { this.stateField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false)] public string zip { get { return this.zipField; } set { this.zipField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false, Order = 4)] public string phone { get { return this.phoneField; } set { this.phoneField = value; } } [System.Runtime.Serialization.DataMemberAttribute (IsRequired = true, EmitDefaultValue = false, Order = 5)] public string country { get { return this.countryField; } set { this.countryField = value; } } } [System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Runtime.Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute (Name = "gender", Namespace = "http://schemas. example.org/enterprise/models/v1")] public enum gender : int { [System.Runtime.Serialization. EnumMemberAttribute()] male = 0, [System.Runtime.Serialization. EnumMemberAttribute()] female = 1, } }
|
Note
As
an alternative to the svcutil utility, you can also use the xsd.exe
utility, which will create .NET types that are serialized using the XMLSerializer rather than the DataContractSerializer.
Using the DataContract Library
You can also create these
types by using code directly with .NET, instead of working with XML
schema markup code. This next example shows how to create the three
types in .NET using the DataContract and DataMember attributes.
Example 5.
[DataContract(Name = "person", Namespace = "http://schemas.example.org/enterprise/models/v1")] public class Person : object, System.Runtime.Serialization.IExtensibleDataObject { [DataMember(IsRequired = true)] public int Id { get; set; } [DataMember(IsRequired=true, EmitDefaultValue=false, Order = 1)] public string ForeName { get; set; } [DataMember(IsRequired = true, EmitDefaultValue=false, Order = 2)] public string MiddleName { get; set; } [DataMember(IsRequired = true, EmitDefaultValue=false, Order = 3)] public string SurName { get; set; } [DataMember(IsRequired = true, EmitDefaultValue=false, Order = 4)] public ulong SocSecNr { get; set; } [DataMember(Order = 5)] public string JobTitle { get; set; } [DataMember(Order = 6)] public Address Address { get; set; } [DataMember(Order = 7)] public Gender Gender { get; set; } private System.Runtime.Serialization. ExtensionDataObject extensionDataField; public System.Runtime.Serialization. ExtensionDataObject ExtensionData { get { return this.extensionDataField; } set { this.extensionDataField = value; } }
|
The address and gender types are created as separate classes that use DataContract and DataMember attributes. The DataContract attribute is used to let .NET know that this is a data contract and that it should be serialized using DataContractSerializer. This serializer will only serialize fields that are annotated with the DataMember attribute.
Apart from signaling to the DataContractSerializer that a particular element should be serialized, the DataMember attribute accepts named arguments.
Specifically, the named arguments used in Example 5 are:
Order – affects the order in which the fields of the type are serialized
IsRequired – instructs consumers and services whether or not a field is required
EmitDefailtValue – when set to false, the default value (for example, 0 for an int) will not be serialized
The DataMember
attribute has several other arguments that can come in handy. For
example, should you require a different name in the .NET code and the
serialized message, the Name attribute lets you change the name of the serialized value.
Figure 5 shows the resulting library created so far.