Sunday, December 2, 2012

WCF : A Simple REST Client using HttpClient

[Articles for WCF REST Services]
1) WCF : A simple WCF REST service example (1)
2) WCF : A Simple WCF REST (2) - Remove .svc in REST URL
3) WCF : A Simple WCF REST (3) - Use WCF REST template
4) Client : WCF : A Simple REST Client using HttpClient 

REST client can be written in many ways such as using AJAX, using .NET APIs, etc. This post shows a simple REST client using one of APIs - HttpClient. The HttpClient is a class in REST Starter Kit that creates a REST client.

The sample client below is a console application that utilizes Microsoft.Http.HttpClient. The sample in this post calls a web service that was created from the previouse post (WCF : A Simple REST service example). (Please note that .svc was removed in REST url for brevity by applying a ASP.NET routing as explained in this post, so let's assume that base address is http://localhost/SimpleRest)

(1) Create a new Console project in VS
(2) Add Reference on Microsoft.Http.dll and Microsoft.Http.Extensions.dll which are installed from REST Starter Kit.
(3) Add DataContract class called Customer. Please note that we set empty string for Namespace in DataContract attribute. The namespace should match the one in server-side DataContract.

using System;
using Microsoft.Http; // for WCF REST Starter Kit
using System.Xml.Linq; // for ReadAsXElement
using System.Runtime.Serialization; // for DataContract
using System.Collections.Generic; 
namespace RestKitClient
{
    class Program
    {
        //test example
        static void Main(string[] args)
        {
            GetAllCustomers();
            AddCustomer();
            GetSingleCustomer();
        }

        static void GetSingleCustomer()
        {
            using (HttpClient client = new HttpClient("http://localhost/SimpleRest/"))
            {
                HttpResponseMessage resp = client.Get("Customer/101");
                resp.EnsureStatusIsSuccessful();
                // Read XML
                string result = resp.Content.ReadAsXElement().ToString();
                Console.WriteLine(result);
            }
        }

        static void GetAllCustomers()
        {
            using (HttpClient client = new HttpClient("http://localhost/SimpleRest/"))
            {
                HttpResponseMessage resp = client.Get("Customers");
                resp.EnsureStatusIsSuccessful();
            
                List<Customer> customers = resp.Content.ReadAsDataContract<List<Customer>>();
                foreach (var c in customers)
                {
                    Console.WriteLine(c.ToString());
                }
            }
        }
        
        static void AddCustomer()
        {
            using (HttpClient client = new HttpClient("http://localhost/SimpleRest/"))
            {
                Customer c = new Customer
                {
                    Id = 101,
                    Name = "Lisa2",
                    Address = "New York"
                };
                var body = HttpContentExtensions.CreateDataContract<Customer>(c);

                HttpResponseMessage resp = client.Send(HttpMethod.POST, "Customer", body);                
                resp.EnsureStatusIsSuccessful();

                Console.WriteLine("Successfully posted");
            }
        }

        // Async BeginSend()
        static void GetAllCustomersAsync()
        {
            HttpClient client = new HttpClient("http://localhost/SimpleRest/");
            HttpRequestMessage req = new HttpRequestMessage("GET", "Customers");
            AsyncCallback callback = (asyncResult) => {
                HttpResponseMessage resp = client.EndSend(asyncResult);
                List<Customer> customers = resp.Content.ReadAsDataContract<List<Customer>>();
                foreach (var c in customers)
                {
                    Console.WriteLine(c.ToString());
                }
            };
            client.BeginSend(req, callback, null);  

            Console.WriteLine("Getting data...");
            Console.WriteLine("Do something in main thread...");
            System.Threading.Thread.Sleep(2000);         
        }
    }
    
    [DataContract(Namespace = "")]
    public class Customer
    {
        [DataMember]
        public int Id;
        [DataMember]
        public string Name;
        [DataMember]
        public string Address;

        public override string ToString()
        {
            return Id + "/" + Name + "/" + Address;
        }
    }
}

(4) Basically all requests such as GET, POST, PUT,DELETE can be sent to web service by using Send() method. But each request also can be called by a little simplified wrapper method. For example, for HTTP GET, HttpClient.Get() method is often used.
To get data from REST service, first create a HttpClient object with base address and then use Get method with relative URL and optional parameter(s). The below URL make a REST call to localhost/simplerest/customer/1.

HttpClient client = new HttpClient("http://localhost/SimpleRest/"))
HttpResponseMessage resp = client.Get("Customer/1");

(5) For HTTP POST, let's use HttpClient.Send() method. To post a new data to REST service, first create a HttpClient object with base address, and build a body content, and call POST with relative URL and a request body. For a request body, it can be string, byte array, stream or DataContract object. Below we used Customer DataContract object.

HttpClient client = new HttpClient("http://localhost/SimpleRest/"))
var body = HttpContentExtensions.CreateDataContract<Customer>(custObj);
HttpResponseMessage resp = client.Send(HttpMethod.POST, "Customer", body);    

HTTP DELETE, PUT requests are similar to POST. We can use Send() method with different HttpMethod enum value. We can also use Post(), Put(), Delete() methods in HttpClient class but they all eventually calls a Send() method under the hood.

(6) For asynchronous processing, BeginSend() or SendAsync() methods can be used instead of synchronous Send() method. GetAllCustomersAsync() method in the example above shows an example of BeginSend(). BeginSend() immediately returns without waiting response and run next statements. When the request is done, callback routine is run and EndSend() returns response from the REST call.

WCF : A Simple WCF REST (3) - Use WCF REST template

[Articles for WCF REST Services]
1) WCF : A simple WCF REST service example (1)
2) WCF : A Simple WCF REST (2) - Remove .svc in REST URL
3) WCF : A Simple WCF REST (3) - Use WCF REST template
4) Client : WCF : A Simple REST Client using HttpClient 

The prior posts WCF : A Simple WCF REST (1) and WCF : A Simple WCF REST (2) show how we can create RESTful web service by using WCF. The posts explains all the steps for creating REST service which is good for educational purpose, practically we'd better use WCF REST VS project template. VS 2008 and VS 2010 does not have WCF REST project template by default. They are provided in Online Template which we have to download.
So to get the template, go to Tools -> Extension Manager -> Online Gallary -> Search for WCF REST. You will see WCF REST Service Template 40 (for .NET 4) and WCF REST Service Template 35 (for .NET 3.5).



Download a template and then the project template will be shown in New Project -> Web -> WCF REST Service Application. Below is VS 2010 template.


Once a new project is created from the template, it has a Service1.cs (without IService.cs - the template directly used [ServiceContract] onto the Service1 class, which is legal) , already REST enabled Web.config,  and Global.asax that already has REST routing.
Here is a Service1 class that is generated from the template.
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace WcfRestService2
{
    // Start the service and browse to http://<machine_name>:<port>/Service1/help to view the service's generated help page
    // NOTE: By default, a new instance of the service is created for each call; change the InstanceContextMode to Single if you want
    // a single instance of the service to process all calls. 
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    // NOTE: If the service is renamed, remember to update the global.asax.cs file
    public class Service1
    {
        // TODO: Implement the collection resource that will contain the SampleItem instances

        [WebGet(UriTemplate = "")]
        public List<SampleItem> GetCollection()
        {
            // TODO: Replace the current implementation to return a collection of SampleItem instances
            return new List<SampleItem>() { new SampleItem() { Id = 1, StringValue = "Hello" } };
        }

        [WebInvoke(UriTemplate = "", Method = "POST")]
        public SampleItem Create(SampleItem instance)
        {
            // TODO: Add the new instance of SampleItem to the collection
            throw new NotImplementedException();
        }

        [WebGet(UriTemplate = "{id}")]
        public SampleItem Get(string id)
        {
            // TODO: Return the instance of SampleItem with the given id
            throw new NotImplementedException();
        }

        [WebInvoke(UriTemplate = "{id}", Method = "PUT")]
        public SampleItem Update(string id, SampleItem instance)
        {
            // TODO: Update the given instance of SampleItem in the collection
            throw new NotImplementedException();
        }

        [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
        public void Delete(string id)
        {
            // TODO: Remove the instance of SampleItem with the given id from the collection
            throw new NotImplementedException();
        }

    }
}
And Web.config is:
<?xml version="1.0"?>
<configuration>
  
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, 
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </modules>
  </system.webServer>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <standardEndpoints>
      <webHttpEndpoint>
        <!-- 
            Configure the WCF REST service base address via the global.asax.cs file and the default endpoint 
            via the attributes on the <standardEndpoint> element below
        -->
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>

</configuration>

Global.asax:

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace WcfRestService2
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            // Edit the base address of Service1 by replacing 
            // the "Service1" string below
            RouteTable.Routes.Add(new ServiceRoute("Service1", 
               new WebServiceHostFactory(), typeof(Service1)));
        }
    }
}

Suppose we published this web service to localhost/app,  then we can test http://localhost/app/Service1 to call GetCollection method.

Online template by default sets helpEnabled to true in web.config. So http://localhost/app/Service1/help will show meta data information which is very convenient.

Saturday, December 1, 2012

WCF : A Simple WCF REST (2) - Remove .svc in REST URL

[Articles for WCF REST Services]
1) WCF : A simple WCF REST service example (1)
2) WCF : A Simple WCF REST (2) - Remove .svc in REST URL
3) WCF : A Simple WCF REST (3) - Use WCF REST template
4) Client : WCF : A Simple REST Client using HttpClient 

In previous post, we used a URL containing .svc endpoint like http://localhost/SimpleRest/SimpleRestService.svc/test

In order to remove .svc from REST URL, we can use ASP.NET routing in .NET 4.0 or above.
(1) Assume we use the same Simple REST WCF project as in previous post.
(2) In Web.config, add UrlRoutingModule module and UrlRoutingHandler handler which are required for ASP.NET routing.

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">    
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,
            System.Web, Version=4.0.0.0, Culture=neutral, 
            PublicKeyToken=b03f5f7f11d50a3a" />
    </modules>
    <handlers>
      <add name="UrlRoutingHandler" preCondition="integratedMode" 
                verb="*" path="UrlRouting.axd"/>
    </handlers>
  </system.webServer>

(3) In Web.config, set [aspNetCompatibilityEnabled] attribute to true in WCF host environment.

<system.serviceModel>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" 
      aspNetCompatibilityEnabled="true" />
</system.serviceModel>

(4) Add New Item - [Global Application Class]. This will add global.asax.
(5) Add Reference to [System.ServiceModel.Activation.dll] to use ServiceRoute class.
(6) In Global.asax.cs file, add a Routing entry for REST service. With this routing information, .svc can be omitted.

protected void Application_Start(object sender, EventArgs e)
{
   RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), 
                  typeof(SimpleRestService)));
}

(7) Now we do not have to use .svc in REST URL. In order to test new URL, try http://localhost/SimpleRest/test

And here is an example of REST POST without .svc.



(8) By the way, SimpleRest is web application name, if we want to remove that as well, we can create a Site in IIS Manager and publish REST application onto the Site root.
  • In IIS Manager, rightclick on Sites and click [Add Web Site]
  • Specify any site name and local folder path for web service. You can choose port 80, but only one port can be assigned to one IIS worker process at a time. So you might need to change Default Web Site port to other number like 8888.
  • Ensure that the Site uses .NET 4.0 (or matching version) in [Application Pools] of IIS Manager. And change process [Identity] appropriately if needed.

  • In VS, Publsh REST web service to the Site root.
So now we can use more precise URL such as  http://localhost/test , http://localhost/Customer/1.