关于HTTP调用WCF传递DataTable参数的处理
在上两节中,已经可以跨域调用WCf提供的服务了,但是如果参数是DataTable的话,就有点麻烦了
但是好在传递DataTable的xml文档其实是固定的,你可以字符串拼接(如果是前端JS的话,可能是免不了的)
现在来分析一下传递的DataTable的xml结构
下面是我生成的DataTable
private DataTable InitTable() { DataTable dt = new DataTable("table"); dt.Columns.Add("ID"); dt.Columns.Add("IName"); for (int i = 0; i < 5; i++) { dt.Rows.Add(i, "a" + i); } dt.AcceptChanges(); dt.Rows[0][1] = "update"; dt.Rows[1].Delete(); dt.Rows[3].Delete(); dt.Rows.Add(100, "Add"); dt.Columns.Add("type", typeof(UserInfoModel)); foreach (DataRow row in dt.Rows) { if (row.RowState != DataRowState.Deleted) { row["type"] = new UserInfoModel() { NickName = row["IName"].ToString() }; } } return dt; }
可以看到我现在的DataTable的结构就是6行3列,其中第2行和第4行是被删除了的,而第1行是经过更新了的,第6行是新添加的
下面是对照上面的DataTable生成的带注释的xml文档
<?xml version="1.0"?> <!--这个节点是固定的,表示这是一个DataTable类型,并且命名空间也是必须要有的--> <DataTable xmlns="http://schemas.datacontract.org/2004/07/System.Data"> <!--这个节点是固定的,表示DataTable的结构--> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <!--这个节点也是固定的,但 msdata:MainDataTable 的值不是固定,该属性的取值为你DataTable的TableName 此值配合下面的 diffgr:diffgram -> DocumentElement -> table(此标签就是TableName)--> <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="table" msdata:UseCurrentLocale="true"> <xs:complexType> <!--这个是固定的--> <xs:choice minOccurs="0" maxOccurs="unbounded"> <!--这里的name就是DataTable的TableName--> <xs:element name="table"> <xs:complexType> <xs:sequence> <!--下面三个就代表这个Table一共有三列,且分别定义了它们的数据类型信息 这里要注意的是,如果是数据类型是基元类型,则其type的值就是 xs:string--> <xs:element name="ID" type="xs:string" minOccurs="0" /> <xs:element name="IName" type="xs:string" minOccurs="0" /> <!--而如果非基元类型,则是它的assembly qualified type name 且还需要在WCF的配置文件中指定, 参考msdn文档 https://docs.microsoft.com/zh-cn/dotnet/framework/data/adonet/dataset-datatable-dataview/security-guidance--> <xs:element name="type" msdata:DataType="PublicModel.UserInfoModel, PublicModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" type="xs:anyType" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <!--这个节点固定的--> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <!--这个节点也是固定的,但要注意一定要有 xmlns="" 空命名空间属性,不可或缺--> <DocumentElement xmlns=""> <!--以下几个table节点就是动态的,表示行信息,其中节点table就是该DataTable的TableName id值也是固定的以TableName+当前行的下标+1来命名 msdata:rowOrder则是其真正的下标 diffgr:hasChange 表示此行的更新信息,只有 modified 和 inserted 两种取值,如果行未做任何更改,则不用写入此特性--> <table diffgr:id="table1" msdata:rowOrder="0" diffgr:hasChanges="modified"> <ID>0</ID> <IName>update</IName> <!--这个列的名称就叫type,命名空间是固定的,必须要这么写上去--> <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Rid>0</Rid> <RLevel>0</RLevel> <NickName>update</NickName> <!--这个属性的值表示它是一个可以为null的值--> <BirthDay xsi:nil="true" /> </type> </table> <table diffgr:id="table3" msdata:rowOrder="2" diffgr:hasChanges="modified"> <ID>2</ID> <IName>a2</IName> <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Rid>0</Rid> <RLevel>0</RLevel> <NickName>a2</NickName> <BirthDay xsi:nil="true" /> </type> </table> <table diffgr:id="table5" msdata:rowOrder="4" diffgr:hasChanges="modified"> <ID>4</ID> <IName>a4</IName> <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Rid>0</Rid> <RLevel>0</RLevel> <NickName>a4</NickName> <BirthDay xsi:nil="true" /> </type> </table> <table diffgr:id="table6" msdata:rowOrder="5" diffgr:hasChanges="inserted"> <ID>100</ID> <IName>Add</IName> <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Rid>0</Rid> <RLevel>0</RLevel> <NickName>Add</NickName> <BirthDay xsi:nil="true" /> </type> </table> </DocumentElement> <!--这里是保存了行更新之前的所有行信息--> <diffgr:before> <!--这个就是表示在之前的下标为0处的行,在更新/删除之前它的值信息, 其中 xmlns="" 是必须要有的,否则更新的行和删除的行信息将无法传递到WCF 而其命名规则与上面是一致的--> <table diffgr:id="table1" msdata:rowOrder="0" xmlns=""> <ID>0</ID> <IName>a0</IName> </table> <table diffgr:id="table2" msdata:rowOrder="1" xmlns=""> <ID>1</ID> <IName>a1</IName> </table> <table diffgr:id="table3" msdata:rowOrder="2" xmlns=""> <ID>2</ID> <IName>a2</IName> </table> <table diffgr:id="table4" msdata:rowOrder="3" xmlns=""> <ID>3</ID> <IName>a3</IName> </table> <table diffgr:id="table5" msdata:rowOrder="4" xmlns=""> <ID>4</ID> <IName>a4</IName> </table> </diffgr:before> </diffgr:diffgram> </DataTable>
下面是C#的序列化代码
public string SerializaTableToXml(DataTable dt) { //检查Table的名称是否为空 if (string.IsNullOrWhiteSpace(dt.TableName)) { //如果为空,则给一个命名,一定要有名称 dt.TableName = "table"; } //检查Table的命名空间是否为空 if (!string.IsNullOrWhiteSpace(dt.Namespace)) { //如果不为空,则一定要删除它的命名空间 dt.Namespace = null; } var stream = new MemoryStream(); XmlSerializer xs = new XmlSerializer(dt.GetType()); XmlWriterSettings settings = new XmlWriterSettings() { CheckCharacters = true, CloseOutput = true, ConformanceLevel = ConformanceLevel.Auto, Encoding = new UTF8Encoding(false), DoNotEscapeUriAttributes = true, NamespaceHandling = NamespaceHandling.OmitDuplicates, NewLineHandling = NewLineHandling.Entitize, NewLineOnAttributes = false, OmitXmlDeclaration = false, WriteEndDocumentOnClose = true }; using (var writer = XmlWriter.Create(stream, settings)) { xs.Serialize(stream, dt); } string xmlStr = Encoding.UTF8.GetString(stream.ToArray()); stream.Dispose(); //下面还需要做一些其它的事情,所以装载到xmldocument中 var xml = new XmlDocument(); xml.LoadXml(xmlStr); //给根路径添加命名空间,这是必须的 xml.DocumentElement.SetAttribute("xmlns", "http://schemas.datacontract.org/2004/07/System.Data"); //创建一个空的命名空间,这也是必须的 var attr = xml.CreateAttribute("xmlns"); attr.Value = ""; //找到第一个子节点下的DocumentElement子节点(因为DataTable序列化后是固定的,所以直接下标获取) var element = xml.DocumentElement.ChildNodes[1].ChildNodes[0]; //设置命名空间 element.Attributes.SetNamedItem(attr); //找到所有的diffgr:before节点下的子节点 var elements = xml.DocumentElement.ChildNodes[1].ChildNodes[1].ChildNodes; foreach (XmlNode node in elements) { //循环添加空命名空间,这是必须的 node.Attributes.SetNamedItem(attr); } return xml.OuterXml; }
如果DataTable中的数据类型都是基元类型,那么这么做就完事了,但我提供的示例里其中type列是一个自定义类型
这种情况下,WCF的配置文件中需要加入以下信息
<!--加入自定义类型信息,给予接收DataTable类型使用,configSections必须是 configuration 节点下的第一个--> <configSections> <!--sectionGroup 节点直接这样照写就行了--> <sectionGroup name="system.data.dataset.serialization" type="System.Data.SerializationSettingsSectionGroup, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <section name="allowedTypes" type="System.Data.AllowedTypesSectionHandler, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </sectionGroup> </configSections> <!--这里就是存放自定义类型信息的地方--> <system.data.dataset.serialization> <!--允许的自定义类型--> <allowedTypes> <!--添加一个自定义类型,它的type必须是程序集的完全名称--> <add type="PublicModel.UserInfoModel, PublicModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </allowedTypes> </system.data.dataset.serialization>