【问题标题】:Change the response output format in XML and JSON only for webHttpBinding仅针对 webHttpBinding 更改 XML 和 JSON 中的响应输出格式
【发布时间】:2016-04-28 14:52:48
【问题描述】:

我已经研究这个问题很长时间了,以下是我的发现和要求:

我们有两个端点:

  • TCP 端点
  • WebHttp 端点

通过 WebHttp 端点,我们需要支持 JSON 和 XML,但需要自定义响应格式。这是所需的格式(为清楚起见仅显示 JSON):

{
    "status": "success",
    "data" : {}
}

我们需要的是让每个返回的对象正常序列化并放在层次结构中的数据下。假设我们有这个OperationContract

ObjectToBeReturned test();

ObjectToBeReturned 是:

[DataContract]
class ObjectToBeReturned {
    [DataMember]
    public string A {get; set;}
    [DataMember]
    public string B {get; set;}
}

现在,我们希望通过TCP 直接交换ObjectToBeReturned 对象,但通过WebHttp 具有以下格式作为响应:

{
    "status": "success",
    "data": {
        "A": "atest",
        "B": "btest"
    }
}

可能性1

我们考虑了两种可能性。第一个是有一个名为 Response 的对象,它将是我们所有 OperationContract 的返回对象,它将包含以下内容:

[DataContract]
class Response<T> {
    [DataMember]
    public string Status {get; set;}
    [DataMember]
    public T Data {get; set;}
}

问题是我们还需要通过 TCP 协议交换这个对象,但这不是我们的理想方案。

可能性2

我们已尝试添加自定义 EndpointBehavior 和自定义 IDispatchMessageFormatter,该自定义 IDispatchMessageFormatter 仅适用于 WebHttp 端点。

在这个类中,我们实现了以下方法:

 public Message SerializeReply(
            MessageVersion messageVersion,
            object[] parameters,
            object result)
        {

            var clientAcceptType = WebOperationContext.Current.IncomingRequest.Accept;

            Type type = result.GetType();

            var genericResponseType = typeof(Response<>);
            var specificResponseType = genericResponseType.MakeGenericType(result.GetType());
            var response = Activator.CreateInstance(specificResponseType, result);

            Message message;
            WebBodyFormatMessageProperty webBodyFormatMessageProperty;


            if (clientAcceptType == "application/json")
            {
                message = Message.CreateMessage(messageVersion, "", response, new DataContractJsonSerializer(specificResponseType));
                webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);

            }
            else
            {
                message = Message.CreateMessage(messageVersion, "", response, new DataContractSerializer(specificResponseType));
                webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Xml);

            }

            var responseMessageProperty = new HttpResponseMessageProperty
            {
                StatusCode = System.Net.HttpStatusCode.OK
            };

            message.Properties.Add(HttpResponseMessageProperty.Name, responseMessageProperty);

            message.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty); 
            return message;
        }

这似乎很有希望。该方法的问题在于,当使用DataContractSerializer 进行序列化时,我们会收到以下错误:

如果您正在使用,请考虑使用 DataContractResolver DataContractSerializer 或添加任何静态未知的类型到 已知类型的列表 - 例如,通过使用 KnownTypeAttribute 属性或通过将它们添加到传递给 序列化器。

我们真的不想在 Response 类之上列出所有已知类型,因为数量太多而且维护将是一场噩梦(当我们列出已知类型时,我们能够获取数据)。请注意,传递给响应的所有对象都将使用DataContract 属性进行修饰。

我必须提到,我们不在乎更改消息格式是否会导致在另一个 C# 项目中无法通过 ServiceReference 访问 WebHttp 端点,他们应该为此使用 TCP

问题

基本上,我们只想自定义WebHttp的返回格式,所以问题是:

  • 有没有比我们现在做的更简单的方法来实现这一点?
  • 有没有办法通过SerializeReply 方法中的result 参数类型告诉序列化程序 knowntype?
  • 我们是否应该实现一个自定义的Serializer,它将在MessageDispatcherFormatter 中调用,从而调整格式以适合我们的格式?

我们觉得我们走在正确的道路上,但缺少一些部分。

【问题讨论】:

标签: c# wcf datacontractserializer


【解决方案1】:

您几乎走在正确的轨道上 - 拥有仅适用于 JSON 端点的端点行为肯定是要走的路。但是,您可以使用消息检查器,它比格式化程序更简单一些。在检查器上,您可以获取现有响应(如果它是 JSON 响应),并使用您的包装对象包装内容。

请注意,WCF 内部都是基于 XML 的,因此您需要使用 Mapping Between JSON and XML,但这并不太复杂。

下面的代码显示了这个场景的实现。

public class StackOverflow_36918281
{
    [DataContract] public class ObjectToBeReturned
    {
        [DataMember]
        public string A { get; set; }
        [DataMember]
        public string B { get; set; }
    }
    [ServiceContract]
    public interface ITest
    {
        [OperationContract, WebGet(ResponseFormat = WebMessageFormat.Json)]
        ObjectToBeReturned Test();
    }
    public class Service : ITest
    {
        public ObjectToBeReturned Test()
        {
            return new ObjectToBeReturned { A = "atest", B = "btest" };
        }
    }
    public class MyJsonWrapperInspector : IEndpointBehavior, IDispatchMessageInspector
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            return null;
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            object propValue;
            if (reply.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out propValue) &&
                ((WebBodyFormatMessageProperty)propValue).Format == WebContentFormat.Json)
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(reply.GetReaderAtBodyContents());
                var newRoot = doc.CreateElement("root");
                SetTypeAttribute(doc, newRoot, "object");

                var status = doc.CreateElement("status");
                SetTypeAttribute(doc, status, "string");
                status.AppendChild(doc.CreateTextNode("success"));
                newRoot.AppendChild(status);

                var newData = doc.CreateElement("data");
                SetTypeAttribute(doc, newData, "object");
                newRoot.AppendChild(newData);

                var data = doc.DocumentElement;
                var toCopy = new List<XmlNode>();
                foreach (XmlNode child in data.ChildNodes)
                {
                    toCopy.Add(child);
                }

                foreach (var child in toCopy)
                {
                    newData.AppendChild(child);
                }

                Console.WriteLine(newRoot.OuterXml);

                var newReply = Message.CreateMessage(reply.Version, reply.Headers.Action, new XmlNodeReader(newRoot));
                foreach (var propName in reply.Properties.Keys)
                {
                    newReply.Properties.Add(propName, reply.Properties[propName]);
                }

                reply = newReply;
            }
        }

        private void SetTypeAttribute(XmlDocument doc, XmlElement element, string value)
        {
            var attr = element.Attributes["type"];
            if (attr == null)
            {
                attr = doc.CreateAttribute("type");
                attr.Value = value;
                element.Attributes.Append(attr);
            }
            else
            {
                attr.Value = value;
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        string baseAddressTcp = "net.tcp://" + Environment.MachineName + ":8888/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress), new Uri(baseAddressTcp));
        var ep1 = host.AddServiceEndpoint(typeof(ITest), new NetTcpBinding(), "");
        var ep2 = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
        ep2.EndpointBehaviors.Add(new WebHttpBehavior());
        ep2.EndpointBehaviors.Add(new MyJsonWrapperInspector());
        host.Open();
        Console.WriteLine("Host opened");

        Console.WriteLine("TCP:");
        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new NetTcpBinding(), new EndpointAddress(baseAddressTcp));
        ITest proxy = factory.CreateChannel();
        Console.WriteLine(proxy.Test());
        ((IClientChannel)proxy).Close();
        factory.Close();


        Console.WriteLine();
        Console.WriteLine("Web:");
        WebClient c = new WebClient();
        Console.WriteLine(c.DownloadString(baseAddress + "/Test"));

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

【讨论】:

    【解决方案2】:

    这就是我能够完成我想要的事情的方法。可能不是完美的解决方案,但在我的情况下效果很好。

    可能性 #2 是要走的路。但我不得不把它改成这样:

     public Message SerializeReply(
                MessageVersion messageVersion,
                object[] parameters,
                object result)
            {
                // In this sample we defined our operations as OneWay, therefore, this method
                // will not get invoked.
    
    
    
                var clientAcceptType = WebOperationContext.Current.IncomingRequest.Accept;
    
                Type type = result.GetType();
    
                var genericResponseType = typeof(Response<>);
                var specificResponseType = genericResponseType.MakeGenericType(result.GetType());
                var response = Activator.CreateInstance(specificResponseType, result);
    
                Message message;
                WebBodyFormatMessageProperty webBodyFormatMessageProperty;
    
    
                if (clientAcceptType == "application/json")
                {
                    message = Message.CreateMessage(messageVersion, "", response, new DataContractJsonSerializer(specificResponseType));
                    webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
    
                }
                else
                {
                    message = Message.CreateMessage(messageVersion, "", response, new DataContractSerializer(specificResponseType));
                    webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Xml);
    
                }
    
                var responseMessageProperty = new HttpResponseMessageProperty
                {
                    StatusCode = System.Net.HttpStatusCode.OK
                };
    
                message.Properties.Add(HttpResponseMessageProperty.Name, responseMessageProperty);
    
                message.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty); 
                return message;
            }
    

    这里的关键是,由于 Response 是一个泛型类型,WCF 需要知道所有已知类型,并且手动列出它们是不可能的。我决定我的所有返回类型都将实现一个自定义 IDataContract 类(是的,为空):

    public interface IDataContract
    {
    
    }
    

    然后,我在 Response 中所做的就是实现一个GetKnownTypes 方法,并在其中循环访问所有在 Assembly 中实现 IDataContract 的类,并将它们返回到一个数组中。这是我的响应对象:

    [DataContract(Name = "ResponseOf{0}")]
        [KnownType("GetKnownTypes")]
        public class Response<T>
            where T : class
        {
    
            public static Type[] GetKnownTypes()
            {
                var type = typeof(IDataContract);
                var types = AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(s => s.GetTypes())
                    .Where(p => type.IsAssignableFrom(p));
    
                return types.ToArray();
            }
    
            [DataMember(Name = "status")]
            public ResponseStatus ResponseStatus { get; set; }
    
            [DataMember(Name = "data")]
            public object Data { get; set; }
    
            public Response()
            {
                ResponseStatus = ResponseStatus.Success;
            }
    
            public Response(T data) : base()
            {
                Data = data;            
            }
        }
    

    这使我可以通过 TCP 连接并直接交换对象,并通过 JSON 或 XML 中的 WebHTTP 进行出色的序列化。

    【讨论】:

      猜你喜欢
      • 2016-07-04
      • 2016-10-05
      • 1970-01-01
      • 2017-04-23
      • 2019-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多