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.
Step 01: Select Azure project
|
Step 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:
- AddToDoItem – will be used for adding a new ToDo item. It will receive a ToDo item in JSON format (RequestFormat), through POST request.
- 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).
- 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.