IErrorHandler中的Message.CreateMessage创建不带包装元素/命名空间的不同消息

时间:2012-05-04 16:42:48

标签: wcf serialization soap datacontract ierrorhandler

由于尝试从我们所有的Web服务中删除面向方面的代码,我遇到了一个奇怪的问题。

有一个IParameterInspector,它执行验证并抛出一个在返回响应的IErrorHandler中捕获的FaultException。

ResponseBase看起来像这样:

 [DataContract(Namespace = Namespaces.Data)]
public class ResponseBase
{
    public ResponseBase()
    {
        this.Build = BuildHelper.GetBuild();
    }

    [DataMember(Name = CommonParameterNames.RequestId, Order = 0)]
    public string RequestId { get; set; }

    [DataMember(Name = CommonParameterNames.Status, Order = 1)]
    public string Status { get; set; }

    [DataMember(Name = CommonParameterNames.Errors, Order = 2, EmitDefaultValue = false)]
    public Error[] Errors { get; set; }

    [DataMember(Name = CommonParameterNames.Build, Order = 3, EmitDefaultValue = false)]
    public Build Build { get; set; }
}

这是Web服务方法创建的所有响应的基类。

当从wervice方法返回而没有发生错误或验证错误时,Message在主体中包含一个包装器元素,名称为“response”,紧跟在方法名称本身的开始标记之后:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>
  <BulkUploadImageBase64Response xmlns="http://www.mydomain.com/webapi/">
     <response xmlns:a="http://www.mydomain.com/webapi/data/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
         <a:request_id>

但是,如果我尝试使用Message.CreateMessage()方法处理IErrorHandler中的错误,从FaultException传递ResponseBase对象,结果会有所不同:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>
  <BulkUploadImageResponse xmlns="http://www.mydomain.com/webapi/data/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
     <request_id>e505d0d3-fc5d-474f-8ec7-5c7521c8a869</request_id>
     <status>DataValidationError</status>

错误处理程序返回的消息似乎以不同的方式序列化。由于某种原因,缺少“响应”包装元素。

这是错误处理程序实现(摘录):

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        if (error is FaultException<ResponseBase>)
        {
            var fe = error as FaultException<ResponseBase>;
            fault = Message.CreateMessage(version, null, fe.Detail);
        }

我已经尝试过不同的东西:将BodyReader与DataContractSerializer一起使用,将MessageContract属性添加到R​​esponseBase类,依此类推,但我不能让它看起来像框架隐式生成的响应消息。

非常感谢任何帮助,非常感谢!

1 个答案:

答案 0 :(得分:0)

我知道这是一个老问题,但是我遇到了同样的问题,并设法从ProvideFault获得了答复,以匹配通常的答复。

这是要点:

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
    if (error is FaultException<ResponseBase>)
    {
        var fe = error as FaultException<ResponseBase>;
        var action = OperationContext.Current.IncomingMessageHeaders.Action;
        var operation = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o => o.Action == action);

        // Instantiate a serializer that will wrap the response like the service would, based on the operation name
        var serializer = new FaultSerializer(operation.Name);

        // Create the message
        fault = Message.CreateMessage(version, operation.ReplyAction, fe.Detail, serializer);
    }
}

然后,序列化器将如下所示:

using System.Runtime.Serialization;
using System.Xml;

namespace MyNamespace
{
    internal class FaultSerializer: XmlObjectSerializer
    {
        private readonly string _actionName;

        public FaultSerializer(string actionName)
        {
            _actionName = actionName;
        }

        public override bool IsStartObject(XmlDictionaryReader reader)
        {
            // Not used
            throw new System.NotImplementedException();
        }

        public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
        {
            // Not used
            throw new System.NotImplementedException();
        }

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

        public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
        {
            // Instead of naming the main object element with the object type,
            // append Result to the action name
            var xmlDictionary = new XmlDictionary();
            var settings = new DataContractSerializerSettings {
                RootName = xmlDictionary.Add($"{_actionName}Result")
            };

            var serializer = new DataContractSerializer(graph.GetType(), settings);
            serializer.WriteObject(writer, graph);
        }

        public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
        {
            // Wrap the entire object in a Response element
            writer.WriteStartElement($"{_actionName}Response", "http://tempuri.org/");
        }
    }
}

希望这会有所帮助!