Web Service : Asynchronous call by using Java Script and DHTML behaviour

Scenario:

In your web application data entry form, suppose you have a drop-down list from which user has to select an option, and on the basis of his/her selection you want to populate other drop-down list or field ( just like on selection of one category name from drop-down list you want to show/populate related products in other drop-down list ).

Calling web service using java script reduces post back and server round trip.

In this example we will access web service written in C# from client side by using java script and Web Service behaviour. I have used Categories, Products and Suppliers tables of Northwind database supplied by Microsoft with MS Access or Visual Studio.

Requirements:

  1. Data Layer : defines functions to retrieve data from database [Optional : you can directly access database from the web method]
  2. Web Service: defines web method that can be accessed across the internet.
  3. Java Script: defines functions to call web method.
  4. webservice.htc file, and: It is an html component file which encapsulates web service behaviour, that can be attached with any form elements using the style attribute. The web service behaviour helps you to use web service without the knowledge of SOAP and, enables client-side script to access a Web Service without navigating to another URL. It provides you to call web method, synchronously and asynchronously. The default mode of remote method call is asynchronous.
  5. HTML form: to show the  result of the web method. 
  6. .Net Framework 1.0

Data Layer Class: This class has three main functions : GetAllCategories, GetProducts and GetSuppliers.

The GetAllCategories function retrieves categoryid and categoryname of all categories from Category Table. This function is used to populated category drop-down list in page load event.

The GetProducts function retrieves productid and productname of all the products of specific category from Products Table.

The GetSuppliers function retrieves supplierid and companyname of supplier of specific product from suppliers table.

// DataLayer.cs
using System;
using System.Data ;
using System.Data.SqlClient ;
namespace JSWebService
{
/// <summary>
/// Summary description for DataLayer.
/// </summary>
public class DataLayer
{
private static SqlConnection _objConnection;
private static SqlDataAdapter _objDataAdapter;
private static string strConnectionString;
private static void OpenConnection()
{
try
{
if ( _objConnection == null)
{
//Change connection string as per your requirement.
strConnectionString = "server=avi; uid=sa; pwd=; database=northwind";
_objConnection =
new SqlConnection (strConnectionString);
_objConnection.Open ();
}
}
catch ( Exception e)
{
throw new Exception ("Error in opening connection : " + e.Message );
}
}
private static void CloseConnection()
{
try
{
if ( _objConnection != null)
{
_objConnection.Close ();
_objConnection =
null;
}
}
catch (Exception e)
{
throw new Exception ("Error in closing connection : " + e.Message );
}
}
public static void GetAllCategories(DataSet p_DataSetCategory)
{
try
{
OpenConnection();
_objDataAdapter =
new SqlDataAdapter ("select categoryID, categoryName from categories", _objConnection);
_objDataAdapter.Fill (p_DataSetCategory,"Categories");
_objDataAdapter =
null;
CloseConnection();
}
catch (Exception e)
{
throw new Exception ("Error :: " + e.Message );
}
}
public static void GetProducts(DataSet p_DataSetProducts,int p_intCategoryID)
{
try
{
OpenConnection();
_objDataAdapter =
new SqlDataAdapter ("select productID,productName from products where categoryID = " + p_intCategoryID , _objConnection);
_objDataAdapter.Fill (p_DataSetProducts,"Products");
_objDataAdapter =
null;
CloseConnection();
}
catch (Exception e)
{
throw new Exception ("Error :: " + e.Message );
}
}
public static void GetSuppliers(DataSet p_DataSetSuppliers,int p_intProductID)
{
try
{
OpenConnection();
_objDataAdapter =
new SqlDataAdapter ("select p.SupplierID,s.CompanyName from products p,suppliers s where p.SupplierId = s.SupplierId and p.ProductId = " + p_intProductID , _objConnection);
_objDataAdapter.Fill (p_DataSetSuppliers,"Suppliers");
_objDataAdapter =
null;
CloseConnection();
}
catch (Exception e)
{
throw new Exception ("Error :: " + e.Message );
}
}
}
}

WebService Class: The web service class has three web methods : GetProductsForCategoryId  and GetSuppliersForProductId.

The GetProductsForCategoryId method calls GetProducts function of datalayer to retrieve records of specified categoryId. It stores result in an array of string and returns the same to callback function.

The GetSuppliersForProductId method calls GetSuppliers function of datalayer to retrieve records of specified productId. It stores result in an array of string and returns the same to callback function.

// ProductWebService.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient ;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using DataLayer;
using System.Xml ;
namespace JSWebService
{
/// <summary>
/// Summary description for ProductWebService.
/// </summary>
public class ProductWebService : System.Web.Services.WebService
{
public ProductWebService()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}
[WebMethod]
public string[] GetSuppliersForProductId(int p_intProductId)
{
DataSet _dsSuppliers;
_dsSuppliers =
new DataSet ();
DataLayer.GetSuppliers ( _dsSuppliers,p_intProductId);
if (_dsSuppliers.Tables[0].Rows.Count > 0)
{
int _recordCount = _dsSuppliers.Tables[0].Rows.Count*2 ;
string[] _productsList = new string [_recordCount];
_recordCount = 0;
foreach(DataRow _drSupplier in _dsSuppliers.Tables[0].Rows )
{
_productsList[_recordCount] = _drSupplier[0].ToString ();
_recordCount += 1;
_productsList[_recordCount] = _drSupplier[1].ToString ();
_recordCount += 1;
}
return _productsList;
}
else
{
return null;
}
}
// this web method that will return list suppliers related to
// specified productId, in the form of array of string
[WebMethod(true)]
public String[] GetProductsForCategoryId(int p_intCategoryID)
{
DataSet _dsProducts;
_dsProducts =
new DataSet ();
DataLayer.GetProducts ( _dsProducts,p_intCategoryID);
if (_dsProducts.Tables[0].Rows.Count > 0)
{
int _recordCount = _dsProducts.Tables[0].Rows.Count*2 ;
string[] _productsList = new string [_recordCount];
_recordCount = 0;
foreach(DataRow _drProduct in _dsProducts.Tables[0].Rows )
{
_productsList[_recordCount] = _drProduct[0].ToString ();
_recordCount += 1;
_productsList[_recordCount] = _drProduct[1].ToString ();
_recordCount += 1;
}
return _productsList;
}
else
{
return null;
}
}
#region Component Designer generated code
//Required by the Web Services Designer
private IContainer components = null;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
}
}

Java Script: This java script external file contains functions to call web method synchronously and to access result delivered by the web method in a callback function.

//Products.js
window.onload= init;
function init()
{
this.proxy = document.getElementById("Service");
/* The useService method establishes a friendly name for a Web Service URL, which can then be referenced from script.
Syntax :
sElementID.useService(<strWebServiceURL>, <strFriendlyName> [ ,oUseOptions])
Parameters :
<sElementID> The id of the element to which the WebService behavior is attached.
<sWebServiceURL> String representing the URL of the Web Service( eg : http://avisan.com/product.asmx?WSDL )
<strFriendlyName> String denoting a friendly name for the Web Service URL.
*/
this.proxy.useService("ProductWebService.asmx?WSDL","ProductService");
this.oSelectCategory = document.getElementById("cmbCategory");
this.oSelectProduct = document.getElementById("cmbProduct");
this.oSelectSupplier = document.getElementById("cmbSupplier");
this.oSelectProduct.disabled = true;
this.oSelectSupplier.disabled = true;
}
function getSuppliers()
{
if ( this.oSelectProduct.selectedIndex < 1 )
{
this.oSelectSupplier.disabled = true;
return false;
}
var intProductId = this.oSelectProduct.value;
/* The callService method invokes the specified web method which implemented on a web service. The first parameter is the name of callback function which will be used to access the result returned by the web method. The second parameter is the name of the web method of the called web service. The third parameter is value that is to be passed to the web method's parameter.
Important Note : When the callService method invokes synchronously the return value of the web method is a result object and while invokes asynchronously , the return value of the web method is an Integer which is a unique identifier for the instance of the method call.
Syntax :
iCallID = sElementID.sFriendlyName.callService( [callBackFunctonName], <strWebMethodNamet>, <webMethodParams>)
Parameter :
[callBackFunctionName] : A function to process the result of method call. The result object is passed as the first parameter of the callback function.
<strWebMethodName> : Name of the web method defined in WebService class, that is to be invoked/called.
<webMethodParams> : Values passed to the parameters of a web method.
*/
oResult = this.proxy.ProductService.callService(fillSuppliersList,"GetSuppliersForProductId", intProductId);
}
/* The fillSuppliersList is a callback function that access the result returned from the web method and populates the same in the drop-down list. */
function fillSuppliersList(oResult)
{
if (!oResult.error && oResult != null)
{
var pList = new Array();
pList = oResult.value;
var oSelectSupplier = document.getElementById("cmbSupplier");
oSelectSupplier.options.length = 0;
oSelectSupplier.options[0] =
new Option("[Select Supplier]",0)
for (var i = 0; i< pList.length ; )
{
oSelectSupplier.options[oSelectSupplier.options.length] =
new Option(pList[i+1],pList[i]);
i +=2;
}
oSelectSupplier.selectedIndex = 0;
oSelectSupplier.disabled =
false;
}
else
{
var faultCode = oResult.errorDetail.code;
var faultString = oResult.errorDetail.string;
var faultSOAP = oResult.errorDetail.raw;
alert("Error :: " + faultCode + faultString + faultSOAP)
}
}
function getProducts()
{
if ( this.oSelectCategory.selectedIndex < 1 )
{
this.oSelectSupplier.disabled = true
this.oSelectProduct.disabled = true;
return false;
}
var intCategoryId = this.oSelectCategory.value;
oResult =
this.proxy.ProductService.callService(fillProductsList,"GetProductsForCategoryId", intCategoryId);
}
function fillProductsList(oResult)
{
if (!oResult.error && oResult != null)
{
var pList = new Array();
pList = oResult.value;
var oSelectProduct = document.getElementById("cmbProduct");
oSelectProduct.options.length = 0;
oSelectProduct.options[0] =
new Option("[Select Product]",0);
var intIndex = 1;
var strTag = "";
for (var i = 0; i< pList.length ; )
{
var oOption = new Option(pList[i+1]);
oOption.value = pList[i];
oSelectProduct.options[oSelectProduct.options.length] = oOption;
intIndex += 1;
i +=2;
}
oSelectProduct.selectedIndex = 0;
oSelectProduct.disabled =
false;
}
else
{
var faultCode = oResult.errorDetail.code;
var faultString = oResult.errorDetail.string;
var faultSOAP = oResult.errorDetail.raw;
alert("Error :: " + faultCode + faultString + faultSOAP);
}
}

Code Behind File: In code behind we populates the category drop-down list on page load event by checking postback.

// SyncCall.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient ;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using DataLayer;
namespace JSWebService
{
/// <summary>
/// Summary description for AsyncCall.
/// </summary>
public class SyncCall : System.Web.UI.Page
{
protected System.Web.UI.HtmlControls.HtmlSelect cmbCategory;
protected System.Web.UI.HtmlControls.HtmlSelect cmbProduct;
protected System.Web.UI.HtmlControls.HtmlSelect cmbSupplier;
private void Page_Load(object sender, System.EventArgs e)
{
if (! Page.IsPostBack )
{
/* populates category drop-down list if it is the first request for the page. */
FillCategories();
}
// Put user code to initialize the page here
}
private void FillCategories()
{
try
{
DataSet _dsCategories;
_dsCategories =
new DataSet ();
DataLayer.GetAllCategories ( _dsCategories);
if (_dsCategories.Tables[0].Rows.Count > 0)
{
cmbCategory.DataSource = _dsCategories;
cmbCategory.DataTextField = "categoryName";
cmbCategory.DataValueField = "categoryID";
cmbCategory.DataBind ();
cmbCategory.Items.Insert (0,"[Select Category]");
/* Add attribute to category and product drop-down list so as to call javascript function on item change event. [It is required only if you are using server control, in case of HTML control you can directly specify inside the tag.]*/
cmbCategory.Attributes.Add ("onchange","getProducts()");
cmbProduct.Attributes.Add ("onchange","getSuppliers()");
}
}
catch (Exception e)
{
Response.Write (e.Message );
}
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
}