WCF 客户端在识别 ServiceKnownTypes 时遇到问题?

发布于 2024-09-25 09:39:04 字数 1004 浏览 8 评论 0原文

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

我知道我可以使用 [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 
{
    ...
}

How would I tell the WCF service what KnownTypes to use when passing data back to the client?

I know I can use the [ServiceKnownType] attribute, which makes the service call run fine from a WCF Test Server, however it still fails from the client. Am I missing something here?

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

Error message from client is:

{"Element
'http://schemas.datacontract.org/2004/07/BaseClassZ'
contains data from a type that maps to
the name
'http://schemas.datacontract.org/2004/07/SubClassA'.
The deserializer has no knowledge of
any type that maps to this name.
Consider using a DataContractResolver
or add the type corresponding to
'SubClassA' to the list of known types
- for example, by using the KnownTypeAttribute attribute or by
adding it to the list of known types
passed to DataContractSerializer."}

Serializing/Deserializing the object on the WCF server using a DataContractSerializer and a list of KnownTypes works fine.

UPDATE: It appears I can get the client to read the object correctly if I add KnownType attributes to the base class, but I am still looking for a way around this if possible since the base class is used for a lot of items and I don't want to have to modify the KnownType attributes on the base class anytime I add a new item.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

苍风燃霜 2024-10-02 09:39:04

为了避免阻止您的服务代码,请将已知类型放入服务的 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();
}

更新:

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


更新 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 方法则不包含已知类型属性t。事实上,这些属性应该放在服务定义中,以便类看起来像这样。

[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个数据契约在客户端和服务器之间共享,则返回集合的方法无法获取正确的已知类型属性在生成的客户端代理中。也许这是设计使然。

To avoid deterring your service code put the known types into web.config of the service:

<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>

If you want to do it by code you need to use this attribute on the service interface and not on the operation method but I would prefer the declarative approach:

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

UPDATE:

I've put up a sample project illustrating the use of web.config to configure known types which is my preferred approach. And another sample project demonstrating the second approach.


UPDATE 2:

After looking at your updated code with the Silverlight application client we notice the following definition:

[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);
}

Notice how the BeginGetSingle method contains the known type attributes while the BeginGetMany method doesn't. In fact those attributes should be placed on the service definition so that the class looks like this.

[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);
}

As this is an autogenerated class there could be a bug in the SLsvcUtil.exe and svcutil.exe as it exhibits the same behavior. Putting the known type attributes on their correct place solves the problem. The problem is that this class is autogenerated by a tool and if you try to regenerate it from the WSDL it will mess up again.

So it seems that if you have the following service definition:

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

    [OperationContract]
    BaseClassZ GetSingle();
}

And the 3 data contracts used here are shared between the client and the server when importing the definition of the service, the method that returns a collection doesn't get the correct known type attributes in the generated client proxy. Maybe this is by design.

◇流星雨 2024-10-02 09:39:04

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

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

您可以在 IDesign 下载页面 上找到它。

在我的例子中,我所要做的就是添加以下代码行:

Client.AddGenericResolver( typeof ( K2Source ) );

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

您可以在 Juval Lowy 所著的《WCF 服务编程:掌握 WCF 和 Azure AppFabric 服务总线》一书中找到更多信息

I spent hours today on what, as best I can tell, is the exact same issue. The solution for me was to use the AddGenericResolver method from IDesign's ServiceModelEx library.

NOTE: .NET 4.0 required as it uses DataContractResolver

You can find it on IDesign Downloads page.

All I had to do in my case was add the following line of code:

Client.AddGenericResolver( typeof ( K2Source ) );

I hope this helps someone else out there save a few hours!

You can find more information in the book "Programming WCF Services: Mastering WCF and the Azure AppFabric Service Bus" by Juval Lowy

野生奥特曼 2024-10-02 09:39:04

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

请参阅:http://www.dnrtv.com/default.aspx?showNum=122

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

There is another way to do this. Rather than using "add service reference" you code the proxy classes. It is a little more coding initially but gives you a much more stable and robust solution. We have found that this saves us time in the long run.

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

Note: this only works if you have control of both the server and the client.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文