Xamarin Android - WCF SOAP Web服务可以将HttpClient用于TLS 1.2吗?

时间:2016-06-14 18:27:21

标签: wcf soap xamarin.android dotnet-httpclient

我的客户需要TLS 1.2才能符合PCI标准。 Xamarin Android不支持TLS 1.2。根据这个 enter image description hereNative HttpClientHandler,您可以使用HttpClient及其特殊机制来访问Android 5及更高版本上的本机Java支持,也可以使用ModernHttpClient。

但是,使用SvcUtil生成的WCF SOAP Webservice代理似乎使用HttpWebRequest,而不是HttpClient。

使用HttpClient(或ModernHttpClient)调用WCF SOAP服务的推荐方法是什么?我是否必须手动编写自己的接口,还是可以使用代理类并自行序列化/反序列化?我不必完全从头开始,特别是因为看起来TLS 1.2目前正在添加到Mono中。

2 个答案:

答案 0 :(得分:0)

我已经使用过这种类型的服务,它正在这里工作,我有共享相关代码,请尝试一下。

 static void TryByWebRequest(string soapMethod)
        {
 XmlDocument soapEnvelopeXml = new XmlDocument();

            soapEnvelopeXml.LoadXml(@"
                        <s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
                            <s:Body>
                                <" + soapMethod + @" 
                                xmlns=""your URI"" 
                                xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
                                    <InputXMLString>
                                        " +
                                    System.Security.SecurityElement.Escape(inputXML)
                                    + @"                
                                    </InputXMLString>                            
                                    <OutputXMLString/>
                                </" + soapMethod + @">
                            </s:Body>
                       </s:Envelope>");


            using (Stream stream = request.GetRequestStream())
            {
                soapEnvelopeXml.Save(stream);
            }

            using (WebResponse response = request.GetResponse())
            {
                using (StreamReader rd = new StreamReader(response.GetResponseStream()))
                {
                    string soapResult = rd.ReadToEnd();
                    Console.WriteLine(soapResult);
                }
            }
}
     static HttpWebRequest CreateWebRequest(string soapMethod)
            {
                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(@"Your .asmx URL ");
                webRequest.Headers.Add(@"SOAPAction", "your URI \" + soapMethod);
                webRequest.ContentType = "text/xml;charset=\"utf-8\"";
                webRequest.Accept = "text/xml";
                webRequest.Method = "POST";

                return webRequest;
            }

答案 1 :(得分:0)

我得到了这个工作。由于这是一个(希望)临时解决方案,我的目标是为代理生成的类创建插件替换,我非常接近。关键是弄清楚如何使用DataContractSerializer创建一个SOAP信封来发送和反序列化结果。

除了发送到Web服务的SOAP信封的序列化之外,我成功了。我最终手动将XML包装在<envelope><body>标记中。我做的任何事情都不能让DataContractSerializer正确地创建它们,尽管Body内容没问题。 Deserializer能够毫无问题地处理来自Web服务的响应。 WCF服务对于SOAP信封的格式非常挑剔,并且正确地注释类是一个挑战。

对于每个函数调用,我必须创建一个Request对象,它包装发送给Web服务的参数,以及一个包装out参数和返回代码的Response对象。

这些看起来像这样,其中FunctionName是代理生成的WCF函数调用的名称。

    // request envelope
    [System.Runtime.Serialization.DataContractAttribute(Name = "FunctionName", Namespace = "http://tempuri.org/")]
    public class FunctionName_Request
    {
        [System.Runtime.Serialization.DataMemberAttribute()]
        public NameSpaceFunctionNameObject1 CallingObject1;

        [System.Runtime.Serialization.DataMemberAttribute()]
        public NameSpaceFunctionNameObject2 CallingObject2;
    }

    // response envelope
    [System.Runtime.Serialization.DataContractAttribute(Name = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
    public class FunctionName_ResponseEnvelope
    {
        [System.Runtime.Serialization.DataContractAttribute(Name = "Body", Namespace = "http://tempuri.org/")]
        public class FunctionName_ResponseBody
        {
            [System.Runtime.Serialization.DataContractAttribute(Name = "FunctionNameResponse", Namespace = "http://tempuri.org/")]
            public class FunctionName_Response
            {
                [System.Runtime.Serialization.DataMemberAttribute()]
                public FunctionNameReturnCodes Result;

                [System.Runtime.Serialization.DataMemberAttribute()]
                public FunctionNameResponseObject Response;
            }

            [System.Runtime.Serialization.DataMemberAttribute()]
            public FunctionName_Response FunctionNameResponse;
        }

        [System.Runtime.Serialization.DataMemberAttribute()]
        public FunctionName_ResponseBody Body;
    }

然后,我可以编写一个我的客户端代码可以调用的替换函数,它与原始代理生成的函数具有完全相同的签名。

    // FunctionName
    public FunctionNameReturnCodes FunctionName(out FunctionNameResponseObject Response, NameSpaceFunctionNameObject1 CallingObject1, NameSpaceFunctionNameObject2 CallingObject2)
    {
        // create the request envelope
        FunctionName_Request req = new FunctionName_Request();
        req.CallingObject1 = CallingObject1;
        req.CallingObject2 = CallingObject2;

        // make the call
        FunctionName_ResponseEnvelope resp = MakeWCFCall<FunctionName_ResponseEnvelope>(_EndpointAddress, _ServerName, req);

        // get the response object
        Response = resp.Body.FunctionName_Response.Response;

        // result
        return resp.Body.FunctionName_Response.Result;
    }

最后,这是将对象实际序列化和反序列化为HttpClient的函数。在我的例子中,这些是同步的,但您可以轻松地将其调整为在标准异步情况下工作。它的模板,因此它适用于任何代理生成的类型。

    /////////////////////////////////////////////////////////////////////
    // make a WCF call using an HttpClient object
    // uses the DataContractSerializer to serialze/deserialze the messages from the objects
    //
    // We manually add the <s:Envelope> and <s:Body> tags.  There should be a way to get
    // the DataContractSerializer to write these too, but everything I tried gave a message
    // that was not able to be procesed by the service.  This includes the Message object.
    // Deserializing works fine, but serializing did not.
    private T MakeWCFCall<T>(string strEndpoint, string strServerName, object SourceObject)
    {
        T Response = default(T);
        string strSoapMessage = "";
        string strSoapAction = "";

        // get the Soap Action by using the DataContractAttribute's name
        // start by getting the list of custom attributes.
        // there should only be the one
        object[] oaAttr = SourceObject.GetType().GetCustomAttributes(false);
        if (oaAttr.Length > 0)
        {
            // iterate just in case there are more
            foreach (DataContractAttribute oAttr in oaAttr)
            {
                // make sure we got one
                if (oAttr != null)
                {
                    // this is the action!
                    strSoapAction = oAttr.Name;
                    break;
                }
            }
        }

        // serialize the request into a string
        // use a memory stream as the source
        using (MemoryStream ms = new MemoryStream())
        {
            // create the DataContractSerializer
            DataContractSerializer ser = new DataContractSerializer(SourceObject.GetType());

            // serialize the object into the memory stream
            ser.WriteObject(ms, SourceObject);

            // seek to the beginning so we can read back out of the stream
            ms.Seek(0, SeekOrigin.Begin);

            // create the stream reader
            using (var streamReader = new StreamReader(ms))
            {
                // read the message back out, adding the Envelope and Body wrapper
                strSoapMessage = @"<s:Envelope xmlns:s = ""http://schemas.xmlsoap.org/soap/envelope/""><s:Body>" + streamReader.ReadToEnd() + @"</s:Body></s:Envelope>";
            }
        }

        // now create the HttpClient connection
        using (var client = new HttpClient(new NativeMessageHandler()))
        {
            //specify to use TLS 1.2 as default connection
            System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

            // add the Soap Action header
            client.DefaultRequestHeaders.Add("SOAPAction", "http://tempuri.org/" + strServerName + "/" + strSoapAction);

            // encode the saop message
            var content = new StringContent(strSoapMessage, Encoding.UTF8, "text/xml");

            // post to the server
            using (var response = client.PostAsync(new Uri(strEndpoint), content).Result)
            {
                // get the response back
                var soapResponse = response.Content.ReadAsStringAsync().Result;

                // create a MemoryStream to use for serialization
                using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(soapResponse)))
                {
                    // create the reader
                    // set the quotas
                    XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(
                        memoryStream, 
                        Encoding.UTF8, 
                        new XmlDictionaryReaderQuotas() { MaxArrayLength = 5000000, MaxBytesPerRead = 5000000, MaxStringContentLength = 5000000 }, 
                        null);

                    // create the Data Contract Serializer
                    DataContractSerializer serializer = new DataContractSerializer(typeof(T));

                    // deserialize the response
                    Response = (T)serializer.ReadObject(reader);
                }
            }
        }

        // return the response
        return Response;
    }

这种方法允许我快速为我的所有WCF服务功能编写包装器,并且到目前为止它运行良好。