C# 将应用程序/x-www-form-urlencoded发布到SOAP/JSON WCF服务
背景 我不久前编写了一个WCF服务,它大量使用自定义操作调用程序、错误处理程序和行为,其中许多都严重依赖于特定类型的输入消息或消息的基本消息类型(每个DataContract继承自一个基类和多个接口)。还为涉及的各种接口和类设置了许多单元测试和集成测试。此外,软件每次修改都必须经过严格的签核过程,重写服务层并不是我的乐趣所在 它当前配置为允许JSON和SOAP请求进入 问题 由于遗留软件的限制,客户希望使用application/x-www-form-urlencoded内容类型发布到此服务。通常情况下,服务将接受如下所示的JSON请求:C# 将应用程序/x-www-form-urlencoded发布到SOAP/JSON WCF服务,c#,json,web-services,wcf,iclientmessageinspector,C#,Json,Web Services,Wcf,Iclientmessageinspector,背景 我不久前编写了一个WCF服务,它大量使用自定义操作调用程序、错误处理程序和行为,其中许多都严重依赖于特定类型的输入消息或消息的基本消息类型(每个DataContract继承自一个基类和多个接口)。还为涉及的各种接口和类设置了许多单元测试和集成测试。此外,软件每次修改都必须经过严格的签核过程,重写服务层并不是我的乐趣所在 它当前配置为允许JSON和SOAP请求进入 问题 由于遗留软件的限制,客户希望使用application/x-www-form-urlencoded内容类型发布到此服务。通
{
"username":"jeff",
"password":"mypassword",
"myvalue":12345
}
username=jeff&password=mypassword&myvalue=12345
[ServiceContract(Namespace = "https://my.custom.domain.com/")]
public interface IMyContract {
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "process")]
MyCustomResponse Process(MyCustomRequest req);
}
客户端可以发送的application/x-www-form-urlencoded消息体看起来有点像这样:
{
"username":"jeff",
"password":"mypassword",
"myvalue":12345
}
username=jeff&password=mypassword&myvalue=12345
[ServiceContract(Namespace = "https://my.custom.domain.com/")]
public interface IMyContract {
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "process")]
MyCustomResponse Process(MyCustomRequest req);
}
或者,客户告知我,他们可以将消息格式如下(如果有用):
也认为服务契约看起来是这样的:
{
"username":"jeff",
"password":"mypassword",
"myvalue":12345
}
username=jeff&password=mypassword&myvalue=12345
[ServiceContract(Namespace = "https://my.custom.domain.com/")]
public interface IMyContract {
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "process")]
MyCustomResponse Process(MyCustomRequest req);
}
我希望保留MyCustomRequest,并避免用流替换它(根据以下链接)
我已经找到了一些帖子,建议如何使用Stream OperationContract参数来实现这一点,但是在我的特定实例中,更改OperationContract参数的类型需要做大量的工作。下面的帖子将详细介绍:
虽然我还没有发现任何特别有用的东西
问题
是否有任何方法可以在消息到达操作契约之前拦截消息,并将其从客户端的输入转换为自定义类,然后让应用程序的其余部分按正常方式处理
自定义消息检查器?操作选择器?我已经有一段时间没有接触WCF了,所以我现在有点生疏了。我花了一段时间寻找下图,因为我记得用它来提醒我调用堆栈——如果它仍然相关的话
不确定您更新
服务合同的自由度,但我会尝试将其扩展如下:
[ServiceContract(Namespace = "https://my.custom.domain.com/")]
public interface IMyContract {
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "process")]
MyCustomResponse Process(MyCustomRequest req);
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "processForm")]
MyCustomResponse ProcessForm(MyCustomRequest req);
}
然后会给这个客户端一个新的URL来发布。所以,我用一个消息检查器解决了这个问题。这不漂亮,但对我的案子有效
using System;
public class StreamMessageInspector : IDispatchMessageInspector {
#region Implementation of IDispatchMessageInspector
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) {
if (request.IsEmpty) {
return null;
}
const string action = "<FullNameOfOperation>";
// Only process action requests for now
var operationName = request.Properties["HttpOperationName"] as string;
if (operationName != action) {
return null;
}
// Check that the content type of the request is set to a form post, otherwise do no more processing
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
var contentType = prop.Headers["Content-Type"];
if (contentType != "application/x-www-form-urlencoded") {
return null;
}
///////////////////////////////////////
// Build the body from the form values
string body;
// Retrieve the base64 encrypted message body
using (var ms = new MemoryStream()) {
using (var xw = XmlWriter.Create(ms)) {
request.WriteBody(xw);
xw.Flush();
body = Encoding.UTF8.GetString(ms.ToArray());
}
}
// Trim any characters at the beginning of the string, if they're not a <
body = TrimExtended(body);
// Grab base64 binary data from <Binary> XML node
var doc = XDocument.Parse(body);
if (doc.Root == null) {
// Unable to parse body
return null;
}
var node = doc.Root.Elements("Binary").FirstOrDefault();
if (node == null) {
// No "Binary" element
return null;
}
// Decrypt the XML element value into a string
var bodyBytes = Convert.FromBase64String(node.Value);
var bodyDecoded = Encoding.UTF8.GetString(bodyBytes);
// Deserialize the form request into the correct data contract
var qss = new QueryStringSerializer();
var newContract = qss.Deserialize<MyServiceContract>(bodyDecoded);
// Form the new message and set it
var newMessage = Message.CreateMessage(OperationContext.Current.IncomingMessageVersion, action, newContract);
request = newMessage;
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState) {
}
#endregion
/// <summary>
/// Trims any random characters from the start of the string. I would say this is a BOM, but it doesn't seem to be.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private string TrimExtended(string s) {
while (true) {
if (s.StartsWith("<")) {
// Nothing to do, return the string
return s;
}
// Replace the first character of the string
s = s.Substring(1);
if (!s.StartsWith("<")) {
continue;
}
return s;
}
}
}
以下是配置更改的摘录:
<extensions>
<behaviorExtensions>
<add name="streamInspector" type="My.Namespace.WCF.Extensions.Behaviors.StreamMessageInspectorEndpointBehavior, My.Namespace.WCF, Version=1.0.0.0, Culture=neutral" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="MyEndpointBehavior">
<streamInspector/>
</behavior>
</endpointBehaviors>
QueryStringSerializer.Deserialize()将querystring反序列化为DataContract(基于DataMember.Name属性,或者如果DataMember属性不存在,则基于属性名)。是的,我确实想到了这一点(仅接受第二个属性上的流),但在我的情况下,它不是很有用。我已经找到了一个使用消息检查器的解决方案。将您自己的答案标记为已接受,这样问题就不会一直没有答案。我知道-我正在等待取消24小时限制:)它对您有何作用?我在我的服务中尝试了你的代码,它在请求时抛出异常因为它将消息视为原始文本,而不是XML,并且无法理解它。在所有的例子中都有XML。是什么让我的服务与众不同?我假设您发送了正常的POST请求,没有XML。然后您的服务以某种方式将其视为XML,并可以提取主体。我得到唯一的异常,因为第一个处理的字符不是“