Friday, November 30, 2012

WCF : A simple WCF REST service example (1)

[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 

This post shows a very simple WCF REST Service example. Here are summarized steps.

(1) Run Visual Studio
(2) New Project - WCF - [WCF Service Application]
(3) Delete IService1.cs and Service1.svc
(4) Add New Item - [WCF Service] item. Name it SimpleRestService.svc.
(5) Write WCF RESTful interface in ISimpleRestService.cs.

using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace SimpleWcfRESTService
{    
    [ServiceContract]
    public interface ISimpleRestService
    {
        [OperationContract]
        [WebGet(UriTemplate="Test")]
        string DoTest();

        [OperationContract]
        [WebGet(UriTemplate = "CustomerName/{id}")] // URI param is always string type
        string GetCustomerName(string id);

        [OperationContract]
        [WebGet(UriTemplate = "Customer/{id}", BodyStyle = WebMessageBodyStyle.Bare)]
        Customer GetCustomer(string id);

        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "Customer", 
             BodyStyle = WebMessageBodyStyle.Bare,
             RequestFormat = WebMessageFormat.Xml)]
             // Default request format is XML
             // Should set Content-Type: application/xml in req. header
        bool AddCustomer(Customer cust);
        
        [OperationContract]
        [WebInvoke(Method="PUT", UriTemplate="Customer/{id}", BodyStyle = WebMessageBodyStyle.Bare)]
        bool UpdateCustomer(string id, Customer cust);

        [OperationContract]
        [WebInvoke(Method = "DELETE", UriTemplate = "Customer/{id}")]
        bool DeleteCustomer(string id);
    }

    [DataContract(Namespace="")]
    public class Customer
    {
        [DataMember]
        public int Id;
        [DataMember]
        public string Name;
        [DataMember]
        public string Address;     
    }

}

(6) Write WCF REST service class that implements the interface.
namespace SimpleWcfRESTService
{
    public class SimpleRestService : ISimpleRestService
    {        
        public string DoTest()
        {
            return "Testing SimpleRestService...";
        }

        public string GetCustomerName(string id)
        {
            Customer c = DBProcessor.GetCustomer(id);
            return c.Name;
        }

        public Customer GetCustomer(string id)
        {
            return DBProcessor.GetCustomer(id);            
        }

        public bool AddCustomer(Customer cust)
        {
            return DBProcessor.AddCustomer(cust);
        }

        public bool UpdateCustomer(string id, Customer cust)
        {
            return DBProcessor.UpdateCustomer(id, cust);
        }

        public bool DeleteCustomer(string id)
        {
            return DBProcessor.DeleteCustomer(id);
        }
    }
}
For persist mechanism, the example uses a helper class for database processing. This persist mechanism is not the focus of the example, here is an example for simple db processing. Assume we have a DBCustomer table (has Id,Name,Address columns) in SQL and we created LINQ to SQL .dbml for the table. For testing, add a few sample data.

using System.Linq;
namespace SimpleWcfRESTService
{
    public class DBProcessor
    {
        static DataClasses1DataContext db = new DataClasses1DataContext();

        public static Customer GetCustomer(string id)
        {
            int cid = int.Parse(id);
            var dbCust = db.DBCustomers.SingleOrDefault(p => p.Id == cid);
            Customer cust = new Customer();
            if (dbCust != null)
            {
                cust.Id = dbCust.Id;
                cust.Name = dbCust.Name;
                cust.Address = dbCust.Address;
            }
            return cust;
        }

        public static bool AddCustomer(Customer cust)
        {
            try
            {
                DBCustomer dbCust = new DBCustomer
                {
                    Id = cust.Id,
                    Name = cust.Name,
                    Address = cust.Address
                };
                db.DBCustomers.InsertOnSubmit(dbCust);
                db.SubmitChanges();
            }
            catch
            {
                return false;
            }
            return true;
        }

        public static bool UpdateCustomer(string id, Customer cust)
        {
            try
            {
                int cid = int.Parse(id);
                var dbCust = db.DBCustomers.SingleOrDefault(p => p.Id == cid);
                dbCust.Name = cust.Name;
                dbCust.Address = cust.Address;  
                db.SubmitChanges();              
            }
            catch
            {
                return false;
            }
            return true;
        }


        public static bool DeleteCustomer(string id)
        {
            try
            {
                int cid = int.Parse(id);
                var dbCust = db.DBCustomers.SingleOrDefault(p => p.Id == cid);
                db.DBCustomers.DeleteOnSubmit(dbCust);
                db.SubmitChanges();
            }
            catch
            {
                return false;
            }
            return true;
        }
    }
}

(7) Update web.config - use webHttpBinding for REST service and add REST (webHttp) endpointBehavior.

<?xml version="1.0"?>
<configuration>

  <connectionStrings>
    <add name="TestDBConnectionString" connectionString="Data Source=.;Initial Catalog=TestDB;Integrated Security=True"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
  
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="SimpleWcfRESTService.SimpleRestService">
        <endpoint address="" binding="webHttpBinding" 
                  behaviorConfiguration="REST"
                  contract="SimpleWcfRESTService.ISimpleRestService">          
        </endpoint>
      </service>
    </services>
        
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- 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="false"/>
        </behavior>
      </serviceBehaviors>

      <!-- If REST behavoir is not specified, the following error occurs:
           The message with To 'http://localhost/SimpleRest/SimpleRestService.svc/test' cannot be processed at the receiver, 
           due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.-->
      <endpointBehaviors>
        <behavior name="REST">
          <webHttp />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>

(8) IIS hosting - we can create a new Site for the REST service. But in this example, we add a new Web Application called SimpleRest under the Default Web Site (port 80). So the REST service can be accessed at http://localhost/SimpleRest/SimpleRestService.svc. (Run VS as an Administrator and publish REST service by using [Build] - [Publish Service] menu.)



(9) Once the REST service is published to Default Web Site, try a simple test URL in web browser.
http://localhost/SimpleRest/SimpleRestService.svc/test

(10) Now try to get data from database.
http://localhost/SimpleRest/SimpleRestService.svc/CustomerName/1
We got an error this time. When checking SQL Server error log, the failure is obvious. This is because by default [Default Web Site] application pool uses IIS APPPOOL\DefaultAppPool account but SQL does not include the account.

Login failed for user 'IIS APPPOOL\DefaultAppPool'. Reason: Token-based server access validation failed with an infrastructure error

So either add this account (IIS APPPOOL\DefaultAppPool) to SQL logins or use different account for DefaultAppPool application pool. For test purpose, we can simply change IIS App Pool account to Network Service (How to: IIS manager - [Application Pools] - Click [DefaultAppPool] - Click [Advanced Settings] - Change [Identity]. Once it is changed and restart app pool, the URL above should give Name for customer id = 1.


(11) HTTP GET url can be tested in web browser but if we want to send some data to REST service via POST or PUT, we write REST clent program, AJAX in html or use utility tool such as Fiddler. To write REST client program, we could use various APIs such as HttpClient (REST Starter Kit), WCF WebChannelFactory, HttpWebRequest, WebClient. But here let's use Fiddler tool.



In order to test [Add Customer], in Fiddler, go to Composer and select POST method and type url http://localhost/SimpleRest/SimpleRestService.svc/Customer

In request header, type Content-Type: application/xml and type data below in Request Body. Click [Execute] then the data will be inserted into DB.
<Customer><Address>Bellevue WA</Address><Id>10</Id><Name>Tom</Name></Customer>

Please note that we add field data in Address/ID/Name order. If you specify the data in ID/Name/Address as specified in DataContract, Address will be null. This is because the default order of DataContract serialization/deserialization is alphabetical. (This order can be changed by specifying Order in DataMember attribute)

(12) HTTP PUT is similar to POST. So it is needed to fill in Request Body with data. HTTP DELETE is similar to GET since the method prototype only requires Id.
Here are some examples for HTTP GET/POST/PUT/DELETE requests in Fiddler.











(13) How to use JSON
Instead of XML as a request input or response output, we can use simpler JSON format. Default request/response message format is XML, so if we want to use JSON, we have to set RequestFormat and/or ResponseFormat to JSON. For example, we can define new URI [Customer/json] to use JSON input and output.
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "Customer/json", 
     BodyStyle = WebMessageBodyStyle.Bare,
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
Customer AddAndReturnCustomer(Customer cust);

And a simple implementation code is:
public Customer AddAndReturnCustomer(Customer cust) {
     bool success = DBProcessor.AddCustomer(cust);
     return cust;
}

In order to use JSON from Fiddler, we set Content-Type to application/json and put json format input in request body as seen below. Response will be also JSON format as we set ResponseFormat to Json in WebInvoke attribute.




No comments:

Post a Comment