Reading XML in VB.NET is pretty straightforward . Create an XML Document Object, then you can load the XML and iterate through all of the nodes as required. You can search for nodes, use x-path, whatever you like.
Creating XML, on the other hand, is not so straight forward. There are essentially two methods.
Option 1 is, in my opinion, a non-option. While it may be easier to create your structures without having to think of serialization, the process of creating the XML Document Object (that's not hard) and then creating and setting each of the Element and Attribute nodes is suicidal! It also will create a code segment that will collapse under its own weight, even with less-than-complex XML! I have a project I did prior to learning serialization and the process of saving the XML takes about 3 pages of code, and I can't understand it any more!
Enter XML Serialization
I am not going to go into excrutiating detail on all of this, instead I have created a VS 2008 project you can download and play with. In it you will see the following things:
I think that covers it...
I am going to create a simple structure for Employees. This structure will consist of a four basic data points - one of which will be a nested structure with three data points of its own.
We will need to be able to create an array of this structure because we will have more than one employee.
We are going to start by building the structure in classes. I suppose you could do it with a simple structure - I may try that later, but today we are going to create classes. I like classes. The classes will all be inside a vb module I call Serialize. Its a dumb name - but it's only a demo folks.
I am creating three classes.
Employee
JobPriorities
Group
Lets ignore the class decoration for now. (the decoration is all that <xmlRoot("Emplo yees")> kind of stuff in front of each class. I'll explain that a little later.
The Group class allows me to create an array of employees, and is in fact a very small, but necessary class.
The Employee class contains the three primitive elements and one element that is of the JobPriorities class.
There's really nothing special in these classes. You can see that I did overload the New constructor - mostly just for convenience - so users would have a way to construct a new object and set the properties easily. These are very simple and straightforward classes with appropriate setters and getters. It has properly constructed private variables and public properties.
Creating the Object, Setting the Properties, and Serializing to XML
Notice at the beginning of the Serialize module I create a global object called EmploymentData. This is the object I will add data to before I serialize the output to XML.
In a button_click event I put the following code
Here I am Creating a MyPriorities object from the JobPriorities class and setting the properties of the EmploymentData object with a bunch of hard coded values. I felt it was important to show some level of nesting in my example - because that can be confusing and difficult to understand for someone just starting out (like me). The EmploymentData object (which was declared Globally in the Serialize module) will have three employees - so I need to Redim it to 2 (0, 1, 2).
I now have an array of EmploymentData that I can fill out. One of the properties of the EmploymentData is going to be a JobPriorities type and I have created MyPriorities for that.
Now I can fill in the structure array
And finally I can send it to my generic Serialization function
Here is where the heavy lifting takes place. The function to serialize the entire structure is really just a few lines of code. I kept it basic and generic so that virtually any object can be passed. I did this by having it accept anything derived from the Object base class (just about everything in VB) and returning anything derived from the Object class. When you SaveXML you pas it the filename, the Object with the data, and you have to tell it exactly what object you are sending it.
The Generic Serialization Code
This code is pretty self-explanatory. The one, non-obvious, element is my creation of a new XmlSerializerNa mespaces object. I did this because .NET, by default, will put some namespace declarations into your XML. It's not necessary, in only creates noise in my opinion. So by creating a blank Namespace and including that in my serializer, it will remove it.
Once it is written out the XML looks like this:
This is very pretty because I set the properties of the XMLWriterSettin gs object I created (xSettings) to define the look of the XML.
Reading the XML into the EmploymentData object
Getting the XML back into an EmploymentData object is equally simple.
In another Button_Click event I place the following code:
I create a new EmployeeData object based on Group. Here I do not need to create the Array as the deserialization will create the array for me.
There's also a bunch of code that places the results into a text box just so you can see that it actually deserialized the XML and created the array.
The deserializer, like the serializer is generic. I followed the same rules and return the data in the base Object class. To use the generic deserializer you only need to tell it where the XML file is, and tell it what type of object you are expecting to deserialize.
This works a treat!
XML Decoration
By default the XML will use your class names for node names. This may be perfectly fine with you. I wanted to override that naming in another project I am doing so I needed to figure that out. Here is the process.
When creating the classes for the Group Class you can tell the Serializer to use different names for the Elements and Attributes. By preceding each class with the appropriate decoration you will override the default behavior of the serialization code (which is to use the class names).
Since Group is my root class I decorate it with
Notice in the Group Class I create one member variable array called Employees which I want to appear as XML Elements so I use the decoration:
Since it will be an array the serializer will give each Employee node the tag name of "IndividualEmpl oyee." You can call the node whatever you like.
For each of the properties in the Employee class I decorated appropriate names, but also declared them to be Attributes. I did this with the following decoration:
I did this with three of the four properties in the Employee class so that when Employee was serialized they would become Attributes in XML rather than new tags. You could place them in as Elements if you like, but I like the way Attributes read for this data. The Priorities Property I decided to call "Jobs" and make it an Element (because it would have sub elements). I used the following decoration there:
In the JobPriorities class I used the decorations to create elements and name them appropriately.
All in all some of my renaming is probably making the code a bit more confusing, but I felt it was important to show how decorations can be used to define how properties appear as either Attributes or Elements, and that you can have them appear in the XML with any name you want. So if it's confusing - I know about it, but it was done that way as a learning tool.
I've included a full VS2008 VB.NET project you can play with. It's self contained and includes all the source referenced in this article.
Hopefully I haven't done anything stupid - but don't be shy about letting me know.
Des
Creating XML, on the other hand, is not so straight forward. There are essentially two methods.
- Create an XML Document Object and set each of the Element and/or attribute nodes to the values you want
- Create the structure you want with serialization in mind and serialize the data to XML
Option 1 is, in my opinion, a non-option. While it may be easier to create your structures without having to think of serialization, the process of creating the XML Document Object (that's not hard) and then creating and setting each of the Element and Attribute nodes is suicidal! It also will create a code segment that will collapse under its own weight, even with less-than-complex XML! I have a project I did prior to learning serialization and the process of saving the XML takes about 3 pages of code, and I can't understand it any more!
Enter XML Serialization
I am not going to go into excrutiating detail on all of this, instead I have created a VS 2008 project you can download and play with. In it you will see the following things:
- A few classes that are designed to create the data structure I wish to create as XML and read in as XML.
- A way to name the XML nodes anything you want
- A way to keep the XML clean (by default .NET will load all kinds of namespace garbage into your XML that doesn't need to be there)
- How to save the Data Object as XML using a generic serialization function
- How to read the XML into the Data Object using a generic serialization function
I think that covers it...
I am going to create a simple structure for Employees. This structure will consist of a four basic data points - one of which will be a nested structure with three data points of its own.
Employee
FirstName
LastName
Birthday
Responsibilitie s
LastName
Birthday
Responsibilitie s
ResponsibilityO ne
ResponsibilityT wo
ResponsibilityT hree
ResponsibilityT wo
ResponsibilityT hree
We will need to be able to create an array of this structure because we will have more than one employee.
We are going to start by building the structure in classes. I suppose you could do it with a simple structure - I may try that later, but today we are going to create classes. I like classes. The classes will all be inside a vb module I call Serialize. Its a dumb name - but it's only a demo folks.
Code:
Public Module Serialize Public EmploymentData As New Group <XmlRoot("Employees")> _ Public Class Group <XmlElement("IndividualEmployee")> _ Public Employees() As Employee End Class Public Class Employee Private vFirstName As String = "" Private vLastName As String = "" Private vBirthday As New Date Private vPriorities As JobPriorities Public Sub New() End Sub Public Sub New(ByVal FirstName As String, ByVal LastName As String, ByVal Birthday As Date, ByVal Responsibilities As JobPriorities) Me.FirstName = FirstName Me.LastName = LastName Me.Birthday = Birthday Me.Priorities = Responsibilities End Sub <XmlAttribute("firstname")> _ Public Property FirstName() As String Get Return vFirstName End Get Set(ByVal value As String) vFirstName = value End Set End Property <XmlAttribute("lastname")> _ Public Property LastName() As String Get Return vLastName End Get Set(ByVal value As String) vLastName = value End Set End Property <XmlAttribute("birthday")> _ Public Property Birthday() As String Get Return vBirthday End Get Set(ByVal value As String) vBirthday = value End Set End Property <XmlElement("Jobs")> _ Public Property Priorities() As JobPriorities Get Return vPriorities End Get Set(ByVal value As JobPriorities) vPriorities = value End Set End Property End Class Partial Public Class JobPriorities Private vResponsibilityOne As String = "" Private vResponsibilityTwo As String = "" Private vResponsibilityThree As String = "" Public Sub New() End Sub Public Sub New(ByVal ResponsibilityOne As String, Optional ByVal ResponsibilityTwo As String = "", Optional ByVal ResponsibilityThree As String = "") Me.ResponsibilityOne = ResponsibilityOne Me.ResponsibilityTwo = ResponsibilityTwo Me.ResponsibilityThree = ResponsibilityThree End Sub <XmlElement("first_priority")> _ Public Property ResponsibilityOne() As String Get Return vResponsibilityOne End Get Set(ByVal value As String) vResponsibilityOne = value End Set End Property <XmlElement("Second_priority")> _ Public Property ResponsibilityTwo() As String Get Return vResponsibilityTwo End Get Set(ByVal value As String) vResponsibilityTwo = value End Set End Property <XmlElement("Third_priority")> _ Public Property ResponsibilityThree() As String Get Return vResponsibilityThree End Get Set(ByVal value As String) vResponsibilityThree = value End Set End Property End Class
Employee
JobPriorities
Group
Lets ignore the class decoration for now. (the decoration is all that <xmlRoot("Emplo yees")> kind of stuff in front of each class. I'll explain that a little later.
The Group class allows me to create an array of employees, and is in fact a very small, but necessary class.
The Employee class contains the three primitive elements and one element that is of the JobPriorities class.
There's really nothing special in these classes. You can see that I did overload the New constructor - mostly just for convenience - so users would have a way to construct a new object and set the properties easily. These are very simple and straightforward classes with appropriate setters and getters. It has properly constructed private variables and public properties.
Creating the Object, Setting the Properties, and Serializing to XML
Notice at the beginning of the Serialize module I create a global object called EmploymentData. This is the object I will add data to before I serialize the output to XML.
In a button_click event I put the following code
Code:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Dim MyPriorities As JobPriorities ReDim Preserve EmploymentData.Employees(2) MyPriorities = New JobPriorities("Secretary", "Eye Candy", "Mistress") EmploymentData.Employees(0) = New Employee("Melissa", "Ryan", "1/1/1970", MyPriorities) MyPriorities = New JobPriorities("Janitor", "Cook", "Bottlewasher") EmploymentData.Employees(1) = New Employee("Amanda", "Terranova", "1/1/1980", MyPriorities) MyPriorities = New JobPriorities("Boss", "Idiot", "Syncophant") EmploymentData.Employees(2) = New Employee("Harlan", "Butts", "1/1/1980", MyPriorities) SaveXML(MyXMLFile, EmploymentData, GetType(Group)) End Sub
Code:
ReDim Preserve EmploymentData.Employees(2)
Code:
Dim MyPriorities As JobPriorities
Code:
MyPriorities = New JobPriorities("Secretary", "Eye Candy", "Mistress") EmploymentData.Employees(0) = New Employee("Melissa", "Ryan", "1/1/1970", MyPriorities) MyPriorities = New JobPriorities("Janitor", "Cook", "Bottlewasher") EmploymentData.Employees(1) = New Employee("Amanda", "Terranova", "1/1/1980", MyPriorities) MyPriorities = New JobPriorities("Boss", "Idiot", "Syncophant") EmploymentData.Employees(2) = New Employee("Harlan", "Butts", "1/1/1980", MyPriorities)
Code:
SaveXML(MyXMLFile, EmploymentData, GetType(Group))
The Generic Serialization Code
Code:
Public Function SaveXML(ByVal FileName As String, ByVal DataToSerialize As Object, ByVal objType As Type) As Boolean 'set up a blank namespace to eliminate unnecessary junk from the xml Dim nsBlank As New XmlSerializerNamespaces nsBlank.Add("", "") 'create an object for the xml settings to control how the xml is written and appears Dim xSettings As New System.Xml.XmlWriterSettings With xSettings .Encoding = Encoding.UTF8 .Indent = True .NewLineChars = Environment.NewLine .NewLineOnAttributes = False .ConformanceLevel = Xml.ConformanceLevel.Document End With Try 'create the xmlwriter object that will write the file out Dim xw As System.Xml.XmlWriter = Xml.XmlWriter.Create(FileName, xSettings) 'create the xmlserializer that will serialize the object to XML Dim writer As New XmlSerializer(objType) 'now write it out writer.Serialize(xw, DataToSerialize, nsBlank) 'be sure to close it or it will remain open xw.Close() Return True Catch ex As Exception MsgBox(ex.Message) Return False End Try End Function
Once it is written out the XML looks like this:
Code:
<?xml version="1.0" encoding="utf-8"?> <Employees> <IndividualEmployee firstname="Melissa" lastname="Ryan" birthday="1/1/1970"> <Jobs> <first_priority>Secretary</first_priority> <Second_priority>Eye Candy</Second_priority> <Third_priority>Mistress</Third_priority> </Jobs> </IndividualEmployee> <IndividualEmployee firstname="Amanda" lastname="Terranova" birthday="1/1/1980"> <Jobs> <first_priority>Janitor</first_priority> <Second_priority>Cook</Second_priority> <Third_priority>Bottlewasher</Third_priority> </Jobs> </IndividualEmployee> <IndividualEmployee firstname="Harlan" lastname="Butts" birthday="1/1/1980"> <Jobs> <first_priority>Boss</first_priority> <Second_priority>Idiot</Second_priority> <Third_priority>Syncophant</Third_priority> </Jobs> </IndividualEmployee> </Employees>
Reading the XML into the EmploymentData object
Getting the XML back into an EmploymentData object is equally simple.
In another Button_Click event I place the following code:
Code:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim EmployeeData As New Group EmployeeData = GetXML(MyXMLFile, GetType(Group)) 'the following is just so you can see the output from the EmploymentData object Dim sData As Employee With TextBox1 For Each sData In EmployeeData.Employees .SelectedText = "Employee: " & sData.FirstName & " " & sData.LastName & Environment.NewLine .SelectedText = "Birthday: " & sData.Birthday & Environment.NewLine .SelectedText = "Job Responsabilities: " & Environment.NewLine .SelectedText = " " & sData.Priorities.ResponsibilityOne & Environment.NewLine .SelectedText = " " & sData.Priorities.ResponsibilityTwo & Environment.NewLine .SelectedText = " " & sData.Priorities.ResponsibilityThree & Environment.NewLine .SelectedText = "---------------" & Environment.NewLine Next .SelectedText = "" End With End Sub
There's also a bunch of code that places the results into a text box just so you can see that it actually deserialized the XML and created the array.
The deserializer, like the serializer is generic. I followed the same rules and return the data in the base Object class. To use the generic deserializer you only need to tell it where the XML file is, and tell it what type of object you are expecting to deserialize.
Code:
Public Function GetXML(ByVal sFileName As String, ByVal objType As Type) As Object If My.Computer.FileSystem.FileExists(sFileName) Then Dim fs As FileStream = New FileStream(sFileName, FileMode.Open) Dim xs As XmlSerializer = New XmlSerializer(objType) Dim obj As Object = CType(xs.Deserialize(fs), Object) fs.Close() Return obj Else Return Nothing End If End Function
XML Decoration
By default the XML will use your class names for node names. This may be perfectly fine with you. I wanted to override that naming in another project I am doing so I needed to figure that out. Here is the process.
When creating the classes for the Group Class you can tell the Serializer to use different names for the Elements and Attributes. By preceding each class with the appropriate decoration you will override the default behavior of the serialization code (which is to use the class names).
Since Group is my root class I decorate it with
<XmlRoot("Emplo yees")> _
Notice in the Group Class I create one member variable array called Employees which I want to appear as XML Elements so I use the decoration:
<XmlElement("In dividualEmploye es:)> _
Since it will be an array the serializer will give each Employee node the tag name of "IndividualEmpl oyee." You can call the node whatever you like.
For each of the properties in the Employee class I decorated appropriate names, but also declared them to be Attributes. I did this with the following decoration:
<XmlAttribute(" firstname")> _
I did this with three of the four properties in the Employee class so that when Employee was serialized they would become Attributes in XML rather than new tags. You could place them in as Elements if you like, but I like the way Attributes read for this data. The Priorities Property I decided to call "Jobs" and make it an Element (because it would have sub elements). I used the following decoration there:
<XmlElement("Jo bs")> _
In the JobPriorities class I used the decorations to create elements and name them appropriately.
All in all some of my renaming is probably making the code a bit more confusing, but I felt it was important to show how decorations can be used to define how properties appear as either Attributes or Elements, and that you can have them appear in the XML with any name you want. So if it's confusing - I know about it, but it was done that way as a learning tool.
I've included a full VS2008 VB.NET project you can play with. It's self contained and includes all the source referenced in this article.
Hopefully I haven't done anything stupid - but don't be shy about letting me know.
Des
Comment