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,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,RequestId = Guid.NewGuid().ToString()
            };
            var xml = XmlHelperCustomized.Serializer(request,"BusinessA");
            var requestA = XmlHelperCustomized.Deserialize<int>(xml,"BusinessA",false);

猜你在找的XML相关文章