Friday, May 17, 2013

WCF REST Service + HttpClient vs WCF Proxy Class - Part 2

Continue from the previous post, I would like to share how to make HTTP GET call to a WCF REST Service.

Following is my operation contract setup:


[OperationContract]
[WebGet(UriTemplate = "ListResults", ResponseFormat = WebMessageFormat.Json)]
List<Result> ListResults();


WCF Service Client

Normally, when we want to call that web service, we can simply perform the "Add Service Reference" from the Visual Studio, and the WCF client aka the proxy class will be auto-generated. Therefore, the following is the code that you normally would use to call a WCF service:


ResultServiceClient proxy = new ResultServiceClient();
List<Result> results = proxy.ListResults();


Well, this code cannot work with the WCF REST service where the operation contract is decorated with WebGet attribute. The reason is WebGet attribute actually make the data retrieval operation expect a GET method call. But, the above code is actually making a POST method call. See the following RAW data which is made by the proxy class:


POST http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Content-Type: application/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo8z4w4PqwblMjjlSjLko010AAAAA6fc873e/5U+GAxRCKLz7Mps+z0ILaFhMs1wQZ9g/XOwACQAA
E2EActivity: DUiXbbtRBk6NgLuSr8X7+A==
Host: 127.0.0.1:65000
Content-Length: 42
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

<ListResults xmlns="http://tempuri.org/"/>

You would get the response HTTP 405 - Method Not Allowed.


HTTP/1.1 405 Method Not Allowed
Server: ASP.NET Development Server/11.0.0.0
Date: Fri, 17 May 2013 00:40:32 GMT
X-AspNet-Version: 4.0.30319
Allow: GET
Content-Length: 1565
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Connection: Close


Therefore, we have to use WebChannelFactory to make the WCF REST service call with HTTP GET method. Here is the code:


var behavior = new WebHttpBehavior();
behavior.DefaultBodyStyle = WebMessageBodyStyle.Wrapped;

//Note: the IResultService is not the one generated from the svcutil.exe
//It should be your Service Contract
using (var factory = new WebChannelFactory<LayeredWebApi.Services.Contracts.IResultService>(
    new WebHttpBinding(),
    new Uri("http://ipv4.fiddler:65000/ResultService.svc")
))
{
    factory.Endpoint.EndpointBehaviors.Add(behavior);

    var channel = factory.CreateChannel();

    //Note: the Result object is not the one generated from the svcutil.exe
    List<LayeredWebApi.Entities.Result> results = channel.ListResults();
}


And, here is the HTTP RAW content made by the WebChannelFactory:


GET http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Content-Type: application/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo6WsSaH5x2NJta5xXQVoGTcAAAAA5M7pLj2DXEOHIOoTRG4R1t5jyJvtJCVLlnuRlW/5RqcACQAA
E2EActivity: BOiPxzfbnUCLg4OkbNlTDw==
Host: 127.0.0.1:65000
Accept-Encoding: gzip, deflate
Connection: Keep-Alive


HttpClient 


There is another way to call the WCF REST service which is by using HttpClient. Here is the code:


using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("http://ipv4.fiddler:65000");
    client.DefaultRequestHeaders.Accept.Add(
        new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.GetAsync("ResultService.svc/ListResults").Result;

    Assert.IsTrue(response.IsSuccessStatusCode, "Failed to call WCF service.");
    string json = response.Content.ReadAsStringAsync().Result;

    JObject jObj = JsonConvert.DeserializeObject(json) as JObject;
               
    //Here is the tricky part.
    //WCF REST service return the real result object inside an object property
    //The object property name is prefixed with service: e.g. <ServiceName>Result
    //You know that your result is a generic list, you have to convert the result to JArray first
    //Then convert the JArray to generic List
    List<Result> result = jObj.GetValue("ListResultsResult")
                              .ToObject<JArray>()
                              .ToObject<List<Result>>();

}




See the following RAW content made by the HttpClient:


GET http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Accept: application/json
Host: 127.0.0.1:65000
Connection: Keep-Alive



Simple and let's look at the result. Note: There is a tricky part when deal with WCF REST service return result. Look at the following RAW content return from the service:


HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Fri, 17 May 2013 03:35:08 GMT
X-AspNet-Version: 4.0.30319
Content-Length: 286
Cache-Control: private
Content-Type: application/json; charset=utf-8
Connection: Close

{"ListResultsResult":[{"<ID>k__BackingField":1,"<Name>k__BackingField":"Ah Beng","<Score>k__BackingField":50},{"<ID>k__BackingField":2,"<Name>k__BackingField":"Ah Lian","<Score>k__BackingField":72},{"<ID>k__BackingField":3,"<Name>k__BackingField":"Ah Boon","<Score>k__BackingField":1}]}


The List<Result> type has been serialized into JSON as you can see above, however, it is assigned to one property call ListResultsResult as highlighted above. Therefore, in my code with HttpClient, after deserialize the whole JSON string into a JObject (from Newtonsoft), I have to have this code jObj.GetValue("ListResultsResult") to get the real result return by the service method. And then, since the data type is a generic list, it must be a JArray type. I have to convert the result into JArray first, then only convert it into List<Result> type.

Compare to WCF client that use WebChannelFactory, the serialization and deserialization is done at the back with the .net serialization library. It is transparent to you with this one line of code: List<LayeredWebApi.Entities.Result> results = channel.ListResults();

Well, it is up to your call which WCF REST client you wish to use.


No comments:

Post a Comment

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...