【问题标题】:WCF IErrorHandler Get Request messageWCF IErrorHandler 获取请求消息
【发布时间】:2016-03-17 13:35:15
【问题描述】:

(类似于this 问题,但在这种情况下最终不需要它)

我正在使用 IErrorHandler 实现——创建一个 FaultException 以便服务的客户端在发生未处理的异常时接收“漂亮”的错误。

我现在想在 request 消息中使用一个元素并在 FaultException 中将其退回,但我没有获胜。

我可以访问OperationContext.Current.RequestContext.RequestMessage,并且可以使用它的ToString() 查看完整的信封(包括我希望 FaultResponse 包含的“消息 ID”属性),但是我想“手动”反序列化请求消息,甚至只是将正文读取为 XML 以提取我正在寻找的请求属性。

但是我无法从消息中读取,因为(我假设)消息已经在 WCF 管道中的某个地方之前被读取过:

'该消息无法支持该操作,因为它已被复制'

这是我的 ProvideFault() 实现(我知道 XElement 解析甚至可能无法以这种形式工作——我现在不关注那个):

public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {
    if (error is FaultException)
    {
        // Let WCF handle            
    }
    else
    {
        // Extract the 'messageID' element from the request message
        var req = OperationContext.Current.RequestContext.RequestMessage;

        // Throws the 'has been copied' exception
        XElement body = XElement.Parse(XElement.ReadFrom(req.GetReaderAtBodyContents()).ToString()); 

        var msgId = (string)body.Descendants("messageID").FirstOrDefault();

        var err = new FaultException<Foo>(new Foo
            {
                MessageID = msgId
            }, "Server error");

        var msgFault = err.CreateMessageFault();
        fault = Message.CreateMessage(
            version,
            msgFault,
            null);
    } }

最好的方法是什么?

编辑:在 IErrorHandler 事件期间似乎无法读取 OperationContext 的请求消息——大概是因为它已经在 WCF 管道中读取过(因此创建了使用 MessageBuffer 的消息将不起作用)。接受的答案中描述了该问题的“解决方法”。

【问题讨论】:

  • 我认为您的问题是您在进行复制之前正在读取故障对象。这是一个by ref流,所以如果你阅读它的内容,它会导致'消息不能支持操作,因为它已被复制'的错误。尝试先复制它,如下所示: MessageBuffer buffer = fault.CreateBufferedCopy(Int32.MaxValue);故障 = buffer.CreateMessage();消息 messageFault = buffer.CreateMessage();并改用 messageFault 对象
  • 我在这里根本没有阅读故障(尽管在我的实际实现中,我正在记录它)——这是我试图处理的请求消息。对请求消息调用 CreateBufferedMessage() 也会导致引发“已复制”异常。
  • 我明白了。究竟什么时候发生错误?
  • 当我尝试阅读(或复制)请求消息时。在我上面的代码中,它是:req.GetReaderAtBodyContents()

标签: c# wcf


【解决方案1】:

一种可能的方法是使用 IDispatchMessageInspector 从请求消息中读取您需要的值并将它们存储在 OperationContext 中,直到您需要访问它们。

MSDN documentation for IDispatchMessageInspector 直截了当。请记住在阅读之前先创建消息的缓冲副本

public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
{
    var bufferedCopy = request.CreateBufferedCopy(2000000); // choose a suitable buffer size
    request = bufferedCopy.CreateMessage();// Message for continued processing

    var messageToRead = bufferedCopy.CreateMessage(); // message to read properties from

    return null;
}

请参阅此question,了解如何将您的属性附加到 OperationContext。

【讨论】:

  • 感谢使用 IExtension 的指针。我调整了我的最终解决方案来使用它。正如@burning_LEGION 的评论中提到的那样,我最终选择了将请求属性“存储”在 DispatchMessageInspector 中,然后在 ProvideFault() 期间访问它们的路线。
  • MSDN 文档的链接与 OperationContext 的引用相同。
【解决方案2】:

你应该复制原始消息的缓冲区:

// Read orignal messagem
var originalMessage = OperationContext.Current.RequestContext.RequestMessage;

// Copy message
MessageBuffer buffer = originalMessage.CreateBufferedCopy(Int32.MaxValue);
//OperationContext.Current.RequestContext.RequestMessage = buffer.CreateMessage();

var req = buffer.CreateMessage();
XElement body = XElement.Parse(XElement.ReadFrom(req.GetReaderAtBodyContents()).ToString());

希望对你有帮助。

【讨论】:

    【解决方案3】:

    你应该自定义你的异常并使用它的属性来保持Id

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault) 
    {
        var myException = error as MyException;
        if (myException == null)
        {
            return;           
        }
    
        var err = new FaultException<Foo>(new Foo
            {
                MessageID = myException.MsgId
            }, "Server error");
    
        var msgFault = err.CreateMessageFault();
        fault = Message.CreateMessage(
            version,
            msgFault,
            null);
    } 
    

    【讨论】:

    • 我正在尝试迎合未经处理的异常;将所有异常包装在我自己的自定义异常中将涉及“手动”捕获 System.Exceptions 并在每个服务方法中重新抛出我的自定义异常......这正是我想要摆脱的。
    • 为什么要避免在我的 N 个服务方法中重复相同的自定义异常重新抛出?我宁愿以一种通用的方式在一个集中的位置处理它,并认为 IErrorHandler 是它的地方!
    • 恕我直言,这是个坏主意,所以我不确定您是否可以重新阅读消息,因此请尝试查找所需的属性,可能是这个OperationContext.Current.IncomingMessageHeaders.MessageId
    • 谢谢——我也不确定它有多好;任何连接的客户端都应该有它发送的请求的上下文,所以我认为反弹属性没有多大用处。不幸的是,我正在重新实现现有的 web 服务,并且要求 API 保持绝​​对不变。我的“messageID”示例实际上是一种简化;大约有 5 个属性需要设置。然而,也许使用 Headers / Properties 可能是一个想法:拦截请求,添加必要的属性,然后通过 OperationContext 在 ProvideFault() 期间读取这些属性..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多