我得到了这个工作。由于这是一个(希望是)临时解决方案,我的目标是为 Proxy 生成的类创建替代品,我已经非常接近了。关键是要弄清楚如何使用 DataContractSerializer 创建一个 SOAP 信封来发送和反序列化结果。
除了发送到 Web 服务的 SOAP 信封的序列化之外,我在所有方面都取得了成功。我最终手动将 XML 包装在 <envelope> 和 <body> 标记中。尽管正文内容没问题,但我所做的一切都无法让 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 服务函数编写包装器,并且到目前为止运行良好。