Basic Contract-First WebServices with XSDObjectGen

by Peter A. Bromberg, Ph.D.

Peter Bromberg
"Drove my Chevy to the Levee and ... Uh-Oh....". -Anonymous

I have written and blogged several times before about my strong support of the "Contract First" Code generation pattern. Rather than launch into a treatise of why Contract First is good, I'll let the XML / OOP luminaries say their piece, which you can easily find on the web by searching for "Contract First".

In a nutshell, .NET provides the necessary tool support for contract-first ASMX development today, and one useful tool (there are several) is the XSDObjectGen Visual Studio.NET add-in. To use a contract-first approach, you start by authoring the XSD definitions to model the messages used by the service. Next you author the WSDL to define the service operations, specifying which XSD messages are sent and received.



XSDObjectGen is similar to the XSD.exe tool that comes with the .NET framework. The difference is that XSDObjectGen builds an enhanced class structure from an XSD schema and handles many more of the common schema types than the original tool. The classes generated are suitable for both creating and consuming XML and for use as parameters and return values in web service implementations. XSDObjecGen is an ideal tool for developers who need to implement support for XML based on an existing schema, such as schemas published by industry standards groups, or a schema that is generated from a database table, as we'll soon see.

Moreover, the classes generated by the XSDObjectGen tool are perfectly good standalone "classes" in their own right, so they can be used to accurately model a schema without regard at all to the need for Webservices.

One of the major advantages of the approach is that development work against the predefined contract, where you may have several organizations implementing the same service contract and various client applications being developed to consume them, can happen simultaneously. I'll show here a very simplified "Intermediate" approach that still uses the Visual Studio IDE to generate the WSDL using the XSDObjectGen - generated class that we've customized to suit our needs. So let's do something simple and then you can do more studying on your own if you have decided to "buy in" to this concept:

First, download XSDObjectGen from the link above and install it for Visual Studio .NET 2003. You'll notice that this adds a new Project Type, "XSDObject Generator". Before you create your project, however, we need to set up our sample schema.

Open up SQL Query Analyzer, use the Northwind database, and execute the following SQL query:

select top 1 * From Employees FOR XML AUTO, ELEMENTS

This will generate our Sample XML Document for our Employee class. Grab the Xml text and save it as "Employees.xml".

Now, Open up Visual Studio.NET and load the XML file. If you right- click on the XML Designer, you can choose "Generate Schema", and you'll see something like the following:

You can see above that I've already started adjusting the stock "string" data types to what they actually should be. You can now save your XSD schema file.

Now, Fire up a new Project of type XSDObject Generator, and you'll see the following:

You can fill in your XSD schema file location, give your class a namespace name, and a File Name. Just for kicks, check all three of the Checkboxes so you can get a better idea of why XSDObjectGen is "good"!

You'll get a class generated by XSDObjectGen that completely wraps the Employee object, perfectly serializable with all requred XML Serialization attribute decorations, including utility methods and a collection "Holder" class to stick multiple Employee objects into:

// 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.4.2.1

// Schema file: Employees.xsd

// Creation Date: 6/1/2006 1:52:45 PM

//--------------------------------------------------------------

 

using System;

using System.Xml.Serialization;

using System.Collections;

using System.Xml.Schema;

using System.ComponentModel;

 

namespace PAB.Employees

{

 

    public struct Declarations

    {

        public const string SchemaVersion = "http://tempuri.org/Employees.xsd";

    }

 

    public delegate void DepthFirstTraversalDelegate(object instance, object parent, object context);

 

 

    [Serializable]

    [EditorBrowsable(EditorBrowsableState.Advanced)]

    public class EmployeesCollection : ArrayList

    {

        public PAB.Employees.Employees Add(PAB.Employees.Employees obj)

        {

            base.Add(obj);

            return obj;

        }

 

        public PAB.Employees.Employees Add()

        {

            return Add(new PAB.Employees.Employees());

        }

 

        public void Insert(int index, PAB.Employees.Employees obj)

        {

            base.Insert(index, obj);

        }

 

        public void Remove(PAB.Employees.Employees obj)

        {

            base.Remove(obj);

        }

 

        new public PAB.Employees.Employees this[int index]

        {

            get { return (PAB.Employees.Employees) base[index]; }

            set { base[index] = value; }

        }

    }

 

 

 

[XmlRoot(ElementName="Employees",Namespace=Declarations.SchemaVersion,
IsNullable=false),Serializable]

    public class Employees

    {

 

[XmlElement(ElementName="EmployeeID",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="int",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public int __EmployeeID;

 

        [XmlIgnore]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public bool __EmployeeIDSpecified;

 

        [XmlIgnore]

        public int EmployeeID

        {

            get { return __EmployeeID; }

            set { __EmployeeID = value; __EmployeeIDSpecified = true; }

        }

 

[XmlElement(ElementName="LastName",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __LastName;

 

        [XmlIgnore]

        public string LastName

        {

            get { return __LastName; }

            set { __LastName = value; }

        }

 

[XmlElement(ElementName="FirstName",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __FirstName;

 

        [XmlIgnore]

        public string FirstName

        {

            get { return __FirstName; }

            set { __FirstName = value; }

        }

 

[XmlElement(ElementName="Title",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Title;

 

        [XmlIgnore]

        public string Title

        {

            get { return __Title; }

            set { __Title = value; }

        }

 

[XmlElement(ElementName="TitleOfCourtesy",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __TitleOfCourtesy;

 

        [XmlIgnore]

        public string TitleOfCourtesy

        {

            get { return __TitleOfCourtesy; }

            set { __TitleOfCourtesy = value; }

        }

 

[XmlElement(ElementName="BirthDate",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="dateTime",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public DateTime __BirthDate;

 

        [XmlIgnore]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public bool __BirthDateSpecified;

 

        [XmlIgnore]

        public DateTime BirthDate

        {

            get { return __BirthDate; }

            set { __BirthDate = value; __BirthDateSpecified = true; }

        }

 

        [XmlIgnore]

        public DateTime BirthDateUtc

        {

            get { return __BirthDate.ToUniversalTime(); }

            set { __BirthDate = value.ToLocalTime(); __BirthDateSpecified = true; }

        }

 

[XmlElement(ElementName="HireDate",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="dateTime",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public DateTime __HireDate;

 

        [XmlIgnore]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public bool __HireDateSpecified;

 

        [XmlIgnore]

        public DateTime HireDate

        {

            get { return __HireDate; }

            set { __HireDate = value; __HireDateSpecified = true; }

        }

 

        [XmlIgnore]

        public DateTime HireDateUtc

        {

            get { return __HireDate.ToUniversalTime(); }

            set { __HireDate = value.ToLocalTime(); __HireDateSpecified = true; }

        }

[XmlElement(ElementName="Address",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Address;

 

        [XmlIgnore]

        public string Address

        {

            get { return __Address; }

            set { __Address = value; }

        }

 

[XmlElement(ElementName="City",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __City;

 

        [XmlIgnore]

        public string City

        {

            get { return __City; }

            set { __City = value; }

        }

 

[XmlElement(ElementName="Region",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Region;

 

        [XmlIgnore]

        public string Region

        {

            get { return __Region; }

            set { __Region = value; }

        }

 

[XmlElement(ElementName="PostalCode",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __PostalCode;

 

        [XmlIgnore]

        public string PostalCode

        {

            get { return __PostalCode; }

            set { __PostalCode = value; }

        }

 

[XmlElement(ElementName="Country",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Country;

 

        [XmlIgnore]

        public string Country

        {

            get { return __Country; }

            set { __Country = value; }

        }

 

[XmlElement(ElementName="HomePhone",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __HomePhone;

 

        [XmlIgnore]

        public string HomePhone

        {

            get { return __HomePhone; }

            set { __HomePhone = value; }

        }

[XmlElement(ElementName="Extension",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Extension;

 

        [XmlIgnore]

        public string Extension

        {

            get { return __Extension; }

            set { __Extension = value; }

        }

 

[XmlElement(ElementName="Photo",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="byte",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public sbyte __Photo;

 

        [XmlIgnore]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public bool __PhotoSpecified;

 

        [XmlIgnore]

        public sbyte Photo

        {

            get { return __Photo; }

            set { __Photo = value; __PhotoSpecified = true; }

        }

 

[XmlElement(ElementName="Notes",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __Notes;

 

        [XmlIgnore]

        public string Notes

        {

            get { return __Notes; }

            set { __Notes = value; }

        }

 

[XmlElement(ElementName="ReportsTo",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="int",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public int __ReportsTo;

 

        [XmlIgnore]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public bool __ReportsToSpecified;

 

        [XmlIgnore]

        public int ReportsTo

        {

            get { return __ReportsTo; }

            set { __ReportsTo = value; __ReportsToSpecified = true; }

        }

 

[XmlElement(ElementName="PhotoPath",IsNullable=false,Form=XmlSchemaForm.Qualified,DataType="string",Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public string __PhotoPath;

 

        [XmlIgnore]

        public string PhotoPath

        {

            get { return __PhotoPath; }

            set { __PhotoPath = value; }

        }

 

        public Employees()

        {

            __BirthDate = DateTime.Now;

            __HireDate = DateTime.Now;

        }

 

        public void MakeSchemaCompliant()

        {

        }

 

        public static event DepthFirstTraversalDelegate DepthFirstTraversalEvent;

        public void DepthFirstTraversal(object parent, object context)

        {

            if (DepthFirstTraversalEvent != null) DepthFirstTraversalEvent(this, parent, context);

        }

    }

 

 

[XmlRoot(ElementName="NewDataSet",Namespace=Declarations.SchemaVersion,
IsNullable=false),Serializable]

    public class NewDataSet

    {

        [System.Runtime.InteropServices.DispIdAttribute(-4)]

        public IEnumerator GetEnumerator()

        {

            return EmployeesCollection.GetEnumerator();

        }

 

        public PAB.Employees.Employees Add(PAB.Employees.Employees obj)

        {

            return EmployeesCollection.Add(obj);

        }

 

        [XmlIgnore]

        public PAB.Employees.Employees this[int index]

        {

            get { return (PAB.Employees.Employees) EmployeesCollection[index]; }

        }

 

        [XmlIgnore]

        public int Count

        {

            get { return EmployeesCollection.Count; }

        }

 

        public void Clear()

        {

            EmployeesCollection.Clear();

        }

 

        public PAB.Employees.Employees Remove(int index)

        {

            PAB.Employees.Employees obj = EmployeesCollection[index];

            EmployeesCollection.Remove(obj);

            return obj;

        }

 

        public void Remove(object obj)

        {

            EmployeesCollection.Remove(obj);

        }

[XmlElement(Type=typeof(PAB.Employees.Employees),ElementName="Employees",IsNullable=false,Form=XmlSchemaForm.Qualified,Namespace=Declarations.SchemaVersion)]

        [EditorBrowsable(EditorBrowsableState.Advanced)]

        public EmployeesCollection __EmployeesCollection;

 

        [XmlIgnore]

        public EmployeesCollection EmployeesCollection

        {

            get

            {

            if (__EmployeesCollection == null) __EmployeesCollection = new EmployeesCollection();

                return __EmployeesCollection;

            }

            set {__EmployeesCollection = value;}

        }

 

        public NewDataSet()

        {

        }

 

        public void MakeSchemaCompliant()

        {

            if (EmployeesCollection.Count == 0)

            {

                Employees _c = EmployeesCollection.Add();

                _c.MakeSchemaCompliant();

            }

        else foreach (Employees _c in EmployeesCollection) _c.MakeSchemaCompliant();

        }

 

public static event DepthFirstTraversalDelegate DepthFirstTraversalEvent;

public void DepthFirstTraversal(object parent, object context)

        {

       if (DepthFirstTraversalEvent != null) DepthFirstTraversalEvent(this, parent, context);

     if (__EmployeesCollection != null) foreach (Employees _d in __EmployeesCollection) _d.DepthFirstTraversal(this, context);

        }

    }

}

Note that it even created an XmlRoot element and a constructor for "NewDataSet" (which of course, you can change). Now if you drop this class into your WebService project, it will SOAP-Serialize out of the box. At that point, all you need to do is wire up some Database code or MSMQ code (whatever transport / storage approach you are using) and some WebMethods, and perhaps a Results object, and you're on your way. It will be easy to add a WebMethod, say, of "AddEmployees" that passes in an EmployeeCollection directly out of the XSDObjectGen -generated class. You'd map this to the Database table that we generated the XML from. Or, you could have a method "FindEmployees" that returns the same.

I'd write you a nice Limerick about it all, startng with "There was a young man from Perst", but I think you get the idea!


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: