8

我希望我的服务能够接受和返回派生自的类型,BaseType而无需真正知道这些类型是什么。我几乎从这篇优秀的博客文章DataContractResolver中得到了一个使用基于SharedTypeResolver 的自定义的解决方案。

难题中缺少的部分是我的服务将处理的类型可能不会被服务共享和知道,但我仍然想接受它们并知道应该是什么类型。我提出了以下充当堆栈的服务示例。您可以推送和弹出任何派生的类型,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();
    }
}

这显然是我遇到的问题的一个大大简化的例子。客户端可以很高兴地推送和弹出BaseType,或者DerivedType因为客户端和服务器都知道它们,但是如果客户端推送UnsharedType了服务不知道的内容,我会如您所料那样收到错误。

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

我目前的想法是添加IExtensibleDataObjectBaseType保存非共享类型的值,并在推送项目时使非共享类型看起来像BaseType反序列化服务;弹出项目时需要发生相反的情况。我只是不知道该怎么做。到目前为止,我对可能方法的想法:

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

我不知道这些是否可行,会涉及什么或什么是最佳解决方案。你?

4

1 回答 1

3

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

我对这个解决方案的不满在于它与服务绑定,因为我必须包含服务的 XML 命名空间并明确命名要操作的方法和参数。除此之外,它似乎工作得相当好,尽管我只在从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)
    {
    }
}
于 2013-04-16T08:33:26.313 回答