在我们提供接口服务给第三方调用时,一般会采用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);