Want to leave a question, comment, or some criticism? Click here!
1. Motivation
Actually, very few .Net developers that I've worked with know what a typed DataSet is let alone how to create one. It's one of those perplexing things that always baffles me as typed DataSets are not a bad way to make the best of the flexibility of DataSets while still offering design time support and compile time error checking (to some degree, of course).
At the core of the typed DataSet is an XML schema that describes the types and structure of the DataSet. While typed DataSets are great, if you ever look at the code, it's quite heavy and not necessarily the most optimized structure for over the wire transport. As even the discussion of typed DataSets narrows down the audience of developers that know how to work with schemas to generate a typed DataSet, the concept of generating entire object models using XML schemas is even less understood. Yes, indeed, the XSD.exe tool that ships with Visual Studio.Net can also be used to generate object classes from the same schemas (although they are a bit clumsy to work with).
You may be asking yourself why bother with schemas when you can just create the classes by hand or some other sort of visual code generation tool. Well, for some people (myself included), schemas seem a very natural way to express object models and classes. I mean, after all, the purpose of a schema is to define what an object (typically an XML document) looks like. But certainly, if that was it, it wouldn't really be worth the effort, now would it? For me, there is an added benefit in that it's very easy to define typed collections in schemas. On top of that, the generated classes come with various XML serialization attributes already marked up for you, which is handy if you plan on using the classes as data transfer objects for your web service.
2. Objective
Using XsdObjectGen.exe (a cousin of xsd.exe), create an example object model for an automobile service deparment. Our object model will be defined in an XML schema and we will use tools to generate the object classes. We'll begin with a simple example and move onto more complex topics like type reference and inheritance. This workshop is not meant as an introduction to XML schemas. More detailed coverage of XML schemas can be found on the official W3C website. A good primer for XML schemas can be found over at xml.com. Although I feel that in general, one does not need a thorough understanding of XML schemas to "get" this workshop.
3. Auxiliary Files/Prerequisites
To follow along, you will need the following:
- XsdObjectGen.exe. This tool is a more powerful version of xsd.exe insofar as generating object classes goes. It offers way more options and the generated code has a nicer programming interface than the code generated by xsd.exe (typed collecitons vs. typed arrays).
- Good ol' EditPlus. It's certainly not a requirement; you can also configure the external tools in Visual Studio.Net (in fact, that may be the preference). But I tend to work with non-C# code/markup in EditPlus, almost exclusively. You are also free to use the command line as well. If you'd like to use EditPlus, go ahead and install it now. In addition grab one of the schema syntax files.
- The zipped project (AcmeAutoService.005.zip). If you'd like, you can follow along by downloading the completed project. It can also serve as a reference point.
4. Configuring EditPlus
If you choose to use EditPlus for this workshop, head over to my "Using EditPlus as a TSQL Editor" workshop for a quick intro to EditPlus and how to set it up for use with external tools.
In EditPlus, I have the XsdObjectGen.exe mapped to a user tool.
The commandline options and switches for the executable can be found in the readme. The first step is to add the path to the executable to the "Command" textbox of the EditPlus user tool configuration pane. In my case, the path is:
C:\Program Files\XSDObjectGenerator\XSDObjectGen.exe
Next, we need to configure the command line switches for the executable.
"$(FilePath)" /n:$(FileNameNoExt) /f:"$(FileDir)/$(FileNameNoExt)" /l:cs
Of course, you are free to customize the parameters as you see fit. As used here, the parameters are:
- "$(FilePath)" is the token (and first argument) that represents the current working file. Note the double quotes around the token. This is necessary in case the path has spaces in it.
- /n is the namespace that the generated classes will be under. In this case, I've mapped it to the EditPlus expansion token "$(FileNameNoExt)" which translates to the name of the file minus the extension.
- /f is the output file name of the generated class file. In this case, I've mapped it to output to the same file directory and the same file name minus the extension (which is specified in the next argument).
- /l specifies the language that the generated code is in. In this case, we will generate C# code.
There are four other arguments/switches; I recommend experimenting with them to see if they offer any additional functionality for your scenario. For the purposes of this workshop, the above should be just fine.
5. A Simple Example
For our first example, we'll start out with a bare-bones schema that defines a Customer object. We'll start out with a bare schema (you can also use the one autogenerated for you when you add a schema to a project in Visual Studio).
Our barebones XSD schema.
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
</xs:schema>
The first step is to define what our Customer object will look like. Let's say for now, that we will define the following properties for our Customers:
- FirstName : String. The customer's first name.
- LastName : String. The customer's last name.
- Email : String. The customer's email address.
- DayContactNumber : String. The customer's day time contact number.
- EveningContactNumber : String. The customer's evening contact number.
- NumberOfPriorVisits : Int32. The number of prior visits by the customer.
- SendServiceReminders : Boolean. A Boolean value indicating whether the customer wants to receive automated service reminders.
While XSD may seem intimidating, it is, in fact, quite a simple and very straight forward (at least in the context of what we need it for). Our Customer object, when translated to the schema, looks very literal.
Schema with Customer type defined.
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--/// Customer type ///-->
<xs:complexType name="Customer">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="DayContactNumber" type="xs:string"/>
<xs:element name="EveningContactNumber" type="xs:string"/>
<xs:element name="NumberOfPriorVisits" type="xs:int"/>
<xs:element name="SendServiceReminders" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
You can find out more about how XSD data types are mapped to .Net data types by checking out the .Net DataSets documentation. Although note that the documentation isn't complete (as XsdObjectGen.exe will translate xs:anyType to Object).
Now if you run the tool, the class files that correspond to this schema definition will be automatically generated for you (I adjusted the line feeds and indentation for better presentation).
Generated C# code
// Copyright 2004, Microsoft Corporation
// Sample Code - Use restricted to terms of use defined in the accompanying
// license agreement (EULA.doc)
//--------------------------------------------------------------
// Autogenerated by XSDObjectGen version 1.3.6.0
// Schema file: ObjectModel.xsd
// Creation Date: 2/8/2006 9:27:25 PM
//--------------------------------------------------------------
using System;
using System.Xml.Serialization;
using System.Collections;
using System.Xml.Schema;
using System.ComponentModel;
namespace ObjectModel
{
public struct Declarations
{
public const string SchemaVersion =
"http://acmeautoservice.com/ObjectModel.xsd";
}
[XmlType(TypeName="Customer"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class Customer
{
[XmlElement(ElementName="FirstName",
IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __FirstName;
[XmlIgnore]
public string FirstName
{
get { return __FirstName; }
set { __FirstName = value; }
}
[XmlElement(ElementName="LastName",
IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __LastName;
[XmlIgnore]
public string LastName
{
get { return __LastName; }
set { __LastName = value; }
}
[XmlElement(ElementName="Email",
IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __Email;
[XmlIgnore]
public string Email
{
get { return __Email; }
set { __Email = value; }
}
[XmlElement(ElementName="DayContactNumber",
IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __DayContactNumber;
[XmlIgnore]
public string DayContactNumber
{
get { return __DayContactNumber; }
set { __DayContactNumber = value; }
}
[XmlElement(ElementName="EveningContactNumber",
IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __EveningContactNumber;
[XmlIgnore]
public string EveningContactNumber
{
get { return __EveningContactNumber; }
set { __EveningContactNumber = value; }
}
[XmlElement(ElementName="NumberOfPriorVisits",
IsNullable=false,DataType="int")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public int __NumberOfPriorVisits;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __NumberOfPriorVisitsSpecified;
[XmlIgnore]
public int NumberOfPriorVisits
{
get { return __NumberOfPriorVisits; }
set { __NumberOfPriorVisits = value;
__NumberOfPriorVisitsSpecified = true;
}
}
[XmlElement(ElementName="SendServiceReminders",
IsNullable=false,DataType="boolean")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __SendServiceReminders;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __SendServiceRemindersSpecified;
[XmlIgnore]
public bool SendServiceReminders
{
get { return __SendServiceReminders; }
set { __SendServiceReminders = value;
__SendServiceRemindersSpecified = true;
}
}
public Customer()
{
}
}
}
Generated VB.Net code.
' Copyright 2004, Microsoft Corporation
' Sample Code - Use restricted to terms of use defined in the accompanying
' license agreement (EULA.doc)
'--------------------------------------------------------------
' Autogenerated by XSDObjectGen version 1.3.6.0
' Schema file: ObjectModel.xsd
' Creation Date: 2/8/2006 9:20:17 PM
'--------------------------------------------------------------
Imports System
Imports System.Xml.Serialization
Imports System.Collections
Imports System.Xml.Schema
Imports System.ComponentModel
Namespace ObjectModel
Public Module Declarations
Public Const SchemaVersion As String = _
"http://acmeautoservice.com/ObjectModel.xsd"
End Module
'--------------------------------------------------
'Customer type
'--------------------------------------------------
<XmlType(TypeName:="Customer"),XmlRoot,Serializable, _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class Customer
'************ FirstName element ************
<XmlElement(ElementName:="FirstName", _
IsNullable:=false,DataType:="string"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __FirstName As String
<XmlIgnore> _
Public Property FirstName As String
Get
FirstName = __FirstName
End Get
Set(Value As String)
__FirstName = Value
End Set
End Property
'************ LastName element ************
<XmlElement(ElementName:="LastName", _
IsNullable:=false,DataType:="string"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __LastName As String
<XmlIgnore> _
Public Property LastName As String
Get
LastName = __LastName
End Get
Set(Value As String)
__LastName = Value
End Set
End Property
'************ Email element ************
<XmlElement(ElementName:="Email", _
IsNullable:=false,DataType:="string"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __Email As String
<XmlIgnore> _
Public Property Email As String
Get
Email = __Email
End Get
Set(Value As String)
__Email = Value
End Set
End Property
'************ DayContactNumber element ************
<XmlElement(ElementName:="DayContactNumber", _
IsNullable:=false,DataType:="string"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __DayContactNumber As String
<XmlIgnore> _
Public Property DayContactNumber As String
Get
DayContactNumber = __DayContactNumber
End Get
Set(Value As String)
__DayContactNumber = Value
End Set
End Property
'************ EveningContactNumber element ************
<XmlElement(ElementName:="EveningContactNumber", _
IsNullable:=false,DataType:="string"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __EveningContactNumber As String
<XmlIgnore> _
Public Property EveningContactNumber As String
Get
EveningContactNumber = __EveningContactNumber
End Get
Set(Value As String)
__EveningContactNumber = Value
End Set
End Property
'************ NumberOfPriorVisits element ************
<XmlElement(ElementName:="NumberOfPriorVisits", _
IsNullable:=False,DataType:="int"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __NumberOfPriorVisits As Integer
<XmlIgnore, _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __NumberOfPriorVisitsSpecified As Boolean
<XmlIgnore> _
Public Property NumberOfPriorVisits As Integer
Get
NumberOfPriorVisits = __NumberOfPriorVisits
End Get
Set(Value As Integer)
__NumberOfPriorVisits = Value
__NumberOfPriorVisitsSpecified = True
End Set
End Property
'************ SendServiceReminders element ************
<XmlElement(ElementName:="SendServiceReminders", _
IsNullable:=False,DataType:="boolean"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __SendServiceReminders As Boolean
<XmlIgnore, _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __SendServiceRemindersSpecified As Boolean
<XmlIgnore> _
Public Property SendServiceReminders As Boolean
Get
SendServiceReminders = __SendServiceReminders
End Get
Set(Value As Boolean)
__SendServiceReminders = Value
__SendServiceRemindersSpecified = True
End Set
End Property
'************ Constructor ************
Public Sub New()
End Sub
End Class
End Namespace
A quick glance at the generated code shows that the generator has spit out a set of fields and a set of properties for each of the fields. This is by design, apparently, due to the way reflection works in .Net with regards to properties. From the readme file:
Xml*Attribute is what maps the classes and fields to their xml counterpart. As of .NET v1.1, Xml*Attribute can not be applied to a private field or property, but only to a public field or property. Originally the plan was to only expose public properties, and have private __FieldName members within the class to store values. Unfortunately when applying the Xml*Attribute to public properties, the resultant XML generated cannot be guaranteed to be in the proper sequence due to the way .NET reflection works. Therefore Xml*Attributes are only applied to hidden public fields, not properties.
The programmer doesnt access the fields directly, which has the advantage of allowing the code to perform lazy instantiation of objects and value validation. Since the materialization of complex hierarchies is accomplished by the property get logic, always programming against properties is recommended. In all cases, therefore, a pairing of a public property and a hidden public field is used to control programmer and serializer access to data (respectively). The public field will have a __FieldName naming convention and should only be used to check for a null value. It turns out having the __FieldName members as public fields is advantageous to check for null in end-user code, without causing a child object to be created.
Note that in each case, the backer field is the one that's actually mapped to an XML element; all of the properties are marked with the XmlIgnore attribute which tells the serializer to ignore the property when serializing. Aside from this, also note that there are fields that we didn't define, the __SendServiceRemindersSpecified and the __NumberOfPriorVisitsSpecified field. What these two have in common is that they are both non-nullable types in .Net. For any non-nullable types in .Net, a Specified field will be generated which is used to indicate whether the value was set by the user or not.
6. Serialization/Deserialization in Action
Now that we've got our basic class set up, we can go ahead and test it out with an XmlSerializer. If you're not familiar with the concept of serialization/deserialization, simply put, it's the process by which a runtime object graph is moved into (serialize) or out of (deserialize) a stream. In our case, we will be moving our run time objects into and out of a TextStream (for more in depth info on serialization, check out Andrew Troelson's excellent Pro C# 2500 and the .Net 2.0 Platform). Copy/paste the code below or download the project from here.
Sample console serialization/deserialization.
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using ObjectModel;
namespace AcmeAutoService.Console {
/// <summary>
/// Sample usage of our object model.
/// </summary>
class SimpleApp {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args) {
SimpleApp app = new SimpleApp();
app.Run();
}
public void Run() {
// Create a customer object and populate some
// properties
Customer customer = new Customer();
customer.FirstName = "Charles";
customer.LastName = "Chen";
customer.DayContactNumber = "7325551234";
customer.EveningContactNumber = "7325552345";
customer.Email = "cchen@charliedigital.com";
customer.NumberOfPriorVisits = 5;
customer.SendServiceReminders = true;
// Create an XML serializer to serialize our
// customer to XML.
XmlSerializer serializer =
new XmlSerializer(typeof(Customer));
// The StringWriter we'll be serializing to.
StringWriter writer =
new StringWriter();
// Serialize the object to the StringWriter.
serializer.Serialize(writer, customer);
String seralizedCustomer = writer.ToString();
// Write to console.
System.Console.WriteLine(seralizedCustomer);
System.Console.WriteLine("Press any key to deserialize...");
System.Console.WriteLine("_______________________________________");
System.Console.ReadLine();
Customer customerCopy =
(Customer) serializer.Deserialize(
new StringReader(seralizedCustomer));
System.Console.WriteLine("FirstName: {0}", customerCopy.FirstName);
System.Console.WriteLine("LastName: {0}", customerCopy.LastName);
System.Console.WriteLine("Email: {0}", customerCopy.Email);
System.Console.WriteLine("_______________________________________");
// Exit.
System.Environment.Exit(0);
}
}
}
If you run this code, you should see the following results:
An interesting aspect of this is that it offers one way to implement ICloneable (albeit probably less performant than say using a BinaryFormatter) as customerCopy is an exact copy, but not reference to, the original customer. [AcmeAutoService.001.zip]
7. Advanced Concepts : Type Reference
Beyond creating simple objects, XML schemas also allow type references. So one complexType can have an element that refers to another complexType, just like any of the CLR languages. To demonstrate this, we'll add three new types to our schema. The first type, Vehicle will represent a customer's vehicle. The second type will be an Enum type that indicates when the standard colors that are sold.
New schema with Vehicle and Color types
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--/// Customer type ///-->
<xs:complexType name="Customer">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="DayContactNumber" type="xs:string"/>
<xs:element name="EveningContactNumber" type="xs:string"/>
<xs:element name="NumberOfPriorVisits" type="xs:int"/>
<xs:element name="SendServiceReminders" type="xs:boolean"/>
<xs:element name="Vehicle" type="Vehicle"/>
</xs:sequence>
</xs:complexType>
<!--/// Car type ///-->
<xs:complexType name="Vehicle">
<xs:sequence>
<xs:element name="ModelName" type="xs:string"/>
<xs:element name="Color" type="Color"/>
<xs:element name="Year" type="xs:int"/>
</xs:sequence>
</xs:complexType>
<!--/// Our color enumeration values ///-->
<xs:simpleType name="Color">
<xs:restriction base="xs:string">
<xs:enumeration value="Black" />
<xs:enumeration value="Silver" />
<xs:enumeration value="White" />
<xs:enumeration value="Red" />
<xs:enumeration value="Blue" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
Note that, unlike with the reference types, the Color type is a value type and represented with a simpleType in the schema (as opposed to complexType). Generating the code again yields:
Newly generated classes and Enum
[Serializable]
public enum Color
{
[XmlEnum(Name="Black")] Black,
[XmlEnum(Name="Silver")] Silver,
[XmlEnum(Name="White")] White,
[XmlEnum(Name="Red")] Red,
[XmlEnum(Name="Blue")] Blue
}
[XmlType(TypeName="Customer"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class Customer
{
// Other fields and properties excluded...
[XmlElement(Type=typeof(Vehicle),ElementName="Vehicle",IsNullable=false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public Vehicle __Vehicle;
[XmlIgnore]
public Vehicle Vehicle
{
get
{
if (__Vehicle == null) __Vehicle = new Vehicle();
return __Vehicle;
}
set {__Vehicle = value;}
}
}
[XmlType(TypeName="Vehicle"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class Vehicle
{
[XmlElement(ElementName="ModelName",IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __ModelName;
[XmlIgnore]
public string ModelName
{
get { return __ModelName; }
set { __ModelName = value; }
}
[XmlElement(ElementName="Color",IsNullable=false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public Color __Color;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __ColorSpecified;
[XmlIgnore]
public Color Color
{
get { return __Color; }
set { __Color = value; __ColorSpecified = true; }
}
[XmlElement(ElementName="Year",IsNullable=false,DataType="int")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public int __Year;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __YearSpecified;
[XmlIgnore]
public int Year
{
get { return __Year; }
set { __Year = value; __YearSpecified = true; }
}
public Vehicle()
{
ModelName = string.Empty;
__ColorSpecified = true;
__YearSpecified = true;
}
}
If we modify our code a bit (just create a Vehicle object and set the property on the customer) and run it again, this is the output that we get: [AcmeAutoService.002.zip]
8. Advanced Concepts : Collections
As I mentioned previously, XsdObjectGen.exe will also create typed collections (which inherit from ArrayList, which is good, because you can use BinarySearch with a custom IComparer) for you. Doing so is as simple as adding one new attribute to our Customer object.
Creating typed collections
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--/// Customer type ///-->
<xs:complexType name="Customer">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="DayContactNumber" type="xs:string"/>
<xs:element name="EveningContactNumber" type="xs:string"/>
<xs:element name="NumberOfPriorVisits" type="xs:int"/>
<xs:element name="SendServiceReminders" type="xs:boolean"/>
<xs:element name="Vehicle" type="Vehicle" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!--/// Other types omitted... ///-->
</xs:schema>
Newly generated code (with portions omitted)
[Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class VehicleCollection : ArrayList
{
public Vehicle Add(Vehicle obj)
{
base.Add(obj);
return obj;
}
public Vehicle Add()
{
return Add(new Vehicle());
}
public void Insert(int index, Vehicle obj)
{
base.Insert(index, obj);
}
public void Remove(Vehicle obj)
{
base.Remove(obj);
}
new public Vehicle this[int index]
{
get { return (Vehicle) base[index]; }
set { base[index] = value; }
}
}
[XmlType(TypeName="Customer"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class Customer
{
// Other properties and fields omitted
[XmlElement(Type=typeof(Vehicle),ElementName="Vehicle",IsNullable=false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public VehicleCollection __VehicleCollection;
[XmlIgnore]
public VehicleCollection VehicleCollection
{
get
{
if (__VehicleCollection == null) __VehicleCollection =
new VehicleCollection();
return __VehicleCollection;
}
set {__VehicleCollection = value;}
}
public Customer()
{
FirstName = string.Empty;
LastName = string.Empty;
Email = string.Empty;
DayContactNumber = string.Empty;
EveningContactNumber = string.Empty;
__NumberOfPriorVisitsSpecified = true;
__SendServiceRemindersSpecified = true;
}
}
// Other classes omitted
Note that in this case, we already had a reference to the type to create the collection with. What if you just want to create a standalone type with no references? Well, the only way that I've found to do this is to create a new complex type (I'll typically name it the plural form of the singular object) and add a single element to it with the maxOccurs set to unbounded. Alternatively, you can use syntax like so:
Schema layout to create standalone collection
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--///
Standalone Vehicle collection type (illustration purposes only)
///-->
<xs:complexType name="Vehicles">
<xs:sequence>
<xs:element name="Vehicle">
<xs:complexType>
<xs:sequence>
<xs:element name="ModelName" type="xs:string"/>
<xs:element name="Color" type="Color"/>
<xs:element name="Year" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:sequence>
</xs:complexType>
</xs:schema>
Note that using this syntax, you cannot directly refer to the Vehicle type from your schema. If you need to reference the object, then simply use two complex types (one for the object, one for the collection). Unfortunately, using either syntax also generates a weird Vehicles class which acts as a wrapper around the typed collection. It's not all bad, I guess, as it allows for lazy initialization of the collection and, in certain situations, you may need a wrapper around your collection (we'll get to this in one sec.).
With our updated code, if I add some vehicles to Customer, I get the following output:
If we had used a wrapper (to create a standalone collection), this is the output we would get:
For the remainder of this workshop, we'll assume that we're not working with the standalone collection type. [AcmeAutoService.003.zip]
9. Advanced Concepts : Inheritance
Beyond simply creating objects, collections, and Enums, we can also create hierarchies. Yup, that's right, you can also specify inheritance using the schema. For this exercise, we'll be modifying our Vehicle type and adding two sub types: Sedans, Coupes, and SUVs. Let's assume that AWD is an option that's only available on SUVs and Coupes may have either 2 or 4 seats (we assume that all other Vehicles have 4 seats).
To accomplish this, we'll be making use of the complexContent and extension elements. The modified schema looks like so:
Modifed schema with inheriting types
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://acmeautoservice.com/ObjectModel.xsd"
xmlns="http://acmeautoservice.com/ObjectModel.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--/// Customer type ///-->
<xs:complexType name="Customer">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="DayContactNumber" type="xs:string"/>
<xs:element name="EveningContactNumber" type="xs:string"/>
<xs:element name="NumberOfPriorVisits" type="xs:int"/>
<xs:element name="SendServiceReminders" type="xs:boolean"/>
<xs:element name="Vehicle" type="Vehicle" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!--/// Vehicle type ///-->
<xs:complexType name="Vehicle" abstract="true">
<xs:sequence>
<xs:element name="ModelName" type="xs:string"/>
<xs:element name="Color" type="Color"/>
<xs:element name="Year" type="xs:int"/>
<xs:element name="Doors" type="xs:int"/>
</xs:sequence>
</xs:complexType>
<!--/// Sedan type ///-->
<xs:complexType name="Sedan">
<xs:complexContent>
<xs:extension base="Vehicle" />
</xs:complexContent>
</xs:complexType>
<!--/// Coupe type ///-->
<xs:complexType name="Coupe">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="NumberOfSeats"
type="xs:int" default="2"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!--/// SUV type ///-->
<xs:complexType name="SUV">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="IsAllWheelDrive"
type="xs:boolean" default="false"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!--/// Our color enumeration values ///-->
<xs:simpleType name="Color">
<xs:restriction base="xs:string">
<xs:enumeration value="Black" />
<xs:enumeration value="Silver" />
<xs:enumeration value="White" />
<xs:enumeration value="Red" />
<xs:enumeration value="Blue" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
When the new code is generated, we can see that the Vehicle class is now abstract. Also notice that the Customer type now includes additional attributes.
Now with inheritance (some code omitted)
// Code omitted...
[XmlType(TypeName="Customer"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[XmlInclude(typeof(SUV))]
[XmlInclude(typeof(Coupe))]
[XmlInclude(typeof(Sedan))]
public class Customer
{
// Code omitted...
}
[XmlType(TypeName="Vehicle"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public abstract class Vehicle
{
[XmlElement(ElementName="ModelName",IsNullable=false,DataType="string")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string __ModelName;
[XmlIgnore]
public string ModelName
{
get { return __ModelName; }
set { __ModelName = value; }
}
[XmlElement(ElementName="Color",IsNullable=false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public Color __Color;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __ColorSpecified;
[XmlIgnore]
public Color Color
{
get { return __Color; }
set { __Color = value; __ColorSpecified = true; }
}
[XmlElement(ElementName="Year",IsNullable=false,DataType="int")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public int __Year;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __YearSpecified;
[XmlIgnore]
public int Year
{
get { return __Year; }
set { __Year = value; __YearSpecified = true; }
}
[XmlElement(ElementName="Doors",IsNullable=false,DataType="int")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public int __Doors;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __DoorsSpecified;
[XmlIgnore]
public int Doors
{
get { return __Doors; }
set { __Doors = value; __DoorsSpecified = true; }
}
public Vehicle()
{
ModelName = string.Empty;
__ColorSpecified = true;
__YearSpecified = true;
__DoorsSpecified = true;
}
}
[XmlType(TypeName="SUV"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Always)]
public class SUV : Vehicle
{
[XmlElement(ElementName="IsAllWheelDrive",IsNullable=false,DataType="boolean")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __IsAllWheelDrive;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __IsAllWheelDriveSpecified;
[XmlIgnore]
public bool IsAllWheelDrive
{
get { return __IsAllWheelDrive; }
set { __IsAllWheelDrive = value; __IsAllWheelDriveSpecified = true; }
}
public SUV() : base()
{
IsAllWheelDrive = false;
}
}
[XmlType(TypeName="Coupe"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Always)]
public class Coupe : Vehicle
{
[XmlElement(ElementName="NumberOfSeats",IsNullable=false,DataType="int")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public int __NumberOfSeats;
[XmlIgnore]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool __NumberOfSeatsSpecified;
[XmlIgnore]
public int NumberOfSeats
{
get { return __NumberOfSeats; }
set { __NumberOfSeats = value; __NumberOfSeatsSpecified = true; }
}
public Coupe() : base()
{
NumberOfSeats = 2;
}
}
[XmlType(TypeName="Sedan"),XmlRoot,Serializable]
[EditorBrowsable(EditorBrowsableState.Always)]
public class Sedan : Vehicle
{
public Sedan() : base()
{
}
}
Accordingly, we also have to modify our code since we can no longer create instances of Vehicle since it's an abstract type now.
Modified code block (portion