posts - 81, comments - 262, trackbacks - 0

XML Serialization and Tiered Architecture


     In .NET XML can be used to serialize objects of almost any type. Even better, objects can be serialized (and de-serialized) to XML while using an XSD schema definition.  This is great for interoperability with outside applications, but, add XSD.exe to the picture and you can do something pretty cool with Typed Data Sets.

 

     Visual Studio will create a strongly typed Data Set off of the XSD, this is no secret.  What if however, you don't want to carry the Data Set inheritance across the layers? Is there a way to have a designer or a tool create a class that is strongly typed off of the XSD without the Data Set inheritance? There is, and its XSD.exe.

 

     XSD.exe will create a class that will serialize to an XML document meeting the XSD definition. This is done by putting the serialization attributes on the members of the class for you. So taking the XSD from a Typed Data Set, we can create a very lean container class perfect for the Business Layer.  But what about filling the generated class?

 

     Remembering the point of the XSD, interoperability and that the Typed Data Set also strictly follows the XSD we can easily populate the generated class without handling the actual definitions of the class while maintaining the strong typing (that's right, no boxing). The trick is to have the Typed Data Set write the contents to an XML document (which will meet the XSD definition), and then de-serialize the generated class on the XML document. Here is how I did this on a Users object in the Data Provider model being proposed by Ed and myself. (note, more content below example)



using System;
using System.Collections.Generic;
using System.Text;
 
//Written by Charles Cook
namespace DataLayer
{
    public abstract class UsersDataProvider
    {
        protected abstract TypedDataSets.Users GetUsersData();
 
        public BusinessLayer.Users Temp()
        {
            //Populate the Typed Data Set Users
            TypedDataSets.Users ds = GetUsersData(); new DataLayer.TypedDataSets.Users();         
 
            //Create a memory stream to read and write from (note: no xml document
            // instantiated outside of the compiled framework)
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            ds.WriteXml(stream);
            stream.Position = 0;
 
            //Desearlize the generated container class using the data from the typed data set
            System.Xml.Serialization.XmlSerializer xs =
                new System.Xml.Serialization.XmlSerializer(typeof(BusinessLayer.Users));
            BusinessLayer.Users users = (BusinessLayer.Users)xs.Deserialize(stream);
 
            return users;
        }
    }
 
    public class UsersSQLDataProvider : UsersDataProvider
    {
        protected override TypedDataSets.Users GetUsersData()
        {
            TypedDataSets.Users ds = new DataLayer.TypedDataSets.Users();
            TypedDataSets.Users.UserDataTable dt =
                new DataLayer.TypedDataSets.Users.UserDataTable();
           
            TypedDataSets.UsersTableAdapters.UserTableAdapter adapter =
                new DataLayer.TypedDataSets.UsersTableAdapters.UserTableAdapter();
           
            adapter.Fill(dt);
            ds.Tables.Add(dt);
 
            return ds;
        }
    }
}
 
And then an implementation of the object.
 
DataLayer.UsersSQLDataProvider dp = new DataLayer.UsersSQLDataProvider();
BusinessLayer.Users users = dp.Temp();
foreach (BusinessLayer.UsersUser user in users.Items)
{
    MessageBox.Show(user.UserName);
}
 
The output:
 
And the generated class definition (not one modification made):
 
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.42
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
//
// This source code was auto-generated by xsd, Version=2.0.50727.42.
//
namespace BusinessLayer {
    using System.Xml.Serialization;
   
   
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://tempuri.org/Users.xsd">http://tempuri.org/Users.xsd",
        IsNullable=false)]
    public partial class Users {
       
        private UsersUser[] itemsField;
       
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("User")]
        public UsersUser[] Items {
            get {
                return this.itemsField;
            }
            set {
                this.itemsField = value;
            }
        }
    }
   
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
    public partial class UsersUser {
       
        private int userIDField;
       
        private string userNameField;
       
        private string userPasswordField;
       
        private System.DateTime userLastLoginField;
       
        private bool userLastLoginFieldSpecified;
       
        /// <remarks/>
        public int UserID {
            get {
                return this.userIDField;
            }
            set {
                this.userIDField = value;
            }
        }
       
        /// <remarks/>
        public string UserName {
            get {
                return this.userNameField;
            }
            set {
                this.userNameField = value;
            }
        }
       
        /// <remarks/>
        public string UserPassword {
            get {
                return this.userPasswordField;
            }
            set {
                this.userPasswordField = value;
            }
        }
       
        /// <remarks/>
        public System.DateTime UserLastLogin {
            get {
                return this.userLastLoginField;
            }
            set {
                this.userLastLoginField = value;
            }
        }
       
        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool UserLastLoginSpecified {
            get {
                return this.userLastLoginFieldSpecified;
            }
            set {
                this.userLastLoginFieldSpecified = value;
            }
        }
    }
}
 
Note that LastLogin is nullable, and it went ahead and added UserLastLoginSpecified method.
 
The actual Xml used to deserialized the class:
 
<?xml version="1.0" standalone="yes"?>
<Users xmlns="
  <User xmlns="test">
    <UserID>1</UserID>
    <UserName>user1</UserName>
    <UserPassword>asdf</UserPassword>
  </User>
  <User xmlns="test">
    <UserID>2</UserID>
    <UserName>user2</UserName>
    <UserPassword>asdf</UserPassword>
  </User>
  <User xmlns="test">
    <UserID>3</UserID>
    <UserName>user3</UserName>
    <UserPassword>asdf</UserPassword>
  </User>
</Users>
 
 
 
Another point to note, it would be very easy to use WebServices with this to export and import Business Objects into a project. Ever have an XSD for a payment gateway, and had to get the data in a painfull way by hand? With serialization it should take more than an hour to build a custom class off of their XSD and populate it. 


Print | posted on Saturday, May 10, 2008 4:28 PM | Filed Under [ Web Programming ]

Powered by:
Powered By Subtext Powered By ASP.NET