Monday, February 25, 2013

Android with WCF web services


Introduction

This post will briefly describe how to implement WCF web service client side in an Android application. For this purpose, simple “ToDo” Android application will be used for demonstration. The complete source code can be downloaded from here.
The easiest way to do this is by using WCF Rest services that will send information in JSON or XML format. If you would like to use SOAP base services, then you would need to use some 3rd party libraries for Android like kSOAP.

Implementing WCF web service

In this post, we’ll use Azure project approach to implement web service, but any other approach will serve the purpose also.
The first step is to create new Azure Cloud Service project. If you don’t have Azure SDK installed, you can download it from Azure web site. In .NET section you’ll find installs both for VS2012 and VS2010.
In Visual Studio, go File > New Project and under the Cloud category choose Windows Azure Cloud Service project. Fill in necessary data, hit Ok and afterwards select WCF Service Web Role.
NewAzureProj
Step 01: Select Azure project
AzureRoleStep 02: Select WCF Service Role
After you created your project, add a new WCF service or refactor default WCF service that has been generated within the project. Replace service interface with the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFServiceWebRole1
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IToDoService" in both code and config file together.
    [ServiceContract]
    public interface IToDoService
    {
        [OperationContract]
        [WebInvoke(
            Method = "POST",
            UriTemplate = "AddToDoItem",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        void AddToDoItem(ToDoItem toDoItem);

        [OperationContract]
        [WebInvoke(
            Method = "GET",
            UriTemplate = "GetToDoItemsByDate?date={date}",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        List<ToDoItem> GetToDoItemsByDate(String date);

        [OperationContract]
        [WebGet(
            UriTemplate = "GetAllToDoItems",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        List<ToDoItem> GetAllToDoItems();
    }

    [DataContract]
    public class ToDoItem
    {
        [DataMember(Name = "description")]
        public String Description { get; set; }
        [DataMember(Name = "scheduleddate")]
        public DateTime ScheduledDate { get; set; }
    }
}

As you can see we have defined three methods:


  1. AddToDoItem – will be used for adding a new ToDo item. It will receive a ToDo item in JSON format (RequestFormat), through POST request.
  2. GetToDoItemsByDate – will be used to retrieve all ToDo items for specific date. Method receives date as an argument in JSON format, through GET request and retrieves list of ToDo items also in JSON format (ResponseFormat).
  3. GetAllToDoItems – will be used to retrieve all ToDo items. The list of items in JSON format will be in response body for GET request.

DataContract represents the ToDo item model and defines the model properties.

In the next step, we need to implement interface methods:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Text;

namespace WCFServiceWebRole1
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "ToDoService" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select ToDoService.svc or ToDoService.svc.cs at the Solution Explorer and start debugging.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class ToDoService : IToDoService
    {  
        public void AddToDoItem(ToDoItem toDoItem)
        {
            int id = 0;
            try
            {
                using (SqlConnection con = DBConnection.GetConnection())
                {
                    SqlCommand cmd = new SqlCommand("InsertToDo", con);
                    cmd.CommandType = System.Data.CommandType.StoredProcedure;
                    cmd.Parameters.Add(new SqlParameter("@ScheduledDate", toDoItem.ScheduledDate));
                    cmd.Parameters.Add(new SqlParameter("@Description", toDoItem.Description));
                    
                    con.Open();

                    SqlDataAdapter ad = new SqlDataAdapter(cmd);
                    DataSet ds = new DataSet();
                    ad.Fill(ds);
                    DataTable result = ds.Tables[0];
                    foreach (DataRow row in result.Rows)
                    {
                        id = Int16.Parse(row[0].ToString());
                    }
                    con.Close();
                }
                
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
        }

        public List<ToDoItem> GetAllToDoItems()
        {
            List<ToDoItem> items = new List<ToDoItem>();
            using (SqlConnection con = DBConnection.GetConnection())
            {
                SqlCommand cmd = new SqlCommand("GetAllToDoItems", con);
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                con.Open();

                SqlDataAdapter ad = new SqlDataAdapter(cmd);
                DataSet ds = new DataSet();
                ad.Fill(ds);
                DataTable result = ds.Tables[0];

                if (result == null)
                {
                    return null;
                }
                else
                {
                    foreach (DataRow row in result.Rows)
                    {
                        ToDoItem todo = new ToDoItem();
                        todo.ScheduledDate = DateTime.Parse(row[1].ToString());
                        todo.Description = row[2].ToString();

                        items.Add(todo);
                    }
                }
                con.Close();
            }

            return items;
        }

        public List<ToDoItem> GetToDoItemsByDate(String date)
        {
            List<ToDoItem> items = new List<ToDoItem>();
            try
            {
                using (SqlConnection con = DBConnection.GetConnection())
                {
                    SqlCommand cmd = new SqlCommand("GetToDoItemsByDate", con);
                    cmd.CommandType = System.Data.CommandType.StoredProcedure;
                    cmd.Parameters.Add(new SqlParameter("@ScheduledDate", date));
                    con.Open();

                    SqlDataAdapter ad = new SqlDataAdapter(cmd);
                    DataSet ds = new DataSet();
                    ad.Fill(ds);
                    DataTable result = ds.Tables[0];
                    if (result == null)
                    {
                        return null;
                    }
                    else
                    {
                        foreach (DataRow row in result.Rows)
                        {
                            ToDoItem todo = new ToDoItem();
                            todo.ScheduledDate = DateTime.Parse(row[1].ToString());
                            todo.Description = row[2].ToString();

                            items.Add(todo);
                        }
                    }
                    con.Close();
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }

            return items;
        }

    }
}

The concrete implementation contains only SQL stored procedure calls. With these stored procedures, data are stored in and retrieved from MSSQL database. Also, DBConnection class defines DB connection and connection string and it looks like this:



using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Web;

namespace WCFServiceWebRole1
{
    public class DBConnection
    {
        private static String connectionString = "user id=<USERNAME>;" +
                                      @"password=<PASSWORD>;server=<YOUR_HOST>\<MSSQLSERVER>;" +
                                       "Trusted_Connection=yes;" +
                                       "database=ToDoDB; " +
                                       "connection timeout=30";
        private static SqlConnection con = null;

        public static SqlConnection GetConnection()
        {
            con = new SqlConnection(connectionString);
            return con;
        }

        public static void CloseConnection()
        {
            if (con != null)
                con.Close();
        }

        public static void OpenConnection()
        {
            if (con != null)
                con.Open();
        }
    }
}

The last thing concerning WCF service implementation is configuration within WebConfig file in your project. Under <system.serviceModel>, in your config file, you’ll need to make the following changes:

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="httpBehavior">
          <dataContractSerializer maxItemsInObjectGraph="10000000"/>
          <webHttp/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false 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>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
    <services>
      <service name="WCFServiceWebRole1.ToDoService">
        <endpoint address="web" 
                  behaviorConfiguration="httpBehavior"
                  binding="webHttpBinding" 
                  contract="WCFServiceWebRole1.IToDoService" />
        <endpoint address="" 
                  binding="basicHttpBinding" 
                  contract="WCFServiceWebRole1.IToDoService" />
      </service>
    </services>
  </system.serviceModel>



Notice, that you’ll need to add <behavior name="httpBehavior"> and service endpoints under <services>. For purpose of demonstration, you can notice two endpoints with different bindings: basic and web. For current implementation, you can only use webHttpBinding, but in the case you have a legacy WCF service with different binding, you can simply add another endpoint for your new requirement and WCF service will continue to work with the previous and the new binding as well.

Implementing Android WCF client


In this section, it will be only presented the code for communication with previously implemented web service. For complete Android application you can refer to complete project from this link.

Since we are using Rest services the client can be implemented by using HttpRequests. The following code represents the implementation of client side in Android application for each web service’s method call:



package com.smallsoftlab.ToDoDemo.Services;

import android.os.AsyncTask;
import com.smallsoftlab.ToDoDemo.Model.ToDoItemModel;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONStringer;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * User: Vladimir
 * Date: 2/20/13
 * Time: 2:00 PM
 * To change this template use File | Settings | File Templates.
 */
public class Services extends AsyncTask<Object, Void, String> {
    private static final String SVC_URL = "http://10.0.2.2:81/ToDoService.svc/web";

    @Override
    protected String doInBackground(Object... params) {
        String action = params[0].toString();
        if (action.contains("AddToDoItem")) {
            ToDoItemModel toDoItem = (ToDoItemModel) params[1];
            return addToDoItem(action, toDoItem);
        } else if (action.contains("GetToDoItemsByDate")) {
            Date date = (Date) params[1];
            return getToDoItemsByDate(action, date);
        } else if (action.contains("GetAllToDoItems")) {
            return getAllToDoItems(action);
        }
        return null;
    }

    public String addToDoItem(String action, ToDoItemModel toDoItem) {
        int statusCode = 400;
        try {
            // POST request to <service>/AddToDoItem
            // Adds new ToDoItem
            HttpPost request = new HttpPost(SVC_URL + action);
            request.setHeader("Accept", "application/json");
            request.setHeader("Content-type", "application/json");

            // Build JSON string
            JSONStringer item = new JSONStringer()
                    .object()
                    .key("toDoItem")
                    .object()
                    .key("description").value(toDoItem.getDescription())
                    .key("scheduleddate").value("/Date("+toDoItem.getScheduleDate().getTime()+")/")
                    .endObject()
                    .endObject();
            StringEntity entity = new StringEntity(item.toString());

            request.setEntity(entity);

            // Send request to WCF service
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(request);

            statusCode = response.getStatusLine().getStatusCode();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return String.valueOf(statusCode);
    }

    public String getToDoItemsByDate(String action, Date date) {
        String result = "";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            // GET request to <service>/GetToDoItemsByDate
            // Gets a list of ToDoItems for specific date
            HttpGet request = new HttpGet(SVC_URL + action + "?date=" + sdf.format(date));
            request.setHeader("Accept", "application/json");
            request.setHeader("Content-type", "application/json");

            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(request);

            HttpEntity responseEntity = response.getEntity();

            // Read response data into buffer
            char[] buffer = new char[(int) responseEntity.getContentLength()];
            InputStream stream = responseEntity.getContent();
            InputStreamReader reader = new InputStreamReader(stream);
            reader.read(buffer);
            stream.close();

            result = new String(buffer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public String getAllToDoItems(String action) {
        String result = "";
        try {
            // GET request to <service>/GetAllToDoItems
            // Gets all ToDoItems 
            HttpGet request = new HttpGet(SVC_URL + action);
            request.setHeader("Accept", "application/json");
            request.setHeader("Content-type", "application/json");

            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(request);

            HttpEntity responseEntity = response.getEntity();

            // Read response data into buffer
            char[] buffer = new char[(int) responseEntity.getContentLength()];
            InputStream stream = responseEntity.getContent();
            InputStreamReader reader = new InputStreamReader(stream);
            reader.read(buffer);
            stream.close();

            result = new String(buffer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

NOTICE: You can see the service URL is directed to 10.0.2.2, and use this IP address since it is the address of the host that runs the WCF service. Do not use 127.0.0.1 since that would be the address of your Android phone or emulator that runs the application. Also, after you run your Azure project on the emulator, check the port number in your URL. Usually, it is 81, but if it is different you’ll need to change it to the right one.

For the newest versions of Android SDK you’ll need to use the AsyncTask class for any kind of connection to remote resource. After you extend this class you’ll override the doInBackground method that receives the array of arguments. This method will proxy your request to specific web service call.

The way you need to use Services class is like this:

    //Add ToDoItem
    public String addToDoItem(ToDoItemModel toDoItem) {
        String status = null;
        try {
            Services svc = new Services();
            status = svc.execute("/AddToDoItem", toDoItem).get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return status;
    }

where status represents the return value of method. It can also be a JSON result for GetToDoItemsByDate and GetAllToDoItems call.

Do not use this:

status = svc.addToDoItem("/AddToDoItem", toDoItem);

Instead use execute() which calls doInBackground method from Services class.