1

I have a service I'm exposing using WCF, running on Mono in a SLES box. I'm using BasicHttpBinding for this. Now, I have a method that simply returns a boolean value, and I want to call this method from the SLES box itself and get the boolean value. I'm trying to use curl for this but so far I have not had any success calling the method.

Let's say my service is called "RemoteService" and the method I want to call is "CheckProcessRunning". Again, CheckProcessRunning simply returns a boolean value, and it returns very fast.

So I have tried:

curl -H "Content-Type: text/xml; charset=utf-8" -H "SOAPAction:" -X GET http://localhost:4000/RemoteService/CheckProcessRunning

curl --header "Content-Type: text/xml;charset=UTF-8" --header "SOAPAction:RemoteService/CheckProcessRunning" http://localhost:4000/RemoteService/CheckProcessRunning

curl --header "Content-Type: text/xml;charset=UTF-8" --header "SOAPAction:url:CheckProcessRunning" http://localhost:4000/RemoteService

All return something like this:

<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">a:DestinationUnreachable</faultcode><faultstring xml:lang="">The request message has the target 'http://localhost:4000/RemoteService/CheckProcessRunning' with action '' which is not reachable in this service contract</faultstring></s:Fault></s:Body></s:Envelope>

So my question is, how can I do curl requests to a SOAP method in a WCF service? I need to do this using BasicHttpBinding - I've looked into WebHttpBinding, but I found I cannot use it as it breaks other functionality.

Tino
  • 304
  • 4
  • 14

2 Answers2

1

The error message in your post says the action is blank or empty. So, a solution might be to change your service to support empty actions. The service will need to work out which method to call by looking at the request body instead. Luckily, there is an example of this in the WCF & WF Samples for .NET 4 and good info on usage here.

There is another article that refers to cURL and tailors the same sample specifically for empty actions here: WCF, cURL, Java and ASMX. Credit must go to the original author, but for posterity, here is the code...

Specify an empty action in the operation contracts:

[OperationContract(Action="")]
ResponseMessage DoOperationA(RequestMessage message);

[OperationContract(Action="")]
ResponseMessage DoOperationB(RequestMessage message);

Add an EmptyActionBehaviorAttribute class to the service project:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class EmptyActionBehaviorAttribute : Attribute, IContractBehavior
{
    public void AddBindingParameters(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters)
    {
        return;
    }

    public void ApplyClientBehavior(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        ClientRuntime clientRuntime)
    {
        return;
    }

    public void ApplyDispatchBehavior(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        DispatchRuntime dispatchRuntime)
    {
        var dispatchDictionary = new Dictionary<XmlQualifiedName, string>();

        foreach (var operationDescription in contractDescription.Operations)
        {
            var qname = new XmlQualifiedName(
                operationDescription.Messages[0].Body.WrapperName, 
                operationDescription.Messages[0].Body.WrapperNamespace);

            dispatchDictionary.Add(qname, operationDescription.Name);
        }

        dispatchRuntime.OperationSelector 
            = new EmptyActionOperationSelector(dispatchDictionary);
    }

    public void Validate(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint)
    {
    }
}

Add an EmptyActionOperationSelector class to the service project:

class EmptyActionOperationSelector : IDispatchOperationSelector
{
    Dictionary<XmlQualifiedName, string> dispatchDictionary;

    public EmptyActionOperationSelector(
        Dictionary<XmlQualifiedName, string> dispatchDictionary)
    {
        this.dispatchDictionary = dispatchDictionary;            
    }

    public string SelectOperation(ref System.ServiceModel.Channels.Message message)
    {
        var xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(message.ToString());

        var nsManager = new XmlNamespaceManager(xmlDoc.NameTable);
        nsManager.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");

        XmlNode node = xmlDoc.SelectSingleNode(
            "/soapenv:Envelope/soapenv:Body", 
            nsManager).FirstChild;

        var lookupQName = new XmlQualifiedName(node.LocalName, node.NamespaceURI);

        return dispatchDictionary.ContainsKey(lookupQName)
            ? dispatchDictionary[lookupQName]
            : node.LocalName;           
    }
}

Finally, use the new attribute on the service contract:

[ServiceContract]
[XmlSerializerFormat(Style = OperationFormatStyle.Rpc, Use = OperationFormatUse.Encoded)]
[EmptyActionBehavior]
public interface IMyService
Community
  • 1
  • 1
davmos
  • 9,324
  • 4
  • 40
  • 43
  • I did this but so far have not been able to get it to work. I get this error message when using the following curl command: `curl -H "Content-Type: text/xml; charset=utf-8" -H "SOAPAction:" -X GET http://localhost:4000/RemoteService/CheckProcessRunning` – Tino Sep 08 '15 at 18:19
  • `a:DestinationUnreachableThe request message has the target 'http://localhost:4000/RemoteService/CheckProcessRunning' with action '' which is not reachable in this service contract< /s:Body>` – Tino Sep 08 '15 at 18:20
  • OK, can you install [Fiddler](http://www.telerik.com/fiddler)? You could use it to get the request details sent from cURL and also from where you can successfully call the service. Look for differences between the failed & successful requests. Also add the request details to the question... and message me again ;) – davmos Sep 08 '15 at 20:41
1

So here is what got it working:

I added the WebInvoke decoration to the WCF interface, to specify the method and format of the request and response:

[OperationContract]
[WebInvoke(Method = "GET", RequestFormat = WebMessageFormat.Json,
       ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
bool CheckProcessRunning();

I also initialized another host (using a different port) to expose my interface as a REST service, using WebServiceHost:

string webHttpAddress = "http://localhost:4001/RemoteService";
WebServiceHost webServiceHost = new WebServiceHost(typeof(RemoteService), new Uri[] { new Uri(webHttpAddress) });

webServiceHost.AddServiceEndpoint(typeof(IRemoteService), new WebHttpBinding(WebHttpSecurityMode.None), webHttpAddress);
webServiceHost.Open();

And now I can call the CheckProcessRunning method using curl:

curl http://localhost:4001/RemoteService/CheckProcessRunning
true
Tino
  • 304
  • 4
  • 14