【问题标题】:WCF Client having problems recognizing ServiceKnownTypes?WCF 客户端在识别 ServiceKnownTypes 时遇到问题?
【发布时间】:2010-10-01 15:06:15
【问题描述】:

在将数据传回客户端时,我如何告诉 WCF 服务使用什么 KnownTypes?

我知道我可以使用[ServiceKnownType] 属性,它使服务调用在 WCF 测试服务器上运行良好,但它仍然在客户端失败。我在这里遗漏了什么吗?

[OperationContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
BaseClassZ GetObject();

来自客户端的错误信息是:

{"元素 'http://schemas.datacontract.org/2004/07/BaseClassZ' 包含来自映射到的类型的数据 名字 'http://schemas.datacontract.org/2004/07/SubClassA'。 解串器不知道 映射到此名称的任何类型。 考虑使用 DataContractResolver 或者添加对应的类型 'SubClassA' 到已知类型列表 - 例如,通过使用 KnownTypeAttribute 属性或通过 将其添加到已知类型列表中 传递给 DataContractSerializer。"}

使用 DataContractSerializer 和 KnownTypes 列表对 WCF 服务器上的对象进行序列化/反序列化可以正常工作。

更新:如果我将 KnownType 属性添加到基类,我似乎可以让客户端正确读取对象,但如果可能的话,我仍在寻找解决此问题的方法,因为基类用于很多项目,我不想在添加新项目时修改基类上的 KnownType 属性。

[DataContract]
[KnownType(typeof(SubClassA))]
[KnownType(typeof(SubClassB))]
public class BaseClassZ 
{
    ...
}

【问题讨论】:

  • 我看到了大量的 MSDN 文档和示例,这些文档和示例确实让这听起来应该是可能的,但该死的,如果我能让它工作的话!添加赏金...

标签: wcf datacontractserializer datacontract serviceknowntype


【解决方案1】:

为避免阻止您的服务代码,请将已知类型放入服务的 web.config:

<system.runtime.serialization>
    <dataContractSerializer>
        <declaredTypes>
            <add type="SomeNs.BaseClassZ, SomeAssembly">
                <knownType type="SomeNs.SubClassA, SomeAssembly" />
                <knownType type="SomeNs.SubClassB, SomeAssembly" />
            </add>
        </declaredTypes>
    </dataContractSerializer>
</system.runtime.serialization>

如果你想通过代码来做,你需要在服务接口而不是操作方法上使用这个属性,但我更喜欢声明性方法:

[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IFoo
{
    [OperationContract]
    BaseClassZ GetObject();
}

更新:

我已经提交了一个sample project 来说明如何使用 web.config 来配置已知类型,这是我的首选方法。另一个sample project 演示了第二种方法。


更新 2:

使用 Silverlight 应用程序客户端查看更新后的代码后,我们注意到以下定义:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
public interface IService1 {

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
    System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);

    System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
    System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);

    MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}

注意BeginGetSingle 方法如何包含已知类型属性,而BeginGetMany 方法不包含。事实上,这些属性应该放在服务定义中,以便类看起来像这样。

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
public interface IService1
{
    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
    System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);

    System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
    System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);

    MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}

由于这是一个自动生成的类,SLsvcUtil.exesvcutil.exe 中可能存在错误,因为它表现出相同的行为。将已知的类型属性放在正确的位置可以解决问题。问题是这个类是由工具自动生成的,如果你尝试从 WSDL 重新生成它,它会再次混乱。

所以看来,如果你有以下服务定义:

[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IService1
{
    [OperationContract]
    BaseClassZ[] GetMany();

    [OperationContract]
    BaseClassZ GetSingle();
}

并且这里使用的3个数据契约在导入服务定义时在客户端和服务器之间共享,返回集合的方法在生成的客户端代理中没有得到正确的已知类型属性。也许这是设计使然。

【讨论】:

  • Class BaseClassZ {...} 是否需要 [KnownType(typeof(SubClassA))] 和 [KnownType(typeof(SubClassB))] 才能使您的第二个示例正常工作?
  • @Aardvark,不,它没有。您可以查看我为说明它而设置的sample project
  • 我敢打赌,这与我重用来自共享程序集的类型有关。
  • @Aardvark,这里是 an example,带有共享程序集。请注意在 client 上的 app.config 中如何配置已知类型。
  • 我应该澄清一下。我正在尝试返回对象集合 (IEnumerable)。基类及其派生类型存在于客户端和服务器都引用的它们自己的程序集中。当我添加客户端代理时,它使用该现有类型,但无法将结果反序列化到派生类中。 (也许我应该提出一个全新的问题,因为它在为什么我这样做时变得稍微复杂一些?)
【解决方案2】:

据我所知,我今天花了几个小时来解决完全相同的问题。我的解决方案是使用 IDesign 的 ServiceModelEx 库中的 AddGenericResolver 方法。

注意:需要 .NET 4.0,因为它使用 DataContractResolver

你可以在IDesign Downloads page找到它。

我所要做的就是添加以下代码行:

Client.AddGenericResolver( typeof ( K2Source ) );

我希望这可以帮助其他人节省几个小时!

您可以在 Juval Lowy 所著的《Programming WCF Services: Mastering WCF and the Azure AppFabric Service Bus》一书中找到更多信息

【讨论】:

    【解决方案3】:

    还有另一种方法可以做到这一点。您无需使用“添加服务引用”来编写代理类。它最初需要更多编码,但为您提供了更稳定和强大的解决方案。我们发现,从长远来看,这可以节省我们的时间。

    见:http://www.dnrtv.com/default.aspx?showNum=122

    注意:这仅在您同时控制服务器和客户端时才有效。

    【讨论】:

      猜你喜欢
      • 2011-03-17
      • 1970-01-01
      • 1970-01-01
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-04
      相关资源
      最近更新 更多