33

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Sentinel
  • 3,582
  • 1
  • 30
  • 44
  • Did you tried to Add it as a **Web Reference** instead of a **Service Reference** ? It could resolve some problems with 3rd party services. Take a look : http://stackoverflow.com/questions/799365/wcf-svcutil-generates-invalid-client-proxy-apache-axis-web-service-overload-o – JoeBilly Nov 16 '13 at 12:42
  • In a WF Service project, the Web Reference is not available. – Sentinel Nov 17 '13 at 18:39
  • ...actually, in my VS2k12 installation at home, it is available. Hmm. Thanks, I will take a look why it was not available in the office. – Sentinel Nov 17 '13 at 18:45
  • ...no , web reference does not result in xamlx activities, so it cannot be used. – Sentinel Nov 17 '13 at 19:03
  • Its strange that the generated code has a pu013Response class at all. I would expect that if it was creating classes based on the input/output message types the class should have been called SimpleResponse. If the input message for the pu013 operation was called SimpleRequest would it have automatically named it pu013Request? – Todd Bowles Nov 18 '13 at 05:47
  • 1
    I have managed to open a tech support ticket with Microsoft on this one, so I am waiting to see what they say. There are multiple problems here, and one of them is that the actual SOAP message I am getting from the server (a 3rd party) is also ignoring the SimpleResponse, and sending back pu013Response , mi102Response etc. I think this is default behaviour according to WSDL specs. – Sentinel Nov 18 '13 at 07:58
  • 1
    Did you try generating proxy using svcutil? This also provide a good way to control your service proxy namespace. I am not really sure if this will fix your problem but I have experienced that adding service/web reference from VS adds additional code sometimes in proxy file. – sajoshi Jan 08 '14 at 09:57
  • Does the structure of the `pu013Response` match that of the `simpleResponse`? It could be that it's just a name. Since the same type is used in both methods, it gets named in the first operation (pu013) & reused the 2nd time. provided the output is correct, it shouldn't really matter what the code is called. – Simon Halsey Feb 03 '14 at 00:02
  • 1
    pu013Response does have the same structure yes. The problem is that all those that reuse pu013Response can no longer deserialise the SOAP response which is .... not This is where manual corrections to the proxy help – Sentinel Feb 03 '14 at 10:21
  • Is performance an issue with this call? In other words, could you import the service dynamically and then call it instead of creating the .cs files at design time? – Graymatter Apr 29 '14 at 06:37
  • WF elements cannot be created dynamically AFAIK, or rather, child elements of some custom activity would need to be created dynamically, but how does that help? – Sentinel Apr 30 '14 at 07:34

2 Answers2

2

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

James Fleming
  • 2,589
  • 2
  • 25
  • 41
0

I would suggest you to generated wsdl proxy using command line utility and add generated proxy file in your project. It will work from every project and you can find the required configurations from output.config that will generate from command line utility.

If you require the wsdl command and options then I can provide you.