同时具有 "application/xml" 和 "text/xml" ContentTypes 的 WCF 绑定 SOAP 1.1 请求

WCF Binding SOAP 1.1 requests with both "application/xml" and "text/xml" ContentTypes

我正在尝试用新的 WCF 服务替换遗留的 SOAP 1.1 Web 服务。

现有客户端较多,更改不可行。

我已经成功创建了一项服务,它适用于大多数客户端,但有一个令人烦恼的异常。

由于旧服务是 SOAP 1.1,我尝试使用 basicHttpBinding,例如:

<bindings>
    <basicHttpBinding>
        <binding name="Whatever" />
    </basicHttpBinding>
</bindings> 

我的大部分入站消息都像下面的例子,一切正常:

POST http://MySoapWebServiceUrl/Service.svc HTTP/1.1
SOAPAction: DoSomething
Content-Type: text/xml;charset=UTF-8
Content-Length: 1234
Host: MySoapWebServiceUrl

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
    <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <xml:InputStuffHere />
    </soap:Body>
</soap:Envelope>

我的问题是几个呼叫者发送完全相同的消息,除了 header 中 'application/xml' 的 Content-Type,并收到此错误:

Error: 415 Cannot process the message because the content type 'application/xml' was not the expected type 'text/xml'.

我试过使用 wsHttpBindingwebHttpBinding 进行绑定。

但是,我无法在这些绑定中找到允许 'application/xml' 和 'text/xml' 内容类型以及 SOAP 1.1 样式 "SOAPAction" 寻址的设置组合header.

我还尝试实现自定义文本消息编码器,从 Microsoft 的 WCF 示例开始 CustomTextMessageEncodingElement

但是,使用自定义文本消息编码器,我可以将 MediaType 设置为 'application/xml' 或 'text/xml'。但是,毫不奇怪,发送指定 Content-Type 的客户端成功,但使用其他 Content-Type 的客户端失败。

我还尝试设置 MediaType,包括像 '*/xml' 这样的通配符,但这对所有调用者来说都失败了。

是否可以创建 WCF 绑定以便服务接受 'application/xml' 或 'text/xml' 的内容类型?

我相信我试图做的事情是不可能的。

我得出的结论是,WCF 是一种不适合实施 backwards-compatible 替代遗留 SOAP Web 服务的技术,它允许 headers 中的各种 Content-Type 值。

相反,我使用 System.Web.IHttpModule 实现了自定义 HttpModule。

下面是基本的实施细节,适用于发现自己陷入困境并需要出路的任何其他人。

代码:

public class MyCustomHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += OnBegin;
    }

    private void OnBegin(object sender, EventArgs e)
    {
        var app = (HttpApplication) sender;
        var context = app.Context;

        if (context.Request.HttpMethod == "POST")
        {
            string soapActionHeader = context.Request.Headers["SOAPAction"];
            byte[] buffer = new byte[context.Request.InputStream.Length];
            context.Request.InputStream.Read(buffer, 0, buffer.Length);
            context.Request.InputStream.Position = 0;

            string rawRequest = Encoding.ASCII.GetString(buffer);

            var soapEnvelope = new XmlDocument();
            soapEnvelope.LoadXml(rawRequest);

            string response = DoSomeMagic(soapActionHeader, soapEnvelope);

            context.Response.ContentType = "text/xml";
            context.Response.ContentEncoding = Encoding.UTF8;
            context.Response.Write(response);
        }
        else
        {
            //do something else
            //returning a WSDL file for an appropriate GET request is nice
        }

        context.Response.Flush();
        context.Response.SuppressContent = true;
        context.ApplicationInstance.CompleteRequest();
    }

    private string DoSomeMagic(string soapActionHeader, XmlDocument soapEnvelope)
    {
        //magic happens here
    }

    public void Dispose()
    {
       //nothing happens here
       //a Dispose() implementation is required by the IHttpModule interface
    }
}

web.config:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
        <add name="MyCustomHttpModule" type="AppropriateNamespace.MyCustomHttpModule"/>
    </modules>
</system.webServer>