XML Serialization

2002-9-19 9:25:58【作者】 畅享网 【进入论坛】
广告

XML Serialization

In this article, we're going to be taking a look at XML serialization. This is the process of taking an object, turning it into an XML document and either storing it for later use, or moving it to another part of the network for use elsewhere. XML serialization in .NET works by examining the properties on a class that are flagged as read/write. Whenever it comes across one of these properties, it takes the current value and inserts in into the XML document. De-serialization is, as I'm sure you've guessed, the opposite of this process.

As you're no doubt aware, .NET makes fairly gratuitous use of XML. What we can do with the XML serialization in .NET is turn any object into a block of XML, ready to send down a stream. As you learn more about .NET, you'll discover that the stream metaphor comes up quite regularly. For example, to write a file to a disk, you open a file stream and to send bytes to another computer on the network, you open a network stream. This abstraction is neat and is common to a lot of aspects of .NET development - once you know how to do it one way, learning how to do it another, related way is very easy.

XML Serialization in the .NET Framework

When I first started looking into XML serialization, I thought it was going to be more complicated than it was. All you have to do to turn an object into a block of XML is instantiate an XmlSerializer object and give it the object you want the XML for. Likewise, to turn a block of XML into an object, we use XmlSerializer again, but this time we give it a block of XML as opposed to an object.

In most cases, you won't have to make any changes to the object you want to serialize. XmlSerializer works by examining the properties on the object and assumes that any read/write property should be included in the serialization. It will then call these properties as if it were a typical consumer.

For example, if your object implements a read/write property called Name, XmlSerializer will ask for that property and insert the return value into the XML block. When XmlSerializer is asked to build an object from a block of XML, it will take the value stored in the XML block and set the property to the value. (This is done through reflection, which we'll talk about in a moment).

On first glance, this seems like a kludge. I, as a fairly typical developer, was not tremendously comfortable with this approach at first glance. I would prefer it if, for example, XmlSerializer called a method on an interface asking me to supply the information I want inserted into the XML. In fact, this is exactly the way the ISerializable interface and the System.Runtime.Serialization namespace works. However, implementing ISerializable is non-trivial, whereas using XmlSerializer is very trivial to implement.

By the time I'd finished taking a good long look at XML serialization, I realized it is tremendously powerful and well worth using. For experienced developers - don't worry about the lack of control. XML serialization provides a quick and dirty way of shoving objects down streams and pulling them out again.

Reflection

For those of you who haven't come across it before, reflection is a very powerful process by which any object in the .NET Framework can be examined to determine it's properties, methods and events. As you know, every object we build in .NET has to be derived from Object. This class contains a method called GetType. This method always returns an object of class SystemType. This new class contains methods like GetProperties and GetMethods that can be used to learn more about the capabilities of the object.

The XmlSerializer simply calls GetProperties to return an array of PropertyInfo objects, each one describing a property on an object. If the property is read/write, it will be included in the serialization.

Although ASPToday will be running an article on reflection in the near future, there is some good reading out there if you're interested. Of course, you can also try out reflection yourself using the ClsView sample included with the Framework SDK. One part of reflection that hurts my head when I think about it is emitting. This lets you build classes from scratch at runtime, and use them in your application as if they were normal classes.

Testing Serialization

To test our serialization, we're going to need an application that contains a class that we want to turn into XML and back again. We're going to do this using a VB .NET/ASP .NET application, so create a new Visual Basic - Web Application project. (Although the code samples here are in VB .NET, we're not doing anything VB specific, so those C# fans among you, simply tweak the code.) For this article, mine is called Serialization, so if you're following along with the code you may find this the best name to use.

To show us what's going on, our project is going to create a form containing a text area field. We're going to use an object called Book that contains the name, ISBN number and list of authors for the book. This field will be populated with the XML generated by XmlSerializer. We can then change the XML and create a new object from the XML block. (For example, we could change the title of the book from Beginning E-commerce to VB .NET Programming with the Public Beta). Once we have the new object, we'll use a simple server-side control to render the results to the user.

Creating the Form

The first thing we need to do is create a form. To do this, open the HTML view for the ASPX page and add make these changes:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.vb"
Inherits="Serialization.WebForm1"%>
<html><head>
<meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic 7.0"></head>
<body>

<form id="WebForm1" method="post" runat="server">
<textarea runat="server" id="xmlarea" cols="60" rows="15"></textarea>
<br>
<input runat="server" type="submit" value="Turn XML into an object!">
</form>

</body></html>

You'll notice that we've set the id attribute of the textarea element to xmlarea. This will let us manipulate the element from our VB code. In particular, we want to use its Value property to get and set the text contained within the element.

If we run the page now, this is what we get:

One problem with the VS .NET IDE: although we've added the text area field to the page, sometimes a reference to xmlarea won't be added to the WebForm1 class definition. Without this, we won't be able to refer to the field as we are processing, hence we won't be able to get or set its value. If you open the code for WebForm1.vb, you should see a line like this:

Public Class WebForm1

Inherits System.Web.UI.Page

Protected WithEvents xmlarea As System.Web.UI.HtmlControls.HtmlTextArea

If the line is not there, add it yourself!

Creating the Book class

The class we're going to work with is called Book. For our first run-through, we're simply going to use the Name and ISBN properties of the class. There's nothing special here, so create a new class called Book and add this code:

Imports System

Imports System.Collections

Public Class Book

Private m_Name As String
Private m_ISBN As String

Public Property Name() As String
Get
Return m_Name
End Get
Set
m_Name = Value
End Set
End Property

Public Property ISBN() As String
Get
Return m_ISBN
End Get
Set
m_ISBN = value
End Set
End Property
End Class

Handling PreRender

Just before we render the form on the page, we want to make sure that the xmlarea field contains a block of XML. To do this, we need to create a new instance of Book, set default Name and ISBN properties and ask XmlSerializer to serialize the object for us.

Of course, we can't use XmlSerializer until we've added a reference to it in our project. To do this, in Solution Explorer, right click References and select Add Reference. Scroll down until you find System.Xml.Serialization.dll. Click Select, then OK.

Before you can use the object, we need to include a reference to it at the top of WebForm1.vb:

Imports System
Imports System.ComponentModel.Design
Imports System.Data
Imports System.Drawing
Imports System.Web
Imports System.Web.SessionState
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls
Imports Microsoft.VisualBasic
Imports System.IO
Imports System.Xml.Serialization

Open WebForm1.vb in the IDE and use the object drop down in the top-left to select the WebForm1 instance, and select PreRender from the methods list on the right. Add this code to check to see if we've already got a value for xmlarea:

Public Sub WebForm1_PreRender(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles WebForm1.PreRender

' if we don't already have a value, create a default book...
If xmlarea.Value = "" Then

If the form has been submitted, xmlarea will have a value. Specifically, it will contain whatever the user wanted to add to our field. Next, we need to create an instance of a Book object and set the properties:

' create an instance of a book...
Dim MyBook As New Book()
MyBook.Name = "Beginning E-commerce"
MyBook.ISBN = "1861003986"

The next thing we need to get hold of is a System.IO.TextWriter that XmlSerializer will send the data to. A TextWriter is usually used to provide access to a stream of data, such as a file on disk or network connection. In our case, we're going to use a System.IO.StringWriter object. This is a TextWriter object that sits on top of a string, so whenever new data is added it is just concatenated onto the end of the data it already has.

' create a new string writer...
Dim writer As New StringWriter()

Next we create the XmlSerializer object itself. This requires the Type object of the thing we're trying to serialize. Once we have that, we ask it to serialize the object to the StringWriter object:

' create a serializer...
Dim serializer As New XmlSerializer(MyBook.GetType)
serializer.Serialize(writer, MyBook)

Finally, we can use the ToString method of the StringWriter to get hold of the XML block. We then set the value of the text area element to be this string:

' set the value in the form...
xmlarea.Value = writer.ToString

End If

End Sub

If we run the code now, we can see the XML serialization string for the default book object:

Turning XML back into an object

What we can do now is change the XML block that we have and use the altered XML to create a new object. For example, we can change the text within the Name element to provide a different Name property for our new object.

To display the details of the object to the user, we're going to use an ASP .NET server-side control. This control will have a property called Book that we will use to tell the control which book we want to render. Create a new class called BookView and add this code:

Imports System
Imports System.Web
Imports System.Web.UI

Namespace Controls

Public Class BookView
Inherits Control

' need somewhere to store the book...
Dim m_Book As Book

Public Property Book() As Book
Get
Return m_Book
End Get
Set
m_Book = Value
End Set
End Property

End Class

End Namespace

The Inherits Control directive tells VB to inherit the class from the System.Web.UI.Control object. This provides the control with the various methods needed to support the control from within an ASPX page. Likewise, before we can use a control, it has to be placed into a namespace. I've used the name Controls, meaning that this class is technically known as Serialization.Controls.Bookview.

To make the control do something, we need to override the Render method. If we don't have a Book object to render, we just want to tell the user we have nothing to do:

Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)

' do we have a book?
If m_Book Is Nothing Then

' render something...
writer.Write("Nothing to do!")

Else

If we do have a book, we want to render everything we know:

' draw the name of the book...
writer.Write("Type: " & book.GetType().ToString & "<br>")
writer.Write("Name: " & book.Name & "<br>")
writer.Write("ISBN: " & book.ISBN & "<br>")

End If

End Sub

Adding the control to the ASPX page

Before we can add the control to the page, we need to add a reference to the namespace to the page. This is done using a Register directive at the top of the page:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.vb" Inherits="Serialization.WebForm1"%>
<%@ Register TagPrefix="mycontrols" Namespace="Serialization.Controls" %>

The TagPrefix attribute tells ASP .NET what namespace a particular server-side control is in. To add our control to our form, we use this prefix coupled with the name of the class itself:

<form id="WebForm1" method="post" runat="server">
<textarea runat="server" id="xmlarea" cols="60" rows="15"></textarea>
<br>
<input runat="server" type="submit" value="Turn XML into an object!">
</form>

<mycontrols:bookview runat="server" id="bookview"></mycontrols:bookview>

Again, notice how we've given the BookView control a name using the ID attribute. This will let us refer to the control from within the VB code by name, much like we do with xmlarea.

The VS .NET IDE may have had an issue adding a definition to bookview to WebForm1. Make sure that this line exists against the class. If it's missing, add it manually:

Public Class WebForm1

Inherits System.Web.UI.Page

Protected WithEvents xmlarea As System.Web.UI.HtmlControls.HtmlTextArea

Protected WithEvents bookview As Controls.BookView

Processing the XML

Whenever the Turn XML into an object button is pressed, the page will be reloaded, hence our WebForm1_Load event handler will be called. We can use the Value property of xmlarea to get the revised XML, pass it through XmlSerializer and set the Book property of our BookView control.

First off then, we have to look to see if the page has been posted back. If it has, we can do our processing. Firstly, we create a new StringReader and tell it to use the value of xmlarea as its source:

Protected Sub WebForm1_Load(ByVal Sender As System.Object, _
ByVal e As System.EventArgs)
If Not IsPostback Then ' Evals true first time browser hits the page

Else

' try and turn the xml back into an object...
Dim reader As New StringReader(xmlarea.Value)

Now, like before, we create an instance of an XmlSerializer and tell it what type of object we want. (Here, to get the type, I've created an instance of a Book object and used GetType. XmlSerializer won't set the properties on this particular object - this object exists solely to provide type information).

' create a serializer...
Dim MyBook As New Book()
Dim serializer As New XmlSerializer(MyBook.GetType())

Finally, we use a Try.Catch block around Deserialize. There's no guarantee the user will give us a valid block of XML, so we have make sure nothing goes wrong.

' try and read it...
Try

' turn it into a book...
bookview.Book = CType(serializer.Deserialize(reader), Book)

Catch

End Try

End If

End Sub

To test this out, we run the project and change the XML that gets created by default. When we click Turn XML into an object, the new Book object we get will contain altered data:

Using Arrays

It's quite common to encounter objects that contain arrays of other objects. Our Book object, for example, should contain an array of authors. XML serialization can handle arrays, providing you implement them in a specific manner.

This illustrates the big drawback to XML serialization - there are quite a few things it can't handle. In short, XML Serialization can only handle very basic data types, thinks like numbers, strings and other objects that have read/write properties that use only those basic types.

One of the ways that ASP developers are used to moving chunks of data around is using things like dictionaries and collections. XML serialization cannot handle these, but it can handle arrays. Paradoxically, ASP developers aren't very comfortable with moving arrays around because previous versions of VB weren't too good at handling these.

Internally, we can store data however we want, and .NET provides plenty of useful objects for handling lists of data. However, to serialize that information into and from XML we need to transform the lists into an array.

The "Author" object

To hold details of a particular author, we need to create a new Author object. Create it now, and add this code:

Imports System

Public Class Author

Private m_Name As String

Public Property Name () As String
Get
Return m_name
End Get
Set
m_name = value
End Set
End Property

End Class

To hold a list of authors on the Book object, we need to create some form of list. We'll use a Hashtable, so add this code to the Book class definition:

Private m_Name As String
Private m_ISBN As String
Private m_Authors As New Hashtable()

To add a new author to the list, we'll create the AddAuthor method. This method will create a new Author object, and add it to our hash table.

' a method to add an author...
Public Function AddAuthor(ByVal Name As String) As Author

' create a new author...
Dim NewAuthor As New Author()
NewAuthor.Name = Name

' add it to the collection...
m_Authors.Add(Name, NewAuthor)

End Function

Now when we create our default object, we need to add an author. We can do this by altering the WebForm1_PreRender handler:

' create an instance of a book...
Dim MyBook As New Book()
MyBook.Name = "Beginning E-commerce"
MyBook.ISBN = "1861003986"
MyBook.AddAuthor("Matthew Reynolds")

As we said, in order to serialize the hash table, we need to convert it to an array. Here's the implementation of the Authors property that converts the hash table to an array of Author objects and vice versa:

Public Property Authors() As Author()
Get

' create a new author array...
Dim AuthorArray(m_authors.Count) As Author
Dim Author As Author, n As Integer
For Each author In m_Authors.Values
AuthorArray(n) = Author
n += 1
Next

' return the array...
Return AuthorArray

End Get
Set

' reset the authors we've already got (i.e. what
' we are given is the definitive list.)
m_Authors.Clear()

' go through the authors we have...
Dim Author As Author
For Each Author In Value
m_Authors.Add(Author.Name, Author)
Next

End Set
End Property

Now if we try and run the project, we'll notice the list of authors is added to the XML block:

To see whether the XML can be serialized back into an object, we need to change our BookView control to render a list of authors. Add this code to the Render event handler:

' draw the name of the book...
writer.Write("Type: " & book.GetType().ToString & "<br>")
writer.Write("Name: " & book.Name & "<br>")
writer.Write("ISBN: " & book.ISBN & "<br>")

' loop through the authors...
Dim Author As Author
For Each Author In m_Book.Authors
writer.Write("Author: " & Author.Name & "<br>")
Next

Now when we change the XML to include a new author and click Turn XML into an object, we get this:

Summary

In this article we've seen how the .NET XmlSerializer object can be used to convert an object into a block of XML and send it down one of the stream objects in the Framework. In this example we used the StringStream object, but FileStream and NetworkStream will both work without any changes to the code - one of the coolest features in .NET! Although in this article we haven't seen a practical example of XmlSerialization, check out how IBuySpy.com use it to hold their site's configuration information. I'll also be elaborating on XmlSerializer and serialization in general in an upcoming case study for ASPToday.

如果您希望与本文章的作者或其所在机构,进一步交流,请联系:畅享网 姜小姐
jill.jiang@amt.com.cn | 021-51096826-112 | 在线联系
ITSM-适境而为[原创]ITIL软着陆需要合适的ITS..

ITIL要实现“软着陆”,ITSM工具必不可少。现有ITSM工具大到Remedy、MRO,小到国内各种自主系统。

企业信息化杂谈[原创]空降CIO的变与不变

CIO需要尽快适应新公司的权力分配体系。在两家公司可能职位名称是一样的,但是对这个职位的责权利可能是很大的不同。

CIO职场,强者生存?

在2008年,我们将继续看到CIO向商业运营方向发展。与此同时,我们也会看到商业管理人员将与技术管理人员一起竞争CIO岗位。 IT领导者的就职机会虽有不少,但其难度将会大幅提高。2……

防震减灾,IT当关

今天,任何的防震救灾体系,都离不开IT技术。地震观测台是数字化的,震害防御需要对以往的地震信息进行数据分析,应急救援要需要现代多样化的通讯技术。如果说,在许多行业,信息技术还只是一……