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.



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