将WCF服务中的派生类型反序列化为基类型,但保留类型信息

时间:2013-04-09 12:51:19

标签: wcf serialization types datacontractserializer

我希望我的服务能够接受并返回从BaseType派生的类型,而不知道这些类型是什么。我几乎已经使用基于SharedTypeResolver from this excellent blog post的自定义DataContractResolver获得了解决方案。

这个难题的缺失部分是我的服务将处理的类型可能不会被服务共享和知道,但我仍然希望接受它们并且知道该类型应该是什么。我已经提出了以下作为堆栈的服务示例。如果您使用BaseType并且类型在客户端和服务器之间共享,则可以推送和弹出从SharedTypeResolver派生的任何类型。

[DataContract]
public class BaseType
{
    [DataMember]
    public string SomeText { get; set; }

    public override string ToString()
    {
        return this.GetType().Name + ": " + this.SomeText;
    }
}

[DataContract]
public class DerivedType : BaseType
{
    [DataMember]
    public int SomeNumber { get; set; }

    public override string ToString()
    {
        return base.ToString() + ", " + this.SomeNumber;
    }
}

[ServiceContract]
public interface ITypeStack
{
    [OperationContract]
    void Push(BaseType item);

    [OperationContract]
    BaseType Pop();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class TypeStackService : ITypeStack
{
    private Stack<BaseType> stack = new Stack<BaseType>();

    public void Push(BaseType item)
    {
        this.stack.Push(item);
    }

    public BaseType Pop()
    {
        return this.stack.Pop();
    }
}

这显然是我遇到的问题的一个简化示例。客户端可以非常快乐地推送和弹出BaseTypeDerivedType,因为客户端和服务器都知道它们,但是如果客户端推送服务不知道的UnsharedType,那么我会收到错误期待。

  

格式化程序在尝试反序列化时抛出异常   消息:尝试反序列化参数时出错   http://tempuri.org/:item。 InnerException消息是'错误输入   第1行位置316.元素“http://tempuri.org/:item”包含数据   从映射到名称'TestWcfClient,Version = 1.0.0.0的类型,   Culture = neutral,PublicKeyToken = null:TestWcfClient.UnsharedType'。该   反序列化器不知道映射到此名称的任何类型。   考虑更改您的ResolveName方法的实现   DataContractResolver返回name的非null值   'TestWcfClient.UnsharedType'和命名空间'TestWcfClient,   Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'。'。请参阅   InnerException以获取更多详细信息。

我目前的想法是将IExtensibleDataObject添加到BaseType以保存非共享类型的值,并在项目被推送时使非共享类型看起来像BaseType到反序列化服务;当弹出一个项目时,需要进行相反的操作。我只是不确定如何去做。到目前为止我对可能的方法的看法:

  • 进一步自定义DataContractResolver,可能涉及TypeDelegator
  • 使用IDataContractSurrogate代替非共享类型
  • 以某种方式保留按下项目时收到的服务的序列化XML,然后在弹出项目时在回复中使用此
  • 使用消息检查器操作消息

我不知道这些是否有效,涉及的内容或最佳解决方案是什么。你呢?

1 个答案:

答案 0 :(得分:3)

我使用消息检查器和实现IExtensibleDataObject的占位符类型在此方面取得了一些进展。检查器maniuplates传入的消息并将类型提示更改为占位符的类型提示,并将原始类型添​​加为属性。当然后在回复中发送类型时会发生相反的情况,从而使占位符看起来像原始类型。

我对此解决方案的不满是它与服务绑定,因为我必须包含服务的XML命名空间并明确命名要进行maniuplated的方法和参数。除此之外似乎工作得相当好,虽然我只是在BaseType派生的相当简单的类型上进行了测试。

有人可以改进吗?这对你有好处。

public class PlaceholderType : BaseType, IExtensibleDataObject
{
    [IgnoreDataMember]
    public string OriginalTypeName { get; set; }

    [IgnoreDataMember]
    public string OriginalNamespace { get; set; }

    ExtensionDataObject IExtensibleDataObject.ExtensionData { get; set; }
}

public class FunkadelicInspector : IDispatchMessageInspector, IContractBehavior
{
    const string PlaceholderNamespace = "http://my.placeholder.namespace";
    const string ServiceNamespace = "http://tempuri.org/";

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        XmlDocument xmlDoc = ReadMessage(request);

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
        // Dislike: having to know the service namespace, method and parameters
        nsmgr.AddNamespace("s", ServiceNamespace);
        XmlNode itemElement = xmlDoc.SelectSingleNode("//s:Push/s:item", nsmgr);

        if (itemElement != null)
        {
            XmlAttribute typeAttribute = itemElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"];
            if (typeAttribute != null)
            {
                // Record original type
                string[] parts = typeAttribute.Value.Split(':');
                string originalTypeName = parts[1];
                // Replace with placeholder type
                typeAttribute.Value = parts[0] + ":" + typeof(PlaceholderType).FullName;

                // Record original assembly
                XmlAttribute nsAtt = itemElement.Attributes["xmlns:" + parts[0]];
                string originalAssembly = nsAtt.Value;
                // Replace with placeholder type's assembly
                nsAtt.Value = typeof(PlaceholderType).Assembly.FullName;

                // Add placeholders
                itemElement.AppendChild(xmlDoc.CreateElement("OriginalType", PlaceholderNamespace)).InnerText = originalTypeName;
                itemElement.AppendChild(xmlDoc.CreateElement("OriginalAssembly", PlaceholderNamespace)).InnerText = originalAssembly;
            }
        }

        //Now recreate the message
        request = WriteMessage(request, xmlDoc);
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        XmlDocument xmlDoc = ReadMessage(reply);

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
        nsmgr.AddNamespace("s", ServiceNamespace);
        nsmgr.AddNamespace("plc", PlaceholderNamespace);
        // Dislike: having to know the service namespace, method and parameters
        XmlNode resultElement = xmlDoc.SelectSingleNode("//s:PopResponse/s:PopResult", nsmgr);

        if (resultElement != null)
        {
            XmlElement originalType = resultElement.SelectSingleNode("plc:OriginalType", nsmgr) as XmlElement;
            XmlElement originalAssembly = resultElement.SelectSingleNode("plc:OriginalAssembly", nsmgr) as XmlElement;
            if (originalType != null && originalAssembly != null)
            {
                // Replace original type
                XmlAttribute type = resultElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"];
                string[] parts = type.Value.Split(':'); // 0 is an alias for the assembly, 1 is the type
                type.Value = parts[0] + ":" + originalType.InnerText;

                // Replace original assembly
                XmlAttribute ass = resultElement.Attributes["xmlns:" + parts[0]];
                ass.Value = originalAssembly.InnerText;

                // Remove placeholders
                resultElement.RemoveChild(originalType);
                resultElement.RemoveChild(originalAssembly);
            }
        }

        //Now recreate the message
        reply = WriteMessage(reply, xmlDoc);
    }

    private static Message WriteMessage(Message original, XmlDocument xmlDoc)
    {
        MemoryStream ms = new MemoryStream();
        xmlDoc.Save(ms);
        ms.Position = 0;
        XmlReader reader = XmlReader.Create(ms);
        Message newMessage = Message.CreateMessage(reader, int.MaxValue, original.Version);
        newMessage.Properties.CopyProperties(original.Properties);
        return newMessage;
    }

    private static XmlDocument ReadMessage(Message message)
    {
        MemoryStream ms = new MemoryStream();
        using (XmlWriter writer = XmlWriter.Create(ms))
        {
            message.WriteMessage(writer); // the message was consumed here
            writer.Flush();
        }
        ms.Position = 0;
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(ms);
        return xmlDoc;
    }

    void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.MessageInspectors.Add(this);
    }

    void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }
}