1. 程式人生 > >Xml序列化當泛型不同時序列化(反序列化)為不同的Xml節點名稱

Xml序列化當泛型不同時序列化(反序列化)為不同的Xml節點名稱

在我們提供介面服務給第三方呼叫時,一般會採用Request/Response模式,即請求與響應都採用統一的外部封裝,真正的業務資料則由Request/Resonse的某個引數比如Data之類的類進行承擔,以Request為例,該請求類假設定義成如下內容:
    /// <summary>
    /// 資料請求類
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [XmlRoot("Request")]
    public class Request<T>
    {
        /// <summary>
        /// 請求外部唯一性流水號,用於Resposne對應
        /// </summary>
        public string RequestId { get; set; }
        /// <summary>
        /// 請求日期 yyyy-MM-dd HH:mm:ss格式
        /// </summary>
        public string RequestDate { get; set; }
        /// <summary>
        /// 請求業務資料
        /// </summary>
        public T Data { get; set; }
    }
這裡初始化該類的一個具體定義
            Request<int> request = new Request<int>
            {
                Data = 1,
                RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                RequestId = Guid.NewGuid().ToString()
            }
其Xml序列化結果預設如下
<?xml version="1.0" encoding="utf-8"?>
<Request xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <RequestId>6a9a99fc-7731-41d9-87b1-2cc637b0afdc</RequestId>
  <RequestDate>2018-03-07 14:38:08</RequestDate>
  <Data>1</Data>
</Request>
這裡業務資料部分不管泛型類為什麼,該業務的Xml節點名稱均為Data,這是很正常的一種資料契約定義方式,但實際我們接入第三方介面時,很可能會碰到另外一種情況,甚至可以說是反人類的定義方式,同一個第三方介面服務,介面A和介面B的業務資料Xml節點名稱不一樣!!!具體可能如下
<?xml version="1.0" encoding="utf-8"?>
<Request xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <RequestId>6a9a99fc-7731-41d9-87b1-2cc637b0afdc</RequestId>
  <RequestDate>2018-03-07 14:38:08</RequestDate>
  <BusinessA>1</BusinessA><!--A服務的業務請求-->
  <!--<BusinessB>1</BusinessB>--><!--B服務的業務請求-->
</Request>
這種情況下,前面的Request<T>肯定是無法支援的,所以需要對Request<T>進行調整
    /// <summary>
    /// 資料請求類
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [XmlRoot("Request")]
    public abstract class Request<T>
    {
        /// <summary>
        /// 請求外部唯一性流水號,用於Resposne對應
        /// </summary>
        public string RequestId { get; set; }
        /// <summary>
        /// 請求日期 yyyy-MM-dd HH:mm:ss格式
        /// </summary>
        public string RequestDate { get; set; }
        /// <summary>
        /// 請求業務資料
        /// </summary>
        [XmlIgnore]
        public abstract T Data { get; set; }
    }
注意除了Data屬性被定義成了abstract外,該節點還設定了XmlIgnore特性,如果不設定該特性,那麼子類自定義XmlElement後再進行Xml序列化會產生異常,下面是業務A的定義
    [XmlRoot("Request")]
    public class RequestForA : Request<int>
    {
        [XmlElement("BusinessA")]
        public override int Data { get; set; }
    }
注意該類也必須要定義XmlRoot特性,否則序列化出來的Xml最外層節點名稱將為RequestForA,同理對於業務B也需要如此操作設定(再說一次這麼提供介面服務的第三方真是反人類!!!)

最後實際進行業務定義的地方進行調整

            var request = new RequestForA
            {
                Data = 1,
                RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                RequestId = Guid.NewGuid().ToString()
            };
這樣就可以對不同的業務,序列化不同的Xml節點名稱(當然反序列化也是沒有任何問題的),順帶補充下Xml序列化輔助類
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Xml.Serialization;
    /// <summary>
    /// xml序列化輔助類
    /// </summary>
    public static class XmlHelper
    {
        /// <summary>
        /// xml序列化
        /// </summary>
        /// <typeparam name="T">泛型</typeparam>
        /// <param name="obj">待序列化物件</param>
        /// <param name="encoding">字元編碼,不指定則utf-8</param>
        /// <param name="showDeclaration">是否顯示xml宣告</param>
        /// <param name="removeDefaultNameSpace">是否移除預設的xmlns:xsi名稱空間(注:如果待序列化物件指定了NameSpace還是會序列化出對應的名稱空間)</param>
        /// <returns></returns>
        public static string Serializer<T>(this T obj, Encoding encoding = null, bool showDeclaration = true, bool removeDefaultNameSpace = false)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            using (MemoryStream stream = new MemoryStream())
            {
                XmlWriterSettings xws = new XmlWriterSettings();
                xws.Indent = true;
                xws.OmitXmlDeclaration = !showDeclaration;
                xws.Encoding = encoding;
                using (XmlWriter xtw = XmlWriter.Create(stream, xws))
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    if (removeDefaultNameSpace)
                    {
                        ns.Add("", "");
                    }
                    serializer.Serialize(xtw, obj, ns);
                    stream.Position = 0;
                    string xml = encoding.GetString(stream.GetBuffer());
                    //這種方法生成的xml字串在不同Encoding下不知道為啥可能會有不可見字元
                    if (xml[0] != '<')
                    {
                        var sIdx = xml.IndexOf('<');
                        var eIdx = xml.LastIndexOf('>');
                        xml = xml.Substring(sIdx, eIdx - sIdx + 1);
                        //return Regex.Replace(xml, @"^[\s\S]*?(?=<)|[^>]*?$", string.Empty);//正則存在效能問題
                    }
                    return xml;
                }
            }
        }
        /// <summary>
        /// xml反序列化
        /// </summary>
        /// <typeparam name="T">泛型</typeparam>
        /// <param name="xml">xml內容</param>
        /// <returns></returns>
        public static T Deserialize<T>(this string xml)
        {
            using (StringReader sr = new StringReader(xml))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                try
                {
                    return (T)serializer.Deserialize(sr);
                }
                catch(Exception e)
                {
                    return default(T);
                }
            }
        }
    }
好吧,上面寫了那麼多,實際這麼做每種業務服務都要定義一個class定義,一旦業務多了,對於強迫症或者程式碼潔癖者可能是一種折磨,畢竟每種業務除了自身的業務類定義外,還需要定義外部包含類,那是否還有其他可行的方法呢?

答案是肯定的,但這種答案是徹底定製的!!!定製的思路是這樣的,既然你的業務節點名稱不同,那在序列化或反序列化時,針對這特定的業務名稱進行特殊處理不就行了?下面是以LinqToXml進行Xml解析的示例程式碼:

    /// <summary>
    /// 完全定製的xml序列化類,只可做參考,不能直接拿來用
    /// </summary>
    public class XmlHelperCustomized
    {
        /// <summary>
        /// 序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <param name="nodeName">要替換的業務節點名稱</param>
        /// <returns></returns>
        public static string Serializer<T>(Request<T> request, string nodeName)
        {
            var xml = XmlHelper.Serializer(request);
            var root = XElement.Parse(xml);
            var ele = root.Element("Data");
            if (ele != null)
            {
                ele.Name = nodeName;
            }
            return root.ToString();
        }
        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xml"></param>
        /// <param name="nodeName">業務節點名稱</param>
        /// <param name="dataIsXml">業務資料是否需要xml反序列化,true表示是</param>
        /// <returns></returns>
        public static Request<T> Deserialize<T>(string xml, string nodeName, bool dataIsXml)
        {
            var request = XmlHelper.Deserialize<Request<T>>(xml);
            var root = XElement.Parse(xml);
            var ele = root.Element(nodeName);
            if (ele != null)
            {
                T obj;
                if (dataIsXml)
                {
                    obj = XmlHelper.Deserialize<T>(ele.ToString());
                }
                else
                {
                    obj = (T)Convert.ChangeType(ele.Value, typeof(T));
                }
                request.Data = obj;
            }
            return request;
        }
    }
這裡為該類業務定製了一個特定的Xml序列化類(注意該部分程式碼依賴上面的XmlHelper),相應的使用例子程式碼如下:
            var request = new Request<int>//注意這裡的Request<T>是非abstract的那個
            {
                Data = 1,
                RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                RequestId = Guid.NewGuid().ToString()
            };
            var xml = XmlHelperCustomized.Serializer(request, "BusinessA");
            var requestA = XmlHelperCustomized.Deserialize<int>(xml, "BusinessA", false);