visual studio partir example crear consume c# web-services visual-studio-2012 wsdl workflow-foundation-4

c# - studio - Cómo obtener una referencia de servicio para generar correctamente con contratos de mensajes basados en WSDL de terceros, o no forzar contratos de mensajes en el proyecto de Servicio WF



wsdl c# (2)

Tengo un problema que, dado el WSDL de terceros, puedo desde una aplicación de consola crear fácilmente un proxy de servicio que funcione, pero desde un servicio WF4 WF no lo estoy. El proxy generado en este último caso es claramente defectuoso, involucrando específicamente 2 problemas: a) Contratos de mensajes siempre generados cuando no se solicitan o necesitan b) Mensajes de respuesta incorrectos y nombres de envoltura xml utilizados, lo que resulta en objetos de respuesta nula y deserialización fallida

El problema al que me enfrento es en la generación real de la clase Reference.cs sobre la base de WSDL de terceros. En el WSDL hay muchas operaciones, y en orden de aparición 2 de ellas son:

<operation name="pu013"> <documentation> <description>Check-response service</description> <help>The service handles (cut out)</help> </documentation> <input message="tns:pu013Request" /> <output message="tns:SimpleResponse" /> </operation> ... <operation name="mi102"> <documentation> <description>Instruction insert to Matching System</description> <help>This service (cut out)</help> </documentation> <input message="tns:mi102Request" /> <output message="tns:SimpleResponse" /> </operation>

Lo que esto resulta en el Reference.cs es el siguiente C #:

WorkflowService1.PSE.pu013Response pu013(WorkflowService1.PSE.pu013Request request); ... WorkflowService1.PSE.pu013Response mi102(WorkflowService1.PSE.mi102Request request);

Tenga en cuenta que, por alguna razón, la operación mi102 se genera con el mensaje de respuesta INCORRECTO de pu013Response, que se declara así:

[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] [System.ServiceModel.MessageContractAttribute(WrapperName="pu013Response", WrapperNamespace="http://pse/", IsWrapped=true)] public partial class pu013Response {

Tenga en cuenta que WrapperName impide que el serializador XML reconozca la respuesta, que es mi102Response, por lo que para todas las operaciones que no son pu013 siempre obtengo una respuesta NULL.

Además, esto NO ocurre si agrego una referencia desde una aplicación de consola. Esto no genera contratos de mensajes, y en este caso, trabajo de invocación y respuesta.

¿Que es diferente? ¿Sirve svcutil detrás de escena? Si es así, ¿qué hay de diferente sobre los parámetros utilizados? ¿Se puede usar svcutil para generar las actividades de xamlx también, para que pueda encontrar una solución de línea de comandos?

Esto se ve como un error VS / Add Service Reference. La alternativa es corregir manualmente muchas operaciones en Reference.cs.

Idealmente, estoy buscando una forma de ejecutar fácilmente, automáticamente, svcutil o agregar referencia de servicio para que la clase de referencia sea correcta y se generen las actividades de xamlx. Un placer tener es una explicación de por qué hay una diferencia, y detrás de escena lo que está sucediendo.

ACTUALIZACIÓN : los contratos de mensajes generados en la aplicación de la consola producen el mismo problema: declaraciones de respuesta incorrectas. El problema desaparece si se usan parámetros en lugar de mensajes, que no están disponibles desde una aplicación de servicio de WF.


Estoy lejos de ser una autoridad en estos temas, y aunque esta respuesta a continuación podría no corresponderse exactamente con su problema, mi experiencia reciente de hacer una conexión sin proxy a un servicio puede ofrecer una idea para usted o la próxima persona con un problema similar .

Comenzaría por ver si puede pasar manualmente la solicitud SOAP utilizando el violín y ver si puede crear el mensaje correcto y enviarlo. Ya que describe las herramientas de automatización como defectuosas (o tal vez haya un problema de configuración, no lo está haciendo). De cualquier manera, tener una comprensión clara de la forma del contrato y poder realizar una prueba confiable en el violín puede ofrecer claridad.

No necesariamente necesita confiar en el uso de un proxy. Puede crear su propio mensaje y enviarlo a través de una de dos maneras. El primero es usar ChannelFactory.

  1. Crea el cuerpo de tu mensaje (si es necesario, la clase de mensaje puede funcionar sin una)
  2. Crea tu mensaje
  3. Envíe su mensaje a través de ChannelFactory

Para el paso 1, puede construir su mensaje haciendo un simple POCO para reflejar lo que se espera en su contrato. Debería poder derivar esa clase a través del WSDL.

Digamos que el servicio es algo como esto:

[ServiceContract(Namespace = "http://Foo.bar.car")] public interface IPolicyService { [OperationContract] PolicyResponse GetPolicyData(PolicyRequest request); } public class PolicyData : IPolicyService { public PolicyResponse GetPolicyData(PolicyRequest request) { var polNbr = request.REQ_POL_NBR; return GetMyData(polNbr); } }

Necesitarías una clase como esta:

[DataContract(Namespace = "http://Foo.bar.car")] public class GetPolicyData { [DataMember] public request request { get; set; } } [DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy")] public class request { ///<summary> /// Define request parameter for SOAP API to retrieve selective Policy level data /// </summary> [DataMember] public string REQ_POL_NBR { get; set; } }

y luego lo llamarías así:

private static Message SendMessage(string id) { var body = new GetPolicyData {request = new request{ REQ_POL_NBR = id }}; var message = Message.CreateMessage(MessageVersion.Soap11, "http://Foo.bar.car/IPolicyService/GetPolicyData", body); // these headers would probably not be required, but added for completeness message.Headers.Add(MessageHeader.CreateHeader("Accept-Header", string.Empty, "application/xml+")); message.Headers.Add(MessageHeader.CreateHeader("Content-Type", string.Empty, "text/xml")); message.Headers.Add(MessageHeader.CreateHeader("FromSender", string.Empty, "DispatchMessage")); message.Headers.To = new System.Uri(@"http://localhost:5050/PolicyService.svc"); var binding = new BasicHttpBinding(BasicHttpSecurityMode.None) { MessageEncoding = WSMessageEncoding.Text, MaxReceivedMessageSize = int.MaxValue, SendTimeout = new TimeSpan(1, 0, 0), ReaderQuotas = { MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue, MaxDepth = int.MaxValue } }; message.Properties.Add("Content-Type", "text/xml; charset=utf-8"); message.Properties.Remove("Accept-Encoding"); message.Properties.Add("Accept-Header", "application/xml+"); var cf = new ChannelFactory<IRequestChannel>(binding, new EndpointAddress(new Uri("http://localhost:5050/PolicyService.svc"))); cf.Open(); var channel = cf.CreateChannel(); channel.Open(); var result = channel.Request(message); channel.Close(); cf.Close(); return result; }

Lo que recibirá de vuelta será un mensaje, que deberá deserializar, y hay algunas maneras OOTB de hacerlo, (Message.GetReaderAtBodyContents, Message.GetBody) para mantener el tema rodado a mano:

/// <summary> /// Class MessageTransform. /// </summary> public static class MessageTransform { /// <summary> /// Gets the envelope. /// </summary> /// <param name="message">The message.</param> /// <returns>XDocument.</returns> public static XDocument GetEnvelope(Message message) { using (var memoryStream = new MemoryStream()) { var messageBuffer = message.CreateBufferedCopy(int.MaxValue); var xPathNavigator = messageBuffer.CreateNavigator(); var xmlWriter = XmlWriter.Create(memoryStream); xPathNavigator.WriteSubtree(xmlWriter); xmlWriter.Flush(); xmlWriter.Close(); memoryStream.Position = 0; var xdoc = XDocument.Load(XmlReader.Create(memoryStream)); return xdoc; } } /// <summary> /// Gets the header. /// </summary> /// <param name="message">The message.</param> /// <returns>XNode.</returns> public static XNode GetHeader(Message message) { var xdoc = GetEnvelope(message); var strElms = xdoc.DescendantNodes(); var header = strElms.ElementAt(1); return header; } /// <summary> /// Gets the body. /// </summary> /// <param name="message">The message.</param> /// <param name="localName">Name of the local.</param> /// <param name="namespaceName">Name of the namespace.</param> /// <returns>IEnumerable&lt;XElement&gt;.</returns> public static IEnumerable<XElement> GetBody(Message message, string localName, string namespaceName) { var xdoc = GetEnvelope(message); var elements = xdoc.Descendants(XName.Get(localName, namespaceName)); return elements; } }

O podrías construir tu sobre de jabón a mano y usar WebClient:

using System.Net; using System.Xml.Linq; public static class ClientHelper { public static string Post(string targetUrl, string action, string method, string key, string value) { var request = BuildEnvelope(method, key, value); using (var webClient = new WebClient()) { webClient.Headers.Add("Accept-Header", "application/xml+"); webClient.Headers.Add("Content-Type", "text/xml; charset=utf-8"); webClient.Headers.Add("SOAPAction", action); var result = webClient.UploadString(targetUrl, "POST", request); return result; } } public static string BuildEnvelope(string method, string key, string value) { XNamespace s = "http://schemas.xmlsoap.org/soap/envelope/"; XNamespace d = "d4p1"; XNamespace tempUri = "http://tempuri.org/"; XNamespace ns = "http://Foo.bar.car"; XNamespace requestUri = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy"; var xDoc = new XDocument( new XElement( s + "Envelope", new XAttribute(XNamespace.Xmlns + "s", s), new XElement( s + "Body", new XElement( ns + method, new XElement(requestUri + "request", new XElement(tempUri + key, value)) ) ) ) ); // hack - finish XDoc construction later return xDoc.ToString().Replace("request xmlns=", "request xmlns:d4p1=").Replace(key, "d4p1:" + key); }

que se llama con:

return ClientHelper.Post("http://localhost:5050/PolicyService.svc", "http://Foo.bar.car/IPolicyService/GetPolicyData", "GetPolicyData", "REQ_POL_NBR", id);

Probarlo en Fiddler se vería así:

Post action: http://localhost:5050/PolicyService.svc Header: User-Agent: Fiddler SOAPAction: http://Foo.bar.car/IPolicyService/GetPolicyData Content-type: text/xml Host: localhost:5050 Content-Length: 381

Cuerpo:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <GetPolicyData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://Foo.bar.car"> <request xmlns:d4p1="http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy"> <d4p1:REQ_POL_NBR>1</d4p1:REQ_POL_NBR> </request> </GetPolicyData> </s:Body> </s:Envelope>

Una vez más, esta respuesta no intenta resolver cómo invocar svcUtil de manera diferente, sino para evitar llamarlo por completo, así que espero que los dioses de la edición no me critiquen por eso ;-)

Mi código anterior ha sido inspirado por mejores desarrolladores que yo, pero espero que ayude.

http://blogs.msdn.com/b/stcheng/archive/2009/02/21/wcf-how-to-inspect-and-modify-wcf-message-via-custom-messageinspector.aspx


Sugiero que genere el proxy wsdl mediante la utilidad de línea de comandos y agregue el archivo proxy generado en su proyecto. Funcionará desde cualquier proyecto y puede encontrar las configuraciones necesarias de output.config que generará desde la utilidad de línea de comandos.

Si necesita el comando wsdl y las opciones, entonces puedo proporcionarle.