Writing custom requests to simple WCF services

Quite often one needs to talk to a WCF service, but using a (WCF) proxy is not a viable alternative. Sometimes the language used isn't a .NET one, the client might not have the .NET framework installed, or the overhead of the proxy is too big for the application need. In this case, creating a request "from scratch" (i.e., using sockets or HTTP requests directly) is usually the best option. In this case, the developer needs to find out the format of the message that is to be sent to the service.

The "correct" way of finding out this format is to look at the metadata from the service, and create a request that complies with all the assertions on the WSDL (or MEX policies). This is usually overkill for simple applications, as a simple template-based input would suffice. This post will present some ways of looking at the message sent from a WCF client (even though you'd not use one in production) and finding out this template.

1. Network capture (the easiest way)

If you can look at what is going on over the wire, you'll be able to see the message. I've found that Fiddler (https://www.fiddler2.com) is one of the best "men-in-the-middle" tools for this kind of task. It installs itself as a proxy in the machine, and any request that goes to https://<machine_name>... will be intercepted by it (notice that for it to work you can't use localhost, unless you configure the proxy settings to not bypass it). It has some problems on Vista, so sometimes it may not be feasible to use it. Also, if the transport of the message is not HTTP, this will not work either.

If you can split the client and server in two machines, then Netmon (https://www.microsoft.com/downloads/details.aspx?familyid=18b1d59d-f4d8-4213-8d17-2f6dde7d7aac&displaylang=en) will work all the times. This is also the best solution if you use TCP to talk to the service.

Network capture is the best way to deal with non-XML messages, as the next option will only show the XML representation of the message, even if it's encoded in some other form (such as JSON, MTOM or the binary encoding).

2. Message Logging (the WCF-only way)

WCF provides a way to log the incoming/outgoing messages, and this will allow you to see what the server is receiving. Enabling message logging with the SvcConfigEditor (https://msdn2.microsoft.com/en-us/library/ms732009.aspx, installed with the SDK) tool is very simple (select "Diagnostics", then "Enable Message Logging"), although I also like to add the "logEntireMessage='true'" option when trying to inspect messages. This config file below enables logging of all messages that flow through the service. The best way to look at the messages is with the SvcTraceViewer tool (https://msdn2.microsoft.com/en-us/library/ms732023.aspx, also installed with the SDK). If the trace viewer isn't available, I find it a quick solution to open the file with notepad, put a "<root>" in the beginning of it, a "</root>" at the end of it, and then opened it on IE (the message log file is an XML file, but it doesn't contain a root element).

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
<listeners>
<add name="ServiceModelMessageLoggingListener">
<filter type="" />
</add>
</listeners>
</source>
</sources>
<sharedListeners>
<add initializeData="messages.svclog"
                    type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                    name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
<filter type="" />
</add>
</sharedListeners>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logMessagesAtTransportLevel="true" logEntireMessage="true"/>
</diagnostics>
</system.serviceModel>
</configuration>

3. Message interception (harder way)

This is definitely overkill for this problem (finding out the messages themselves), so I'll leave their details to a future post. There are two alternatives here. One "less hard": add an instance of an IDispatchMessageInspector (https://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.idispatchmessageinspector.aspx) to the list of the inspectors on the endpoint (you'll need an endpoint behavior to do that). The other (harder) is the one that involves more work, which involves creating a custom MessageEncoder, which can look at the incoming/outgoing message bytes as they are on the wire, and then add all the plumbing required to add it to the binding (a MessageEncoderFactory and a MessageEncodingBindingElement). This custom message encoder would simply delegate the work to the "actual" encoder that is being used by the service.

Examples 

Below is a service contract and some examples of requests to each of its three operations. The parts in bold in the requests are to be replaced by the actual values in the request.

[

DataContract]
public class MyDC
{
[DataMember]
    public string str = "The string";
}
[ServiceContract]
public interface ITest
{
[OperationContract]
    string EchoString(string text);
[OperationContract]
    int Add(int x, int y);
[OperationContract]
    MyDC EchoDC(MyDC input);
}
public class Service : ITest
{
    public string EchoString(string text)
{
        return text;
}
    public int Add(int x, int y)
{
        return x + y;
}
    public MyDC EchoDC(MyDC input)
{
        return input;
}
}
static Binding GetBinding()
{
    BasicHttpBinding result = new BasicHttpBinding();
    return result;
}
public static void Test()
{
    string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
    ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
host.Open();
    Console.WriteLine("Host opened");

    ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
    ITest proxy = factory.CreateChannel();
    Console.WriteLine(proxy.EchoString("Hello"));
    Console.WriteLine(proxy.EchoDC(new MyDC()));
    Console.WriteLine(proxy.Add(3, 5));

((IClientChannel)proxy).Close();
factory.Close();
    Console.Write("Press ENTER to close the host");
    Console.ReadLine();
host.Close();
}

EchoDC:

POST /Service HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "https://tempuri.org/ITest/EchoDC"
Host: THE_MACHINE_NAME:8000
Content-Length: THE_ACTUAL_LENGTH

<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/"><s:Body><EchoDC xmlns="https://tempuri.org/"><input xmlns:a="https://schemas.datacontract.org/2004/07/WCFForums" xmlns:i="https://www.w3.org/2001/XMLSchema-instance"><a:str>The string</a:str></input></EchoDC></s:Body></s:Envelope>

Add:

POST /Service HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "https://tempuri.org/ITest/Add"
Host: THE_MACHINE_NAME:8000
Content-Length: THE_ACTUAL_LENGTH

<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/"><s:Body><Add xmlns="https://tempuri.org/"><x>3</x><y>5</y></Add></s:Body></s:Envelope>

EchoString:

POST /Service HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "https://tempuri.org/ITest/EchoString"
Host: THE_MACHINE_NAME:8000
Content-Length: THE_ACTUAL_LENGTH

<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/"><s:Body><EchoString xmlns="https://tempuri.org/"><text>Hello</text></EchoString></s:Body></s:Envelope>