Thursday, October 18, 2012

WCF POX, JSON and SOAP Coexist


Sometimes, we want to make a service available in different protocols so that clients could have an option to choose one of their favorite methods to consume the web services.
Here we are going to talk about how to make one WCF service available in POX(Plain Old XML as XML for short), JSON and SOAP from different endpoints within same service host.
A bit background. A WCF service can have multiple endpoints configured within the same host.
For example, TestService can be exposed at the following endpoints.
  • http://www.example.com//TestService/soap
  • http://www.example.com//TestService/pox
  • http://www.example.com//TestService/json
Each endpoint could have its own binding and endpointBehavior. The binding can specify the protocol that the endpoint agreed with. The endpointBehavior can be utilized to extend the service/client run-time, in this case, it specifies the serialization (more details in later section)
In this section, we will use basicHttpBinding and webHttpBinding that pre-configured by the WCF framework.
  • basicHttpBinding is for services that conform to WS-I Basic Profile 1.1. e.g. SOAP, Envelope, Body etc
  • .
  • webHttpBinding specifies that the service understands generic HTTP requests instead of SOAP requests. The REST service is built on top of generic HTTP request with GET HTTP verb.
Let’s do some coding, first we create a simple service with only one operation contract.
// Service Contract     [ServiceContract(Namespace = "http://www.example.com/service")]     public interface ITestService     {         [OperationContract]         [WebGet]         FooDataContract[] GetFoo(string name, int age, int numberOfFoo);     }
There is WebGet attribute, mocked up on GetFoo operation contract, specifies this operation can
understand HTTP GET verb in the request, the goal is to make the operation RESTful. WebGet attribute is only understood by WCF WebHttpBehavior, so it does not affect the SOAP request since the SOAP endpoint will not use WebHttpBehavior (This will be explained in endpoint configuration section)
You may notice the FooDataContract array is the return type of the method, we will defined a simple class for it.
// DataContract     [DataContract(Name = "Foo", Namespace = "http://www.example.com/data")]     public class FooDataContract     {         [DataMember(Order = 0)]         public string Name { get; set; }         [DataMember(Order = 1)]         public int Age { get; set; }     }
There is nothing much to explain about the datacontract, it simply tells the serializer the name and namespace that the object should be serialized on wire and the ordering of the properties at message level. However the auto property i.e. {get;set;} is a neat feature of .NET 3+
The final step of coding the service is the service implementation.
// ITestService Implementation,     // Each request will initialize its own instance of the TestService     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]     public class TestService : ITestService     {         public FooDataContract[] GetFoo(string name, int age, int numberOfFoo)         {             List<FooDataContract> result = null;             for (int i = 0; i < numberOfFoo; i++)             {                 if (result == null)                     result = new List<FooDataContract>();                 result.Add(new FooDataContract()                 {                     // default to "null"                     Name = (name ?? "null") + "_" + i,                     Age = age                 });             }             // return null or array             return result == null ? null : result.ToArray();         }     }
The service returns a list of FooDataContract objects to the clients according to the parameters.
We have completed the WCF service implementation, but we need to make it configured and hosted for the main purposes of this post, the most interesting part is the configuration part
There are three key areas of configuration i.e. service endpoints, binding and endpointBehavior.
Let’s start with endpoint configuration, it is completed server app.config below.
<configuration>   <system.serviceModel>     <!-- bindings -->     <bindings>       <basicHttpBinding>         <binding name ="soapBinding">           <security mode="None">           </security>         </binding>       </basicHttpBinding>       <webHttpBinding>         <binding name="webBinding">         </binding>       </webHttpBinding>     </bindings>     <!-- behaviors -->     <behaviors>       <endpointBehaviors>         <!-- plain old XML -->         <behavior name="poxBehavior">           <webHttp/>         </behavior>         <!-- JSON -->         <behavior name="jsonBehavior">           <enableWebScript  />         </behavior>       </endpointBehaviors>       <serviceBehaviors>         <behavior name="defaultBehavior">           <serviceDebug includeExceptionDetailInFaults="true" />           <serviceMetadata httpGetEnabled="true" />         </behavior>       </serviceBehaviors>     </behaviors>     <services>       <service name="WcfService.TestService" behaviorConfiguration="defaultBehavior">         <host>           <baseAddresses>             <!-- note, choose an available port-->             <add baseAddress="http://localhost:81/TestService" />           </baseAddresses>         </host>         <endpoint address="soap"                   binding="basicHttpBinding"                   bindingConfiguration="soapBinding"                   contract="WcfService.ITestService" />         <endpoint address="pox"                    binding="webHttpBinding"                   bindingConfiguration="webBinding"                    behaviorConfiguration="poxBehavior"                   contract="WcfService.ITestService" />         <endpoint address="json"                   binding="webHttpBinding"                   bindingConfiguration="webBinding"                    behaviorConfiguration="jsonBehavior"                   contract="WcfService.ITestService" />       </service>     </services>   </system.serviceModel> </configuration>
As mentioned earlier, there will be three endpoints available, pox, json as REST service and soap as SOAP service.
The SOAP endpoint uses basicHttpBinding which comes from WCF, it specifies the endpoint uses SOAP as service (You can specify the version of the message, security etc in “soapBinding” section, but will not be explained here in details).
The POX endpoint uses webHttpBinding with “webBinding” configuration section and the endpointBehavior is poxBehavior.
This tells the POX endpoint it can receive generic HTTP requests and the poxBehavior with webHttp element tells the current endpoint will provide web style services and also at run time, it looks for WebGet attribute on the operation contract in order to determine if the parameters can be parsed from the URI/URL in the GET request. The result is in XML (Plain Old way that you normally see how XML looks like, not SOAP).
The JSON endpoint is specified by webHttpBinding again because it will surely understand generic HTTP request. The special part in this endpoint is the endpoint behavior configuration which is jsonBehavior. It contains enableWebScript element which is derived type of webHttp used in POX endpoint behavior, this means on top of POX REST service, it enables the JavaScript proxy endpoint which allows AJAX calls from Browser. In current context, the purpose is to make the response data to the clients in JSON format.
The code below is the console host for the service.
class Program     {         static void Main(string[] args)         {             // per call instancing             ServiceHost host = new ServiceHost(typeof(TestService));             // start listening             host.Open();             Console.WriteLine("Service started \nPress [Enter] to Close");             Console.ReadLine();             host.Close();         }     }
We have done most of the service, it is time to write a client to test against it. It is a bit tricky to get the client going by calling each of the endpoint.
Firstly, create a new console project and add the service reference to http://localhost:81/TestService
Now you have automatically generated proxy with configuration in the app.config
The generated content within app.config file is useless, we need to manually configure it.
Here is a completed configured client app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.serviceModel>       <!-- bindings -->       <bindings>         <basicHttpBinding>           <binding name ="soapBinding">             <security mode="None">             </security>           </binding>         </basicHttpBinding>         <webHttpBinding>           <binding name="webBinding">           </binding>         </webHttpBinding>       </bindings>       <!-- behaviors -->       <behaviors>         <endpointBehaviors>           <!-- plain old XML -->           <behavior name="poxBehavior">             <webHttp/>           </behavior>           <!-- JSON -->           <behavior name="jsonBehavior">             <enableWebScript  />           </behavior>         </endpointBehaviors>         <serviceBehaviors>           <behavior name="defaultBehavior">             <serviceDebug includeExceptionDetailInFaults="true" />             <serviceMetadata httpGetEnabled="true" />           </behavior>         </serviceBehaviors>       </behaviors>         <client>           <endpoint address="http://localhost:81/TestService/soap"                     binding="basicHttpBinding"                     bindingConfiguration="soapBinding"                     contract="TestServiceReference.ITestService" /> <!-- only one endpoint can be configured for client  at a time           <endpoint address="http://localhost:81/TestService/pox"                      binding="webHttpBinding"                     bindingConfiguration="webBinding"                      behaviorConfiguration="poxBehavior"                     contract="TestServiceReference.ITestService" />           <endpoint address="http://localhost:81/TestService/json"                     binding="webHttpBinding"                     bindingConfiguration="webBinding"                      behaviorConfiguration="jsonBehavior"                     contract="TestServiceReference.ITestService" /> -->         </client>     </system.serviceModel> </configuration>
In client Program.cs file, calling the method GetFoo with SOAP,
Remember, only one endpoint can be configured at a time, let’s try the SOAP one first by commenting out the other two endpoints,
<!-- ***http headers*** POST /TestService/soap HTTP/1.1 Content-Type: text/xml; charset=utf-8 SOAPAction: "http://www.example.com/service/ITestService/GetFoo" Host: localhost:81 Content-Length: 211 Expect: 100-continue --> <!-- SOAP --> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">   <s:Body>     <GetFoo xmlns="http://www.example.com/service">       <name>wall-e</name>       <age>256</age>       <numberOfFoo>2</numberOfFoo>     </GetFoo>   </s:Body> </s:Envelope>
The request is HTTP POST with SOAP content to the service.
It is the corresponding SOAP response below.
Yes, we have successfully made a SOAP request and obtained a SOAP response.
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">   <s:Body>     <GetFooResponse xmlns="http://www.example.com/service">       <GetFooResult xmlns:a="http://www.example.com/data" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">         <a:Foo>           <a:Name>wall-e_0</a:Name>           <a:Age>256</a:Age>         </a:Foo>         <a:Foo>           <a:Name> wall-e_1 </a:Name>           <a:Age>256</a:Age>         </a:Foo>       </GetFooResult>     </GetFooResponse>   </s:Body> </s:Envelope>
Let’s try POX endpoint, comment out SOAP and JSON endpoint and leave pox endpoint uncommented. Run the client again. Here we come to a tricky part, we get an exception!
Operation ‘GetFoo’ of contract ‘ITestService’ specifies multiple request body parameters to be serialized without any wrapper elements. At most one body parameter can be serialized without wrapper elements. Either remove the extra body parameters or set the BodyStyle property on the WebGetAttribute/WebInvokeAttribute to Wrapped.
This means by default, the client tries to request the service with POST, therefore client tries to serialize the parameters in the http body. Without SOAP, there is no way to serialize
GetFoo(“wall-e”, 256, 2) into XML
<!–  this is not XML –>
      <name>wall-e</name>
      <age>256</age>
      <numberOfFoo>2</numberOfFoo>
unless you specify the body style should be wrapped automatically.
<!–  this is wrapped XML –>
      <request>
        <name>wall-e</name>
        <age>256</age>
        <numberOfFoo>2</numberOfFoo>
      </request>
However, we want to call the service by GET, not POSTing the data to the server for a “GET” actions. There is an important extra step to do in order to let the client dispatch the HTTP request with parameters in the URL which conforms to the REST service call. It is the WebGet attribute on the client side, while generating the proxy by adding service reference, WebGet attribute does not add to the service contract automatically. WebGet attribute is also required on the client side for consuming the service in the REST way. (Remember, webHttp and enableWebScript behavior look for this attribute to design the HTTP request in the earlier discussion). What we need to do is simply locate the file “reference.cs” generated after adding service reference.
Search keyword: “public interface ITestService” within the client project, you will find the generated service contract, add [System.ServiceModel.Web.WebGet] on top of the OperationContract.
[System.ServiceModel.Web.WebGet]
WcfServiceClient.TestServiceReference.Foo[] GetFoo(string name, int age, int numberOfFoo);
OK, it is done, run the client again,
// REST request with parameters in the URL
GET /TestService/pox/GetFoo?name=wall-e&age=256&numberOfFoo=2 HTTP/1.1
Content-Type: application/xml; charset=utf-8
Host: localhost:81
The request no more contains any HTTP body as all the information is expressed in the URL.
The following replied XML (Plain Old XML) is really just the serialized form of array of FooDataContract.
<ArrayOfFoo xmlns="http://www.example.com/data" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">   <Foo>     <Name>wall-e_0</Name>     <Age>256</Age>   </Foo>   <Foo>     <Name>wall-e_1</Name>     <Age>256</Age>   </Foo> </ArrayOfFoo>
The JSON request, this is the final test in this session,
The request is exactly the same as the POX request,
// REST request with parameters in the URL
GET /TestService/json/GetFoo?name=wall-e&age=256&numberOfFoo=2 HTTP/1.1
Content-Type: application/xml; charset=utf-8
Host: localhost:81
As expected the response is in JSON format.
{"d":[       {         "__type":"Foo:http:\/\/www.example.com\/data",         "Name":"wall-e_0",         "Age":256       },       {         "__type":"Foo:http:\/\/www.example.com\/data",         "Name":"wall-e_0",         "Age":256       }     ] }
This example uses windows console as service self-host, in IIS6 or 7, it will be slightly different.
The client utilizes Visual Studio 2008 service proxy generation, in real world, programmers might choose generic WebClient to consume the REST service. A must-have tool during web service development is a http proxy analyzer, not only it can show you how things have been done at the wire level, also it helps a lot to debug the services and clients during the development. All the raw JSON, XML, SOAP or even HTTP headers are exposed to programmers. There are free and commercial tools available, Google will find one of the best HTTP proxy analyzers for you.

No comments:

Post a Comment