Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 将应用程序/x-www-form-urlencoded发布到SOAP/JSON WCF服务_C#_Json_Web Services_Wcf_Iclientmessageinspector - Fatal编程技术网

C# 将应用程序/x-www-form-urlencoded发布到SOAP/JSON WCF服务

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内容类型发布到此服务。通

背景

我不久前编写了一个WCF服务,它大量使用自定义操作调用程序、错误处理程序和行为,其中许多都严重依赖于特定类型的输入消息或消息的基本消息类型(每个DataContract继承自一个基类和多个接口)。还为涉及的各种接口和类设置了许多单元测试和集成测试。此外,软件每次修改都必须经过严格的签核过程,重写服务层并不是我的乐趣所在

它当前配置为允许JSON和SOAP请求进入

问题

由于遗留软件的限制,客户希望使用application/x-www-form-urlencoded内容类型发布到此服务。通常情况下,服务将接受如下所示的JSON请求:

{
"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小时限制:)它对您有何作用?我在我的服务中尝试了你的代码,它在
请求时抛出异常