Я получил эту работу. Поскольку это временное решение (надеюсь), моя цель состояла в том, чтобы создавать замены для сгенерированных Proxy-классов, и я получил довольно близко. Ключ должен был выяснить, как использовать DataContractSerializer для создания конверта SOAP для отправки и десериализации результатов.
У меня было все, кроме сериализации конверта SOAP, который отправляется на веб-службу. Я закончил вручную обматывание XML в тегах <envelope>
и <body>
. Ничто из того, что я сделал, не может заставить DataContractSerializer создать их правильно, хотя содержимое Body было в порядке. Однако Deserializer смог обработать ответ от веб-службы без каких-либо проблем. Услуги WCF очень придирчивы к формату конверта SOAP, и получение классов, аннотированных только в порядке, было проблемой.
Для каждого вызова функции мне пришлось создать объект Request, который обертывает параметры, отправляемые в веб-службу, и объект Response, который обертывает параметры out и код возврата.
Они выглядят примерно так, где 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, и он работает до сих пор.