为啥 Azure WebJob ServiceBus 默认反序列化 XML?

Posted

技术标签:

【中文标题】为啥 Azure WebJob ServiceBus 默认反序列化 XML?【英文标题】:Why is Azure WebJob ServiceBus deserializing XML by default?为什么 Azure WebJob ServiceBus 默认反序列化 XML? 【发布时间】:2015-03-02 15:35:02 【问题描述】:

我有一个简单的 Azure WebJobs ServiceBusTrigger,看起来像

public static async void ProcessQueueMessage([ServiceBusTrigger("myqueuename")] String json, TextWriter log)  ... 

不幸的是,它无法将 JSON 反序列化为 XML(不足为奇)。我检查了有效载荷并确认它只是一个 UTF-8 编码的字节数组。我有两个问题。

    为什么假定我的字符串是 XML? 我怎么知道它没有,没有 XML,只有一个字符串?

堆栈跟踪:

System.InvalidOperationException: Exception binding parameter 'json' ---> System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type System.String. The input source is not correctly formatted. ---> System.Xml.XmlException: The input source is not correctly formatted.
 at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
 at System.Xml.XmlBufferReader.ReadValue(XmlBinaryNodeType nodeType, ValueHandle value)
 at System.Xml.XmlBinaryReader.ReadNode()
 at System.Xml.XmlBinaryReader.Read()
 at System.Xml.XmlBaseReader.IsStartElement()
 at System.Xml.XmlBaseReader.IsStartElement(XmlDictionaryString localName, XmlDictionaryString namespaceUri)
 at System.Runtime.Serialization.XmlReaderDelegator.IsStartElement(XmlDictionaryString localname, XmlDictionaryString ns)
 at System.Runtime.Serialization.XmlObjectSerializer.IsRootElement(XmlReaderDelegator reader, DataContract contract, XmlDictionaryString name, XmlDictionaryString ns)
 at System.Runtime.Serialization.DataContractSerializer.InternalIsStartObject(XmlReaderDelegator reader)
 at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
 at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
 --- End of inner exception stack trace ---
 at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
 at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName)
 at Microsoft.ServiceBus.Messaging.DataContractBinarySerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName)
 at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlReader reader, Boolean verifyObjectName)
 at System.Runtime.Serialization.XmlObjectSerializer.InternalReadObject(XmlReaderDelegator reader, Boolean verifyObjectName)
 at System.Runtime.Serialization.XmlObjectSerializer.InternalReadObject(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
 at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
 at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
 at Microsoft.ServiceBus.Messaging.DataContractBinarySerializer.ReadObject(Stream stream)
 at Microsoft.ServiceBus.Messaging.BrokeredMessage.GetBody[T](XmlObjectSerializer serializer)
 at Microsoft.ServiceBus.Messaging.BrokeredMessage.GetBody[T]()
 at Microsoft.Azure.WebJobs.ServiceBus.Triggers.BrokeredMessageToStringConverter.ConvertAsync(BrokeredMessage input, CancellationToken cancellationToken)
 at Microsoft.Azure.WebJobs.ServiceBus.Triggers.ConverterArgumentBindingProvider`1.ConverterArgumentBinding.<BindAsync>d__0.MoveNext()

编辑:WebJobs 文档建议不仅我所做的工作 (String),而且 ServiceBusTrigger 应该自动反序列化 JSON 对象。但是,如果我尝试取出我的 POCO,我仍然会收到 XML 反序列化错误。有趣的是,如果我将类型设置为 Byte[],我也会收到 XML 反序列化错误,这也应该可以工作。

编辑 2:Stream 也不起作用。似乎 only BrokeredMessage 适用于触发器,而 GetBody 是我能找到从 BrokeredMessage 中获取字符串的唯一方法。

【问题讨论】:

我遇到了这个确切的问题,但通过尝试下面的@mahesh-kshirsagar 的CustomMessageProvider 解决方案发现消息的ContentType 是“json”,而它应该是“ 应用程序/json";这不是辅助角色 ServiceBus 处理程序的问题,但“自动”WebJob 反序列化回退到“application/xml”。幸运的是,我能够修复引发消息的 ContentType 【参考方案1】:

我能够通过添加自定义消息处理器来完成反序列化工作。

自定义消息处理器示例可在 https://github.com/Azure/azure-webjobs-sdk-samples/tree/master/BasicSamples/MiscOperations 获得

您将 ContentType 设置为 application/json 如下 -

public class CustomMessagingProvider : MessagingProvider

    private readonly ServiceBusConfiguration _config;

    public CustomMessagingProvider(ServiceBusConfiguration config) : base(config)
    
        _config = config;
    

    public override MessageProcessor CreateMessageProcessor(string entityPath)
    
        return new CustomMessageProcessor(_config.MessageOptions);
    

    private class CustomMessageProcessor : MessageProcessor
    
        public CustomMessageProcessor(OnMessageOptions messageOptions)
            : base(messageOptions)
        
        

        public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
        
            message.ContentType = "application/json";

            return base.BeginProcessingMessageAsync(message, cancellationToken);
        
    

然后在 webjob 中配置 ServiceBusConfiguration 时设置此消息处理器。

 JobHostConfiguration config = new JobHostConfiguration();
        ServiceBusConfiguration serviceBusConfig = new ServiceBusConfiguration
        
            ConnectionString = _servicesBusConnectionString
        ;

        serviceBusConfig.MessagingProvider = new CustomMessagingProvider(serviceBusConfig);

        config.UseServiceBus(serviceBusConfig);

【讨论】:

【参考方案2】:

如果你的有效载荷是字符串,你有两个选择:

    将参数类型改为BrokeredMessage,然后自己反序列化 将BrokeredMessage.ContentType 属性设置为text/plain(假设您可以控制生成消息的代码)

除了需要内容类型的奇怪String 情况外,规则是服务总线有效负载只能反序列化为与有效负载相同的对象。这是因为 ServiceBus 二进制序列化程序。

【讨论】:

我不知道有内容类型,谢谢!我回家后会摆弄它,但你知道我是否将它设置为正确的东西(在这种情况下为application/json),我仍然可以拉出一个字符串并手动反序列化吗? 不,很遗憾,它不像 Azure 存储队列那样反序列化 JSON POCO,因此将内容类型设置为 application/json 不会做任何事情 即使它不会自动反序列化它,如果我将它设置为 application/json,我是否至少能够在不尝试将其反序列化为 XML 的情况下提取字符串?我可以很容易地自己解析它,我只想能够将内容类型设置为实际的内容,而不是不得不声称消息是文本/纯文本,即使它不是。 这个答案“解决”了我的问题,(将消息上的内容类型设置为 text/plain)但它仍然没有回答为什么 ServiceBusTrigger 试图反序列化 application/xml 以外的任何内容XML 反序列化器。我确实尝试将内容类型设置为 application/json,但它仍然尝试使用 XML 反序列化器。 实际上,这似乎是 ServiceBus 问题,而不是 Azure WebJobs 问题。我将整理一个更简单的重现案例并提交一个错误(但是对于服务总线这样做)。【参考方案3】:

在回答 #2 时,一个巧妙的解决方法是从 ServiceBusTrigger 获取 BrokeredMessage 并调用 message.ToBody&lt;Stream&gt;()。然后你可以使用普通的东西将字节流转换为字符串。

将 JSON 编码的有效负载反序列化为对象的示例:


public static async void ProcessQueueMessage([ServiceBusTrigger("my-queue")] BrokeredMessage message)

    var stream = message.ToBody();
    using (var streamReader = new StreamReader(stream, Encoding.UTF8)
    
        var json = await streamReader.ReadToEndAsync();
        var deserialized = JsonConvert.DeserializeObject(json);
    

注意:如果有人有更好的答案,我仍然对它感兴趣。在一个完美的世界里,我可以为 ServiceBusTrigger 提供一个自定义反序列化器(基于 JSON.net 或只是一个字符串反序列化器),但如果可能的话,我不知道该怎么做。

【讨论】:

看看我的回答。我认为它可能更接近您正在寻找的东西。【参考方案4】:

我在研究这个问题时发现了一些巫术。

我们在二进制序列化方面做得很好,但想看看当我们通过 webjobs 仪表板观察 webjobs 处理事件时消息的正文是什么。我从 github 上的 WebJobs ServiceBus 代码中窃取了代码来解决这个问题。对于为什么必须将 poco 序列化为 JSON 并将其打包到内存流中,然后将内容类型设置为 application/json,我仍然有点困惑。我的结论是 Azure 服务总线 SDK 内部一定有一些东西需要它。我还没有找到该代码,但在测试中效果很好。

首先我们遇到了这种情况,在 webjobs 仪表板中被序列化的对象只是字节。

使用下面的代码将消息放在总线上后,我们最终得到:

我们不必更改消费端的签名,现在我们可以看到 BrokeredMessage 的正文。 public void PersistEvents([ServiceBusTrigger(EventProcessor.CoolTopicName, LoggingSubscription)] EventDto eventDto, TextWriter logger)

这里是代码 Azure ServiceBus SDK 修复了它。 这是我的示例代码(从 SDK 借来的): string json = JsonConvert.SerializeObject(eventDto); // Using Json.net here MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json), writable: false); BrokeredMessage message = new BrokeredMessage(stream); message.ContentType = "application/json"; client.Send(message);

【讨论】:

以上是关于为啥 Azure WebJob ServiceBus 默认反序列化 XML?的主要内容,如果未能解决你的问题,请参考以下文章

Azure 触发的 Webjob - 检测 webjob 何时停止

Laravel 队列和 Azure WebJob

禁用触发的 Azure WebJob

Azure - 将 WCF 服务作为 WebJob 运行

将 WebJob 部署到 Azure 时出错?

在 azure 中创建 webjob 时出现 python 包安装错误