Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get service reference to generate correctly with message contracts based on 3rd party WSDL, or force no message contracts in WF Service project

I have a problem that given 3rd party WSDL I am able from a Console App to easily create a service proxy that works, but from a WF4 WF service I am not. The generated proxy in the latter case is clearly buggy, involving specifically 2 problems: a) Message contracts always generated when not requested or needed b) Incorrect response messages and xml wrapper names used, resulting in null response objects and failed deserialization

The problem I am facing is in the actual generation of the Reference.cs class on the basis of 3rd party WSDL. In the WSDL there are many operations, and in order of appearance 2 of them are as so:

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

What this results in in the Reference.cs is the following C#:

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

Note that for some reason the mi102 operation is generated with the INCORRECT response message of pu013Response, which is declared as this:

 [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 {  

Note the WrapperName prevents the XML serializer from recognising the response, which is mi102Response, so for all operations that are not pu013 I always get a NULL response.

Also, this does NOT occur if I add a reference from a console application. This does not generate Message contracts, and in this case, invocation and response work.

What is different? Is svcutil being invoke behind the scenes? If so, what is different about the parameters used? Can svcutil be used to generate the xamlx activities too, so that I might find a command line workaround?

This looks like a VS / Add Service Reference bug. The alternative is to manually correct many operations in the Reference.cs.

Ideally, I am looking for a way to easily, automatically, run svcutil or Add Service Reference so that the Reference class is correct and the xamlx activities generated. A nice to have is an explanation of why there is a difference, and behind the scenes what is happening.

UPDATE: Message contracts generated in the console app result in the same problem - incorrect Response declarations. The problem goes away if parameters are used instead of messages, which are not available from a WF service app.

like image 999
Sentinel Avatar asked Nov 08 '13 22:11

Sentinel


People also ask

How do I add a service reference in Web API?

In Solution Explorer, right-click the name of the project to which you want to add the service, and then click Add Service Reference. The Add Service Reference dialog box appears. In the Address box, enter the URL for the service, and then click Go to search for the service.

How do you update a service reference?

Go to Your Project > properties > Web > web Servers and select External Host on the Combobox. Set the project URL to the same as your local IIS is running your Application. Try Update your reference again.

How do I add a Web service reference in Visual Studio 2015?

In Solution Explorer, right-click the name of the project to add the Web service to and then click Add Web Reference. The Add Web Reference dialog box is displayed. In the URL box, enter the URL of the Web service to use.


1 Answers

I am far from an authority on these issues, and while this response below might not be an exact fit to your problem, my recent experience of making a proxyless connection to a service might offer some insight to you or the next person with a similar issue.

I would start by seeing if you can hand roll the SOAP request using fiddler, and see if you are able to create the correct message and send that along. Since you describe the automation tools as being buggy (or perhaps there is a config issue you're not getting just so). Either way, having a clear understanding of the shape of the contract, and being able to perform a reliable test in fiddler may offer clarity.

You don't necessarily need to rely on using a proxy. You can create your own soap message and send it along one of two ways. The first is using the ChannelFactory.

  1. Create your message body (if required, message class can work w/out one)
  2. Create your message
  3. Send your message via the ChannelFactory

For step 1 you can construct your message by making a simple POCO to mirror what is expected in your contract. You should be able to derive what that class is via the WSDL.

Let's say the service is something like this:

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

You would need a class something like this:

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

and then you would call it like this:

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

What you receive back will be a Message, which you will need to deserialize, and there are a few OOTB ways of doing this, (Message.GetReaderAtBodyContents, Message.GetBody) in keeping w/the hand-rolled theme:

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

OR you could build your soap envelope by hand and use 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); } 

which is called with:

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

Testing it in Fiddler would look something like this:

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 

Body:

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

Again, this answer isn't trying to resolve how to invoke svcUtil differently, but to avoid calling it altogether, so I hope the edit gods don't ding me for that ;-)

My code above has been inspired by better developers than I, but I hope it helps.

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

like image 75
James Fleming Avatar answered Oct 11 '22 14:10

James Fleming