none
请教当返回自定义类型时如何通过<declaredTypes>实现序列化 RRS feed

  • 问题

  • 我有一个Service方法,返回类型是SearchResult:

            [OperationContract]
            SearchResult CallSearchMethod(string recordType, ...)

    其中SearchResult结构如下,因为我想重用这个结构体,所以我在其中定义了一个IList类型的属性,用于传回不同类型的数据:

        /// <summary>
        /// 查询返回结果
        /// </summary>
        [DataContract]
        public class SearchResult
        {
            /// <summary>
            /// 总记录数
            /// </summary>
            [DataMember]
            public int RecordCount { get; set; }

            /// <summary>
            /// 总分页数
            /// </summary>
            [DataMember]
            public int PageCount { get; set; }

            /// <summary>
            /// 记录列表
            /// </summary>
            [DataMember]
            public IList RecordList { get; set; }
        }

    现在客户端要调用Service的CallSearchMethod("Vew_sec_BusinessUser", ...)方法,返回信息如下:

                        return new SearchResult
                        {
                            PageCount = 0,
                            RecordCount = 0,
                            RecordList = new Vew_sec_BusinessUser[] { }
                        };

    其中Vew_sec_BusinessUser是个自定义类,结构如下:

    namespace Company.Security.Contract
    {
        [DataContract]
        public class Vew_sec_BusinessUser
        {
            [DataMember]
            public string fBusinessTypeCode { get; set; }

            [DataMember]
            public string fBusinessKey { get; set; }

            [DataMember]
            public Guid fUserID { get; set; }
        }
    }

    客户端调用这个方法时,提示以下错误:

    格式化程序尝试对消息反序列化时引发异常: 尝试对参数 http://tempuri.org/ 进行反序列化时出错: CallSearchMethodResult。InnerException 消息是“在行 1、位置 654 出现错误。 元素“http://schemas.microsoft.com/2003/10/Serialization/Arrays:anyType”含有“http://schemas.datacontract.org/2004/07/Company.Security.Contract:Vew_sec_BusinessUser”数据协定的数据。反序列化程序不知道映射到此协定的类型。请将与“Vew_sec_BusinessUser”对应的类型添加到已知类型的列表中,例如,通过使用 KnownTypeAttribute 属性或通过将其添加到传递给 DataContractSerializer 的已知类型的列表等方法。”。有关详细信息,请参阅 InnerException。

    查了一些帮助,发现是由于返回类型中有个IList,所以无法序列化,如果把SearchResult结构调整成如下方式可以解决问题:

        [DataContract]
        [KnownType(typeof(Company.Security.Contract.Vew_sec_BusinessUser)]
        public class SearchResult
        {
            ...
        }

    但我的目的是希望SearchResult可以公用,而不是每加一种类型,都要在SearchResult的KnownTypeAttribute中增加一行再重编译,所以我想采用修改配置文件的方式,在Service的Web.Config中添加了如下配置:

      <system.runtime.serialization>
        <dataContractSerializer>
          <declaredTypes>
            <add type = "Company.Security.Contract.Vew_sec_BusinessUser, Company.Security.Contract, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
              <knownType type = "Company.Security.Contract.Vew_sec_BusinessUser, Company.Security.Contract, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </add>
          </declaredTypes>
        </dataContractSerializer>
      </system.runtime.serialization>

    但好像没有效果,依旧报无法序列化的错误,请问配置信息应如何设置,谢谢。

    • 已编辑 cxy 2009年5月27日 8:13
    2009年5月27日 8:04

答案

  •    Hi cxy,
          我看了你提供的连接。他们确实有个:
    <add type="System.Collections.Generic.Dictionary`2, SerializationTypes, Version = 2.0.0.0, Culture = neutral, PublicKeyToken=null">
              <knownType type="System.Collections.Generic.List`1, SerializationTypes, Version = 2.0.0.0, Culture = neutral, PublicKeyToken=null">
                <parameter index="1"/>
              </knownType>
            </add>

          1. 可以尝试一下,使用List作为DataMember的类型。它继承自IList接口。
          2. List是.net的内置类型,即使你添加了节点,但是在反序列话的时候也也会使用数组代替的。
         数组是标准的做法,这个和Web Service十分相似。不然只能在.net平台上进行数据交互了~
         
    you have a dream,you gonna protect it! http://www.cnblogs.com/frank_xl
    2009年5月31日 12:07
    版主

全部回复

  •   hi cxy,
       你遇到的问题,在WCF服务编程中比较棘手的问题。这个有WCF自身的机制的缺陷。它不支持服务操作未知的数据契约类型。
    其中一个主要的问题就是数据契约类型的继承和引用。
    1.开始的错误导致的原因就是:服务端定义的数据契约,客户端反序列话,本地代码进行调用。客户端通过代理待用服务操作就出现反序列错误的问题。你必须定义为已知类型。
    2.还有一种情况就是数据契约继承的问题。服务端服务操作可以定义某个子数据契约类型的参数;客户端反序列化。代理调用服务操作,如果传递的类型是子类型的话,也会出现一样的错误。客户端不知道符合反序列化该类型。
    3.WCF给出的解决方法就是KnownType和ServiceKnownType.两者都可以对服务声明一个数据契约为已知类型。但是后者允许更细的定义级别。可以定义到操作级别。支持代码中直接声明的方式。另外还提供了配置文件的方式。编译方式麻烦,维护复杂。配位文件方式简单。支持在客户端和服务端配置已知类型。
    4.上面你其实已经尝试了配置文件。你的配置文件节点信息视乎有错。程序集名字不对。你检查一下,MSDN参考的代码如下:
    <add type="MyCompany.Library.Shape, 
               MyAssembly, Version=2.0.0.0, Culture=neutral,
               PublicKeyToken=XXXXXX, processorArchitecture=MSIL">
               <knownType type="MyCompany.Library.Circle, 
                          MyAssembly, Version=2.0.0.0, Culture=neutral,
                          PublicKeyToken=XXXXXX,
                          processorArchitecture=MSIL"/>
    </add>
         原文链接:http://msdn.microsoft.com/zh-cn/express/ms731795.aspx
        你再调试一下.如果还有问题留言我们继续交流吧~

    you have a dream,you gonna protect it! http://www.cnblogs.com/frank_xl
    2009年5月27日 10:12
    版主
  • 谢谢回复,我也发现的确是配置内容不对,之前add type节点应该改成如下方式:

     <system.runtime.serialization>
        <dataContractSerializer>
          <declaredTypes>
            <add type="Company.Library.SearchResult, Company.Library, Version=2.0.3438.31427">
              <knownType type="Company.Security.Contract.Vew_sec_BusinessUser, Company.Security.Contract, Version=1.0.3438.26733" />
            </add>
          </declaredTypes>
        </dataContractSerializer>
      </system.runtime.serialization>

    另外,因为文件版本和程序集版本号不一样,所以把版本号调整了一下(假如程序集的版本如果不对,在运行时会提示“The given assembly name or codebase was invalid.”),但改好后错误依旧。

    在msdn的参考资料中找了一下,都是针对自定义类的配置方式,只有一个例子(http://msdn.microsoft.com/zh-cn/express/ms731806.aspx)使用List泛型作为DataMember,但没有发现以IList作为DataMember时的配置说明。不知是否也要把IList添加到KnownType配置中(如"<add type="System.Collection.IList, mscorlib, Version=2.0.50727.3082">")?
    谢谢。
    • 已编辑 cxy 2009年5月31日 9:55
    2009年5月31日 9:52
  •    Hi cxy,
          我看了你提供的连接。他们确实有个:
    <add type="System.Collections.Generic.Dictionary`2, SerializationTypes, Version = 2.0.0.0, Culture = neutral, PublicKeyToken=null">
              <knownType type="System.Collections.Generic.List`1, SerializationTypes, Version = 2.0.0.0, Culture = neutral, PublicKeyToken=null">
                <parameter index="1"/>
              </knownType>
            </add>

          1. 可以尝试一下,使用List作为DataMember的类型。它继承自IList接口。
          2. List是.net的内置类型,即使你添加了节点,但是在反序列话的时候也也会使用数组代替的。
         数组是标准的做法,这个和Web Service十分相似。不然只能在.net平台上进行数据交互了~
         
    you have a dream,you gonna protect it! http://www.cnblogs.com/frank_xl
    2009年5月31日 12:07
    版主