Xml序列化當泛型不同時序列化(反序列化)為不同的Xml節點名稱
阿新 • • 發佈:2019-02-12
在我們提供介面服務給第三方呼叫時,一般會採用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節點名稱均為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> <Data>1</Data> </Request>
這種情況下,前面的Request<T>肯定是無法支援的,所以需要對Request<T>進行調整<?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>
/// <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);