• An XMLSerializer input/output utility


    Introduction

    Back when I started using XML documents in C++, parsing and getting data 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 serialization works (these are ideas for possible future articles). I will, however, introduce a very simple Hello World class that will be used in this example. 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 pretty easy, 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 this one:

    "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 good step 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 introduce some 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 of XMLSupport 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

    For other advanced reasons, two events have been added: 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 the included 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 Microsoft XmlSerializer 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.

    作者:Angelo Lee
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    web api中允许跨域访问
    HTTP Error 500.19
    使用SQL语句时应该注意的一些问题
    关于EsayUI中datagrid重复提交后台查询数据的问题
    EF6中使用事务的方法
    jquery中常用的方法和注意点
    在EF中正确的使用事务
    css解决移动端1px边框问题
    判定 JS 数据类型的最佳解决方案
    将伪数组转化为真数组
  • 原文地址:https://www.cnblogs.com/yefengmeander/p/2888039.html
Copyright © 2020-2023  润新知