首页 > 技术文章 > 关于HTTP调用WCF传递DataTable参数的处理

rbzz 2021-02-01 22:35 原文

在上两节中,已经可以跨域调用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>

 

推荐阅读