如何告诉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服务器上的对象进行序列化/反序列化工作正常。
UPDATE:如果我将KnownType属性添加到基类,我可以让客户端正确读取对象,但是我仍在寻找一种方法,如果可能的话,因为基类用于很多项目,我不想在我添加新项目时随时修改基类的KnownType属性。
[DataContract]
[KnownType(typeof(SubClassA))]
[KnownType(typeof(SubClassB))]
public class BaseClassZ
{
...
}
答案 0 :(得分:10)
为了避免阻止您的服务代码,将已知类型放入服务的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.exe和svcutil.exe
可能存在错误,因为它表现出相同的行为。将已知类型属性放在正确的位置可以解决问题。问题是这个类是由一个工具自动生成的,如果你试图从WSDL重新生成它,它将再次陷入困境。
所以看来如果您有以下服务定义:
[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IService1
{
[OperationContract]
BaseClassZ[] GetMany();
[OperationContract]
BaseClassZ GetSingle();
}
此处使用的3个数据协定在导入服务定义时在客户端和服务器之间共享,返回集合的方法不会在生成的客户端代理中获取正确的已知类型属性。也许这是设计的。
答案 1 :(得分:1)
我今天花了几个小时来讨论完全相同的问题。我的解决方案是使用IDesign的ServiceModelEx库中的AddGenericResolver方法。
注意:在使用DataContractResolver
时需要.NET 4.0您可以在IDesign Downloads page找到它。
在我的案例中,我只需添加以下代码行:
Client.AddGenericResolver( typeof ( K2Source ) );
我希望这可以帮助别人在那里拯救几个小时!
您可以在Juval Lowy的“编程WCF服务:掌握WCF和Azure AppFabric服务总线”一书中找到更多信息
答案 2 :(得分:0)
还有另一种方法可以做到这一点。您可以对代理类进行编码,而不是使用“添加服务引用”。它最初是一个更多的编码,但为您提供了一个更加稳定和强大的解决方案。我们发现从长远来看,这节省了我们的时间。
请参阅:http://www.dnrtv.com/default.aspx?showNum=122
注意:这仅在您控制服务器和客户端时才有效。