An XMLSerializer input/output utility
2011-01-05 14:39
295 查看
Introduction
Back when I started using XML documents in C++, parsing and gettingdata out of the document was not what I would have called friendly, nor
was generating a new document from scratch. Around that time, I also
started playing with this new language called C#. At some point, I was
doing XML parsing with C# and discovered the XML serialization features (
XMLSerializer
).
Needless to say, this was a great answer to my problems because I was
often working with in-memory object relationships and would spend quite a
bit of code translating between the two.
Over time, I found myself writing a set of utility functions to load
and save the XML documents to and from different formats (files, string,
memory streams, etc.). One day it occurred to me that I could use
Generics to make this much easier for me over copying, pasting, and
modifying the same method form a different class over and over.
Background on XML Serialization
This article is not going to explain in detail how XML serializationworks (these are ideas for possible future articles). I will, however,
introduce a very simple Hello World class that will be used in thisexample. The basic XML document looks like:
<? xml version =" 1.0" ? > < hello > < message > Hello World< / message > < / hello >
The C# class we use for serialization looks like this:
namespace BackgroundCode { public class Hello { public string Message { get ; set ; } } }
This is pretty straightforward and, in fact, looks just like a normal
C# object. Here is where the magic comes in; say, we want to read in
the XML document from above, you could use the following code:
public static Hello ReadDocument(string fileName) { XmlSerializer xs = new XmlSerializer(typeof (Hello)); using (Stream stream = File.OpenRead(fileName)) { return xs.Deserialize(stream) as Hello; } }
Now, you may notice that there are a number of places where things
can fail here, and anyone calling this function should expect that you
may get exceptions from
File.OpenRead
and
xs.Deserialize
.
You can also write a similar function for writing out to a file:
public static void WriteDocument(Hello xml, string fileName) { XmlSerializer xs = new XmlSerializer(typeof (Hello)); using (Stream stream = File.OpenWrite(fileName)) { xs.Serialize(stream, xml); } }
Iteration 1
The actual work done in reading and writing a document seems prettyeasy, but as a proponent of DRY (Don't Repeat Yourself), it seems that
the most logical place for this functionality to live is with the data
definition class itself. My updated class looks like this:
using
System.IO;
using
System.Xml.Serialization;
namespace
Iteration1
{
public
class
Hello
{
public
string
Message { get
; set
; }
public static Hello ReadDocument(string fileName) { XmlSerializer xs = new XmlSerializer(typeof (Hello)); using (Stream stream = File.OpenRead(fileName)) { return xs.Deserialize(stream) as Hello; } }
public
void
WriteDocument(string
fileName)
{
XmlSerializer xs = new
XmlSerializer(typeof
(Hello));
using
(Stream stream = File.OpenWrite(fileName))
{
xs.Serialize(stream, this);
}
}
}
}
There are some minor changes from what we had in the background, and a
few things to note. The first thing to note is that
ReadDocument
is a static function. This allows you to use a nice trick to load your
document:
Hello read = Hello.ReadDocument(fname);
The next thing of note is
WriteDocument
, it now only
takes a file name and not an instance of a
Hello
object.
This is because we can just use the keyword “
this
” to write out the document. A
call to save this out would look something like this:
Hello data = new Hello { Message = " Hello World" }; data.WriteDocument(fname);
Now, for the longest time, this is where I would end my work. After
all, when I needed to do this for a new class, I could just copy and
paste these functions and modify them. Now, let me show you the fallacy
of that statement. Say, we have a new class (this one is a goodbye world
class). So, we start off with something like:
namespace Iteration1 { public class Goodbye { public string Reason { get ; set ; } } }
Now, we copy in our utility functions and modify them:
using
System.IO;
using
System.Xml.Serialization;
namespace
Iteration1
{
public
class
Goodbye
{
public
string
Reason { get
; set
; }
public
static
Goodbye ReadDocument(string
fileName)
{
XmlSerializer xs = new
XmlSerializer(typeof
(Goodbye));
using
(Stream stream = File.OpenRead(fileName))
{
return
xs.Deserialize(stream) as
Goodbye;
}
}
public
void
WriteDocument(string
fileName)
{
XmlSerializer xs = new
XmlSerializer(typeof
(Hello));
using
(Stream stream = File.OpenWrite(fileName))
{
xs.Serialize(stream, this);
}
}
}
}
Do you see the error? It won't cause a compile error, and as long as
all you do is read from files, you may not notice this error for a long
time, until you finally go to do a save and get an exception like thisone:
"There was an error generating the XML document."
Iteration 2
Refactor is the word here, and now we introduce Generics to help us.As a logical extension to our second example, let's look at the next
iteration of this solution with our first
XMLSupport
class.
First of all, we revert back to the initial implementation of Hello.cs
:
Now, we introduce the utility class:
using System.IO; using System.Xml.Serialization; namespace Iteration2 { public sealed class XMLUtility { public static T ReadDocument<t>(string fileName) { XmlSerializer xs = new XmlSerializer(typeof (T)); using (Stream stream = File.OpenRead(fileName)) { return (T) xs.Deserialize(stream); } } public static void WriteDocument<t>(T xmlObject, string fileName) { XmlSerializer xs = new XmlSerializer(typeof (T)); using (Stream stream = File.OpenWrite(fileName)) { xs.Serialize(stream, xmlObject); } } } }
You can now read from a file using a statement like:
Hello read = XMLUtility.ReadDocument<hello>(fname);
You can write out to a file using a statement like:
Hello data = new Hello { Message = " Hello World" }; XMLUtility.WriteDocument<hello>(data, fname);
Iteration 3
While the previous solution (with a little augmentation) is a goodstep forward (and possibly the correct solution in a number of
scenarios), I think there is still more we can do with it. As opposed to
using a class with generic functions, we introduce a generic class:
using System.IO; using System.Xml.Serialization; namespace Iteration3 { public sealed class XMLUtility<t> { public static T ReadDocument(string fileName) { XmlSerializer xs = new XmlSerializer(typeof (T)); using (Stream stream = File.OpenRead(fileName)) { return (T)xs.Deserialize(stream); } } public static void WriteDocument(T xmlObject, string fileName) { XmlSerializer xs = new XmlSerializer(typeof (T)); using (Stream stream = File.OpenWrite(fileName)) { xs.Serialize(stream, xmlObject); } } } }
Now honestly, this doesn't make things much better yet, since the
calls still look like:
Hello data = new Hello { Message = " Hello World" }; XMLUtility<hello>.WriteDocument(data, fname);
and:
Hello read = XMLUtility<hello>.ReadDocument(fname);
Iteration 4
Now that we have abstracted out to a generic class, let's introducesome inheritance to the mix. First, the utility class:
using
System;
using
System.IO;
using
System.Xml.Serialization;
namespace
Iteration4
{
public
abstract
class
XMLSupport<t>
{
public
static
T ReadDocument(string
fileName)
{
XmlSerializer xs = new
XmlSerializer(typeof
(T));
using
(Stream stream = File.OpenRead(fileName))
{
return
(T)xs.Deserialize(stream);
}
}
public
void
WriteDocument(string
fileName)
{
XmlSerializer xs = new
XmlSerializer(typeof
(T));
using
(Stream stream = File.OpenWrite(fileName))
{
xs.Serialize(stream, this);
}
}
}
}
Now, let's take a look at our data class:
namespace Iteration4 { public class Hello : XMLSupport<hello> { public string Message { get ; set ; } } }
So this makes a call to read look like:
Hello read = Hello.ReadDocument(fname);
And the call to write look like:
Hello data = new Hello { Message = " Hello World" }; data.WriteDocument(fname);
Which is considerably easier to read and use over previous
iterations.
Other Functionality
The final version ofXMLSupport
adds new pieces of
functionality, and also entails some name changes. Because there are
times when you want to read and write to strings and generic streams (as
opposed to files), there are now three read and three write operations:
Read | Write |
FromXmlString | ToXmlString |
FromStream | ToStream |
FromFile | ToFile |
BeforeSave
and
AfterLoad
. They are generic event handlers (
EventHandler< eventargs>
)
that you can add your own events to.
Using the Code
Making use of this library is pretty simple. Take a look at theincluded test code, and you can see a
Hello
class:
public class Hello : XMLSupport<hello> { public Hello() { BeforeSave += BeforeSaveEvent; AfterLoad += AfterLoadEvent; } public string Message { get ; set ; } [XmlIgnore] public bool beforeSaveCalled = false ; [XmlIgnore] public bool afterLoadCalled = false ; protected void BeforeSaveEvent(object sender, EventArgs args) { beforeSaveCalled = true ; } protected void AfterLoadEvent(object sender, EventArgs args) { afterLoadCalled = true ; } }
This class makes use of the
BeforeSave
and
AfterLoad
events for testing purposes, but gives an example of how to use it.
Here is a simple example of reading in from a string using the above
class:
Hello ret = Hello.FromXmlString(" <hello><message>Hello World</message></hello>" );
The functions, for the most part, work just like iteration 4
described above, with just slightly different method names.
Conclusion
The MicrosoftXmlSerializer
is an extremely useful
utility when working with XML files. Using some of the advanced features
of C# and Generics, it is possible to make adding load and save support
a very simple operation. Using the XMLSupport library can make this a
very simple process.
相关文章推荐
- An XMLSerializer input/output utility
- android的main.xml文件中编写控件EditText时This text field does not specify an inputType or a hint
- Redirecting an arbitrary Console's Input/Output
- OpenCV--File Input and Output using XML and YAML files
- WOX―an xml serializer for java or c# objects
- A single input file is required for a non-link phase when an outputfile is specified
- Android Build with Gradle and ProGuard : “The output jar must be specified after an input jar, or it
- Input/output subsystem having an integrated advanced programmable interrupt controller for use in a personal computer
- [转]Create an XMLSerializer from a given XML string
- Output an XML type attribute using DataContract
- The installer encountered an error copying files to the hard disk: [Errno 5] Input/output error
- OpenCV_Tutorials——CORE MODULE.THE CORE FUNCTIONALITY—— File Input and Output using XML and YAML files
- Error: Q0466E: An output file can only be specified if there is a single input file
- org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x0) was found in the CDATA sectio
- itk input and output
- 5.Struts的<action input=""></action>:struts-config.xml
- InputFormat&OutputFormat
- V4L2 input/output方式
- Spring 3.2 源码解析 -- XML bean 元素到 BeanDefinition 解析过程
- Cannot remove : Input/output error