Showing posts with label HttpClient. Show all posts
Showing posts with label HttpClient. Show all posts

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.


Tuesday, April 23, 2013

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

Today, I want to share how to host WCF REST service and how to post HTTP content to the service with WCF proxy class and HttpClient. Both clients should behave the same. Also, knowing from my previous post, WebAPI cannot support multiple input parameters. Let's see how WCF REST deal with the same scenario.

I have a simple WCF REST service like this:


[ServiceContract]
public interface IResultService
{

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

    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "SubmitResult", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
    string SubmitResult(int id, string name, int score);
}



I have one HTTP GET method to list Result object and the response format is in JSON. And, I have another one for HTTP POST method to submit result, and of cause, more than one input parameters. :) Both request and response format are set to JSON.

Once the coding part are done, here are my WCF configuration:


<serviceHostingEnvironment multipleSiteBindingsEnabled="true">
  <serviceActivations>
    <add factory="System.ServiceModel.Activation.ServiceHostFactory" relativeAddress="./ResultService.svc" service="LayeredWebApi.Services.ResultService" />
  </serviceActivations>
</serviceHostingEnvironment>
<services>
  <service name="LayeredWebApi.Services.ResultService" behaviorConfiguration="DefaultServiceBehavior">
    <endpoint name="webHttpResultService" address="" binding="webHttpBinding" bindingConfiguration="webHttp"
              contract="LayeredWebApi.Services.Contracts.IResultService" behaviorConfiguration="webEndpoint"  />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>
<bindings>
  <webHttpBinding>
    <binding name="webHttp">
      <security mode="None">
        <transport clientCredentialType="None" />
      </security>
    </binding>
  </webHttpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="webEndpoint">
      <webHttp defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Json" />
    </behavior>
  </endpointBehaviors>
  <serviceBehaviors>
    <behavior name="DefaultServiceBehavior">
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
      <serviceMetadata httpGetEnabled="True" />
      <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true" />
    </behavior>
  </serviceBehaviors>
</behaviors>


I am hosting my service with fileless activation by using Service Host Factory, and hosting the service in IIS Express or ASP.NET Development Server. And, since I want to host my WCF service in REST, hence I am using WebHttpBinding binding.

Side Note:

You may encounter common error like this when your client call the WCF REST service.

The message with To 'http://localhost:65000/ResultService.svc/SubmitResult' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.

Or,

InvalidOperationException was unhandled by user code
Manual addressing is enabled on this factory, so all messages sent must be pre-addressed.

You need to create an endpoint behavior with webHttp setting for both service and client:


<behavior name="webEndpoint">
  <webHttp defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Json" />
</behavior>



Once the service is built successfully, we can proceed for testing.
There are 2 ways to submit request to WCF REST service.

Option 1: Easy way. Add service reference to generate WCF proxy class and use the method provided in the proxy class.


[TestMethod]
public void WCFClientPost()
{
    ResultServiceClient proxy = new ResultServiceClient();
    string result = proxy.SubmitResult(1, "Ah Beng", 50);
    Assert.AreEqual("{\"SubmitResultResult\":\"Passed\"}", result, "Wrong result.");
}

The HTTP request format with this option look like this:

POST http://127.0.0.1:65000/ResultService.svc/SubmitResult HTTP/1.1
Content-Type: application/xml; charset=utf-8
VsDebuggerCausalityData: uIDPoxiHM0FUTQ1GpxKA9eADzAcAAAAA0UmoTcKIUEGfJ6K/JCLy3vX1VOeGmo1Ch+u/q3THM1EACQAA
E2EActivity: XmiSZEVwOUKY9poZO7IuKg==
Host: 127.0.0.1:65000
Content-Length: 104
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

<SubmitResult xmlns="http://tempuri.org/"><id>1</id><name>Ah Beng</name><score>50</score></SubmitResult>


Result:

{"SubmitResultResult":"Passed"}


Notice that the proxy class generated the request and wrapped the content in XML format, and then post it to WCF service, but the service respond the result in JSON format. Although the operation contract has specified the request message format is JSON in the WebInvoke attribute, but the service still accept the XML message. However, the response message is in JSON format is correct as expected.


Option 2: Lower level way. Use HttpClient to post object content.

[TestMethod]
public void HttpClientPost()
{
    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"));

        var input = new { id = 1, name = "Ah Beng", score = 50 };

        ObjectContent content = new ObjectContent(input.GetType(), input, new JsonMediaTypeFormatter());

        HttpResponseMessage response = client.PostAsync("ResultService.svc/SubmitResult", content).Result;

        Assert.IsTrue(response.IsSuccessStatusCode, "Failed to call WCF service.");

        if (response.IsSuccessStatusCode)
        {
            Assert.AreEqual("{\"SubmitResultResult\":\"Passed\"}", response.Content.ReadAsStringAsync().Result, "Wrong result.");
        }
    }
}


The HTTP request format with this option look like this:


POST http://127.0.0.1:65000/ResultService.svc/SubmitResult HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: 127.0.0.1:65000
Content-Length: 36
Expect: 100-continue
Connection: Keep-Alive

{"id":1,"name":"Ah Beng","score":50}


Result:


{"SubmitResultResult":"Passed"}



In summary, WebHttpBinding allow you to submit request with XML or JSON format without the need to create another extra duplicate service that accept different request format type. Also, the operation contract support multiple input parameters. When you submit a wrapped and serialized object in XML or JSON format to the WCF service, it itself will automatically deserialize the wrapped content and bind it to the web method parameters accordingly by matching the object property name with the parameter name.

Next, I will cover the HTTP GET method in the next post. This is quite tricky when dealing with the service responded JSON string deserialization especially the return type is generic list type by using the new JSON API from Newtonsoft.



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...