【问题标题】:Xamarin Android - Can a WCF SOAP webservice use HttpClient for TLS 1.2?Xamarin Android - WCF SOAP Web 服务可以将 HttpClient 用于 TLS 1.2 吗?
【发布时间】:2016-06-14 18:27:21
【问题描述】:

我有一位客户要求使用 TLS 1.2 来满足 PCI 合规性。 Xamarin Android 不能很好地支持 TLS 1.2。根据这个 Native HttpClientHandlerTransport Layer Security,您可以使用 HttpClient 及其特殊机制来访问 Android 5 及更高版本上的本机 Java 支持,也可以使用 ModernHttpClient。

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

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

【问题讨论】:

    标签: wcf soap xamarin.android dotnet-httpclient


    【解决方案1】:

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

     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;
                }
    

    【讨论】:

      【解决方案2】:

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

      除了发送到 Web 服务的 SOAP 信封的序列化之外,我在所有方面都取得了成功。我最终手动将 XML 包装在 &lt;envelope&gt;&lt;body&gt; 标记中。尽管正文内容没问题,但我所做的一切都无法让 DataContractSerializer 正确创建这些内容。反序列化器能够毫无问题地处理来自 Web 服务的响应。 WCF 服务对 SOAP 信封的格式非常挑剔,正确地注释类是一项挑战。

      对于每个函数调用,我必须创建一个包装发送到 Web 服务的参数的 Request 对象,以及一个包装输出参数和返回代码的 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 服务函数编写包装器,并且到目前为止运行良好。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-05
        • 1970-01-01
        • 1970-01-01
        • 2016-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多