【问题标题】:Force WCF Rest client to use Json deserializer regardless of content-type无论内容类型如何,强制 WCF Rest 客户端使用 Json 反序列化器
【发布时间】:2012-11-05 02:36:40
【问题描述】:

无论内容类型如何,如何强制 WCF Rest 客户端使用 Json 反序列化器?

我正在通过 WCF 调用基于 REST 的 Web 服务。

服务返回 JSON 正文,但内容类型为“Application/xml”。 WCF 框架现在给了我 XmlException。

public class MessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter _formatter;

    public MessageFormatter(IClientMessageFormatter formatter)
    {
        _formatter = formatter;
    }

    public object DeserializeReply(System.ServiceModel.Channels.Message message, object[] parameters)
    {
        return _formatter.DeserializeReply(message, parameters);
    }
}

_formatter.DeserializeReply 正在抛出 XmlException。我在任何地方都找不到任何示例来强制回复时进行 json 反序列化。

编辑 - 鼠标悬停时的“消息”对象抛出“{...错误读取正文:System.Xml.XmlException:根级别的数据无效。第1行,位置1....}”

我的另一个项目中与不同的 REST 服务(Picasa Web 服务)通信的同一个对象有一个看起来像 JSON 对象的 xml 序列化版本的东西?所以这个问题似乎更进一步。我需要找到这个对象的来源。我会去玩 MessageEncoder 类。

编辑 - (添加更多信息)

public class MyBinding : WebHttpBinding
{
    public MyBinding(WebHttpSecurityMode mode)
        : base(mode)
    {

    }

    public override BindingElementCollection CreateBindingElements()
    {
        var result = base.CreateBindingElements();

        var replacements = result.OfType<MessageEncodingBindingElement>().ToList();
        foreach (var messageEncodingBindingElement in replacements)
        {
            var index = result.IndexOf(messageEncodingBindingElement);
            result.Remove(messageEncodingBindingElement);
            result.Insert(index, new MyMessageEncodingBindingElement(messageEncodingBindingElement));
        }

        return result;
    }
}

public class MyMessageEncodingBindingElement : MessageEncodingBindingElement
{
    private readonly MessageEncodingBindingElement _element;

    public MyMessageEncodingBindingElement(MessageEncodingBindingElement element)
    {
        _element = element;
    }

    public override BindingElement Clone()
    {
        var result = _element.Clone();

        if (result is MessageEncodingBindingElement)
            return new MyMessageEncodingBindingElement(result as MessageEncodingBindingElement);

        return result;
    }

    public override MessageEncoderFactory CreateMessageEncoderFactory()
    {
        return new MyMessageEncoderFactory(_element.CreateMessageEncoderFactory());
    }
}

即使在设置断点时遇到构造函数和 Clone 方法,也永远不会调用 CreateMessageEncoderFactory() 方法。有什么帮助吗?我正在尝试设置自定义MessageEncoder和MessageEncoderFactory类来修改Message对象的实例化过程。

【问题讨论】:

  • 顺便说一句,来自 iContact.com 背后的优秀团队。 xml 上没有 xml 声明。 json 响应的内容类型错误。如果节点的名称没有用双引号括起来,反序列化 json 会出现问题。哪个不称职的白痴在那里管理他们的团队。他们甚至无法通过垃圾邮件过滤器收到一封“欢迎”信。
  • 您是否将 WebGetAttribute / WebInvokeAttribute 的 RequestFormat 和 ResponseFormat 属性设置为 WebMessageFormat.Json?即 [WebGet(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
  • 是的,我是。 [WebInvoke(Method = "GET", UriTemplate = "messages", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
  • 您能否检查您发送的 http 标头以查看它是发送“Accept”标头还是仅发送“Content-Type”?
  • 对不起,我已经尽力了,但看起来这个问题超出了我的范围。祝你好运。 (老实说,您可能只想给他们打个电话,看看他们以前是否遇到过这个问题。他们可能希望“Accept”标头是非标准的确切字符串)

标签: json wcf rest


【解决方案1】:

您可以为此使用WebContentTypeMapper。这是WebHttpBinding 的一个属性,您可以自定义编码器如何从该绑定中完成反序列化,包括强制它始终使用 JSON 反序列化器,而不管传入消息的 Content-Type。下面的代码展示了如何做到这一点。

public class StackOverflow_13225272
{
    [DataContract]
    public class Person
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("Person[Name={0},Age={1}]", Name, Age);
        }
    }
    [ServiceContract]
    public interface ITest
    {
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        Person GetPerson(string responseContentType);
    }

    public class Service : ITest
    {
        public Person GetPerson(string responseContentType)
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = responseContentType;
            return new Person { Name = "John Doe", Age = 29 };
        }
    }
    class AllJsonContentTypeMapper : WebContentTypeMapper
    {
        public override WebContentFormat GetMessageFormatForContentType(string contentType)
        {
            return WebContentFormat.Json;
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
        host.Open();
        Console.WriteLine("Host opened");

#if USE_NETFX4
        // This works on .NET 4.0 and beyond
        WebHttpBinding binding = new WebHttpBinding();
        binding.ContentTypeMapper = new AllJsonContentTypeMapper();
#else
        // This works on .NET 3.5
        CustomBinding binding = new CustomBinding(new WebHttpBinding());
        binding.Elements.Find<WebMessageEncodingBindingElement>().ContentTypeMapper = new AllJsonContentTypeMapper();
        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
#endif

        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
        factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
        ITest proxy = factory.CreateChannel();

        Console.WriteLine("With JSON: {0}", proxy.GetPerson("application/json"));
        Console.WriteLine("With XML: {0}", proxy.GetPerson("application/xml"));

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}

【讨论】:

  • 适用于 .net 4.0。试图弄清楚如何将其用于 .net 3.5,因为 WebHttpBinding 没有该属性。
  • 好的。找到了。创建一个继承自 WebHttpBinding 类并实现“public override BindingElementCollection CreateBindingElements()”的类。调用“base.CreateBindingElements();”从元素列表中,找到“WebMessageEncodingBindingElement”类型的元素。将 ContentTypeMapper 属性设置为 .net 3.5 的自定义“AllJsonContentTypeMapper”。请将其添加到答案中,以便我可以勾选它。非常感谢!
  • 你实际上不需要创建一个新类;只需改用CustomBinding,并在WebMessageEncodingBindingElement 上设置内容类型映射器。更新了代码以显示这一点。
  • CustomBinding 和 WebHttpBinding 在 .net framework 3.5 中没有该属性。是的,4.0 就是这种情况。 msdn.microsoft.com/en-us/library/…
  • CustomBinding(或WebHttpBinding)没有,但WebMessageEncodingBindingElement 类有。查看更新的代码。
【解决方案2】:

这可能有效。

public class ForceJsonClientMessageFormatter : IClientMessageFormatter
{
    private readonly DataContractJsonSerializer _jsonSerializer;

    public ForceJsonClientMessageFormatter(Type responseType)
    {
        _jsonSerializer = new DataContractJsonSerializer(responseType);
    }

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        throw new NotImplementedException("This client message formatter is for replies only!");
    }

    public object DeserializeReply(Message message, object[] parameters)
    {
        string messageBody = message.GetBody<string>();

        using (MemoryStream messageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)))
        {
            messageStream.Seek(0, SeekOrigin.Begin);
            object deserializedObject = _jsonSerializer.ReadObject(messageStream);
            return deserializedObject;
        }
    }
}

public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
    protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
    {
        return new ForceJsonClientMessageFormatter(operationDescription.Messages[1].Body.ReturnValue.Type);
    }
}

【讨论】:

  • 抱歉,“反序列化 System.String 类型的对象时出错。根级别的数据无效。第 1 行,位置 1。”关于“字符串 messageBody = message.GetBody();”
  • 就像说谢谢一样。我不能投票给你的答案,因为他们还没有解决这个问题,但我已经投票给 cmets。
  • 明白,没问题。不幸的是,您不会因为投票支持 cmets 而享有盛誉,所以还是谢谢您。 =)
【解决方案3】:

我还没有尝试过,但我认为这会奏效。您可以创建一个自定义 IClientMessageFormatter,它将消息格式覆盖为 Json,将其包装在一个行为中,然后将该行为应用于您的客户端端点配置。

public class ForceJsonClientMessageFormatterDecorator : IClientMessageFormatter
{
    private readonly IClientMessageFormatter _decoratedFormatter;
    public ForceJsonClientMessageFormatterDecorator(IClientMessageFormatter decoratedFormatter)
    {
        _decoratedFormatter = decoratedFormatter;
    }

    public object DeserializeReply(Message message, object[] parameters)
    {
        message.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);
        return _decoratedFormatter.DeserializeReply(message, parameters);
    }

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        return _decoratedFormatter.SerializeRequest(messageVersion, parameters);
    }
}

public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
    protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
    {
        IClientMessageFormatter decoratedFormatter = base.GetReplyClientFormatter(operationDescription, endpoint);
        return new ForceJsonClientMessageFormatterDecorator(decoratedFormatter);
    }
}

【讨论】:

  • 谢谢,但没有运气。我发布的课程确实是一个 IClientMessageFormatter。 message.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);没有帮助。消息对象抛出异常,表示它到达此阶段时已经不是有效的 xml。必须在上游。
  • 只是想在这里覆盖我的基础......你是让它运行并看到异常被抛出,还是你在 DeserializeReply 的第一行设置断点并在该行之前检查消息对象有机会跑吗?如果是后者,您对该对象的检查将导致该对象上的所有 getter 触发,并可能导致异常发生。
  • 作为测试,您能否创建一个不调用 base.GetReplyClientFormatter(operationDescription, endpoint) 而是直接返回一个新的 ForceJsonClientMessageFormatterDecorator 的行为?但是你必须摆脱 decoratedFormatter 并在你的 ClientMessageFormatter 中使用 DataContractJsonSerializer 来序列化/反序列化你的消息。如果您需要帮助设置,我可以在另一个答案中提供代码示例。
  • “你是让这个运行并看到异常被抛出,还是你在 DeserializeReply 的第一行设置断点并在该行有机会运行之前检查消息对象?”是的,就是这样。我断点和鼠标悬停,我在“消息”类中看到了异常。但无论我是否断点/鼠标悬停,下一行都会引发异常。
  • “但是你必须摆脱 decoratedFormatter 并在你的 ClientMessageFormatter 中使用 DataContractJsonSerializer 来序列化/反序列化你的消息。”我不确定如何从消息中获取响应字符串。如果我能得到它,那就太好了。
猜你喜欢
  • 1970-01-01
  • 2014-11-21
  • 2012-10-10
  • 2011-01-08
  • 1970-01-01
  • 1970-01-01
  • 2012-03-22
  • 2017-04-26
  • 1970-01-01
相关资源
最近更新 更多