首页 文章

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

提问于
浏览
7

我如何告诉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 
{
    ...
}

3 回答

  • 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.exesvcutil.exe中可能存在一个错误,因为它表现出相同的行为 . 将已知类型属性放在正确的位置可以解决问题 . 问题是这个类是由一个工具自动生成的,如果你试图从WSDL重新生成它,它将再次陷入困境 .

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

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

    此处使用的3个数据协定在导入服务定义时在客户端和服务器之间共享,返回集合的方法不会在生成的客户端代理中获取正确的已知类型属性 . 也许这是设计的 .

  • 1

    今天我花了几个小时来讨论完全相同的问题 . 我的解决方案是使用IDesign的ServiceModelEx库中的AddGenericResolver方法 .

    注意:.NET 4.0需要使用DataContractResolver

    你可以在IDesign Downloads page找到它 .

    在我的案例中我只需要添加以下代码行:

    Client.AddGenericResolver( typeof ( K2Source ) );
    

    我希望这可以帮助别人在那里节省几个小时!

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

  • 0

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

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

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

相关问题