实现支持字符串或对象作为方法参数的WCF方法

时间:2014-08-11 18:05:40

标签: c# .net web-services wcf soap

我在基于WCF的Web服务中有以下合同:

public List<string> GetAllPossibleQueryEngineHostNames(Instance instance);

我从客户端这样称呼它:

string instance = "value";
svc.GetAllPossibleQueryEngineHostNames(instance);

注意instanceString,而不是Instance对象。我得到了例外:

  

格式化程序在尝试反序列化时抛出异常   消息:尝试反序列化参数时出错   http://tempuri.org/:instance。 InnerException消息是'错误输入   第1行位置152.期待状态'元素'..遇到'文本'   名称为'',名称空间''。 ”。有关更多信息,请参阅InnerException   的信息。

我正在寻找一种方法来解决此问题,而无需更改任何客户端代码,因为我们需要能够支持连接到此服务的旧版客户端。< / p>

注意,客户端和服务器上的Instance都有一个隐式转换运算符:

public static implicit operator Instance(string value)
{
   // Converts string to an Instance
}

但是,在反序列化时,WCF似乎没有考虑到这一点。有没有办法控制WCF如何反序列化Instance个实例,允许它是字符串或Instance对象?

2 个答案:

答案 0 :(得分:2)

标准DataContractSerializer不会自动将字符串转换为实例类型。你不能在WCF中重载该方法,所以我建议你只需将字符串作为参数然后对实例进行转换。

public List<string> GetAllPossibleQueryEngineHostNames(string instanceName)
{
    var instance = Instance(instanceName);
    // Do everything else
}

我会考虑保持这种方式或添加另一个实际接受实例参数的方法。

最后一个选项是将byte []作为参数,并尝试将其解析为您需要的类型,并进行检查以确定它是字符串还是类型。

答案 1 :(得分:0)

有这个工作。这是我的代码,以防任何人对解决方案感兴趣。基本上,我使用以下类创建了一个名为Legacy.cs的新文件:

public class InstanceSerializer : XmlObjectSerializer
{
    const string localName = "instance";

    public override bool IsStartObject(XmlDictionaryReader reader)
    {
        return String.Equals(reader.LocalName, localName, StringComparison.OrdinalIgnoreCase);
    }

    public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
    {
        string xml = reader.ReadOuterXml();
        XDocument doc = XDocument.Parse(xml);

        string shortCode = doc.Descendants()
            .Where(e => e.Name.LocalName == "ShortCode")
            .Select(e => e.Value)
            .FirstOrDefault();

        string connStr = doc.Descendants()
            .Where(e => e.Name.LocalName == "ConnectionString")
            .Select(e => e.Value)
            .FirstOrDefault();

        if (connStr != null || shortCode != null) // Instance passed as Instance object
        {
            return new Instance(shortCode, connStr);
        }

        // Instance passed as String
        Instance instance = ((XElement) doc.FirstNode).Value;
        return instance;
    }

    public override void WriteEndObject(XmlDictionaryWriter writer)
    {
        writer.WriteEndElement();
    }

    public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
    {
    }

    public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
    {
        writer.WriteStartElement(localName);
    }
}

public class InstanceBehavior : DataContractSerializerOperationBehavior
{
    public InstanceBehavior(OperationDescription operation) : base(operation) { }

    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
    {
        return typeof(Instance) == type
            ? new InstanceSerializer()
            : base.CreateSerializer(type, name, ns, knownTypes);
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return typeof(Instance) == type
            ? new InstanceSerializer()
            : base.CreateSerializer(type, name, ns, knownTypes);
    }
}

public class SupportStringInstanceAttribute : Attribute, IContractBehavior
{
    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        ReplaceSerializerOperationBehavior(contractDescription);
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
    {
        ReplaceSerializerOperationBehavior(contractDescription);
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    private static void ReplaceSerializerOperationBehavior(ContractDescription contract)
    {
        foreach (OperationDescription od in contract.Operations)
        {
            for (int i = 0; i < od.Behaviors.Count; i++)
            {
                DataContractSerializerOperationBehavior dcsob = od.Behaviors[i] as DataContractSerializerOperationBehavior;
                if (dcsob != null)
                {
                    od.Behaviors[i] = new InstanceBehavior(od);
                }
            }
        }
    }
}

最后,我将[SupportStringInstance]添加到我的服务实现的顶部(接口或类应该这样做)。

注意,这仅支持反序列化 Instance对象(无论它是作为字符串还是对象传入)。您还必须实施InstanceSerializer.WriteObjectContent以支持序列化,幸运的是,我不需要这样做(至少还有)。