|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI was really fascinated by the fantastic article I came across on the web. The article "Building Layered Web Applications with Microsoft ASP.NET 2.0" by "Imar Spaanjars" Building Layered Web Applications with Microsoft ASP.NET 2.0 helped me come up with my own four-tier, web-based Architecture using Ajax. While Imar's examples were made easy for the readers, I extended the BO (Presentation), BLL (Business Logic Layer), and the DAL (Data Access Layer) into Class Library projects and wrapped the entire application in a more structured way. The purpose of this application is to implement the four-tier architecture and demonstrate the running of the application using a single housekeeping form. Let us assume we have a database named "ACCOUNTS". This database contains a table Named "AccountTypes". In the below examples I will demonstrate:
BackgroundI recommend you to go through Imar's Article before going through my illustration. Building Layered Web Applications with Microsoft ASP.NET 2.0 Using the codePhase II will start off by creating a new Class Library Project for the Business Object (BO).
Below is the code for the class
using System;
using System.Collections.Generic;
using System.Text;
namespace BO
{
public class AccountTypes
{
#region Private Variables
private string accType = String.Empty;
private int accTypeId = -1;
#endregion
#region Public Properties
public int AccountTypeId
{
get
{
return accTypeId;
}
set
{
accTypeId = value;
}
}
public string AccountType
{
get
{
return accType;
}
set
{
accType = value;
}
}
#endregion
}
}
Below is the code for the class
using System;
using System.Collections.Generic;
using System.Text;
namespace BO
{
public class AccountTypesList : List<AccountTypes>
{
public AccountTypesList()
{ }
}
}
Phase III will now create a Class Library Project for the Data Access Layer (DAL).
Below is the code for the class
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace DAL
{
class Database
{
/// <summary>
/// Summary description for Database.
/// </summary>
#region Private Variables
private string ConnectionString = string.Empty;
private SqlConnection oConnection = null;
private SqlCommand oCommand = null;
#endregion
#region Public Methods
public Database()
{
ConnectionString = GetDbConnectionString();
}
public static string GetDbConnectionString()
{
string DbServer =
ConfigurationSettings.AppSettings["DatabaseServer"].ToString();
string DbName =
ConfigurationSettings.AppSettings["Database"].ToString();
string UserId =
ConfigurationSettings.AppSettings["DbUserId"].ToString();
string Password =
ConfigurationSettings.AppSettings["DbPassword"].ToString();
string ConnectionString = "Data Source=" + DbServer + ";" +
"Initial Catalog=" + DbName + ";" + "User ID=" + UserId + ";" +
"Password=" + Password;
return ConnectionString;
}
public bool GetDBReader(string SQL, out SqlDataReader SqlDataReader)
{
SqlDataReader = null;
if ((SQL == null) || (SQL == ""))
return false;
oConnection = new SqlConnection(ConnectionString);
oCommand = new SqlCommand(SQL, oConnection);
oConnection.Open();
SqlDataReader =
oCommand.ExecuteReader(CommandBehavior.CloseConnection);
return SqlDataReader.HasRows;
}
public void DisposeDBReader(SqlDataReader oReader)
{
if (oReader != null)
{
if (!oReader.IsClosed)
oReader.Close();
}
if (oCommand != null)
{
oCommand.Dispose();
}
if (oConnection != null)
{
if (oConnection.State != ConnectionState.Closed)
oConnection.Close();
oConnection.Dispose();
}
}
public int ExecuteQuery(string sSQL)
{
SqlConnection oConn = new SqlConnection(ConnectionString);
SqlCommand oComm = new SqlCommand(sSQL, oConn);
int iRecordsAffected = 0;
try
{
oConn.Open();
iRecordsAffected = oComm.ExecuteNonQuery();
oConn.Close();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (oCommand != null)
{
oCommand.Dispose();
}
if (oConnection != null)
{
if (oConnection.State != ConnectionState.Closed)
oConnection.Close();
oConnection.Dispose();
}
}
return iRecordsAffected;
}
#endregion
}
}
Below is the code for the class
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using BO;
namespace DAL
{
public class AccountTypesDB
{
#region Private Methods
private static AccountTypes FillDataRecord(IDataRecord myDataRecord)
{
AccountTypes accountType = new AccountTypes();
accountType.AccountTypeId =
myDataRecord.GetInt32(
myDataRecord.GetOrdinal("AccountTypeId"));
if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("AccountType")))
{
accountType.AccountType = myDataRecord.GetString(
myDataRecord.GetOrdinal("AccountType"));
}
return accountType;
}
// Function: FillGridData
// Description: This function is used to create a 2D array to pass data
// to the client side
// Array Format: [[a1,a2,...],
Phase IIIFinally, I will create a Class Library Project for the Business Logic Layer (BLL).
Below is the code for the class
using System;
using System.Collections.Generic;
using System.Text;
using BO;
using DAL;
namespace BLL
{
public class AccountTypesManager
{
#region Private Methods
#endregion
#region Public Methods
public static AccountTypes GetAccountType(int accountTypeId)
{
return AccountTypesDB.GetAccountType(accountTypeId);
}
public static AccountTypes GetGridData(string SQL)
{
return AccountTypesDB.GetGridData(SQL);
}
public static AccountTypesList GetAccountTypeList()
{
return AccountTypesDB.GetAccountTypeList();
}
public static int SaveAccountType(AccountTypes accounts)
{
ValidateSaveAccountType(accounts);
accounts.AccountTypeId = AccountTypesDB.SaveAccountType(accounts);
return accounts.AccountTypeId;
}
public static void ValidateSaveAccountType(AccountTypes Accounts)
{
if (String.IsNullOrEmpty(Accounts.AccountType))
{
throw new Exception("Cannot save an Account Type without a
valid property.");
}
}
public static bool ValidateSaveAccountType(AccountTypes Accounts,
bool throwError)
{
if (String.IsNullOrEmpty(Accounts.AccountType))
{
if (throwError == true)
throw new Exception("Cannot save an Account Type without a
valid property.");
else
return false;
}
return true;
}
#endregion
}
}
Phase IVNote: Phase IV can be skipped if you are interested in implementing Ajax in your application. Phase IV shows the basic usage of the functionality through code behind files. Now that our three-tier architecture is ready, we only have to see the implementation and how things work using the below:
Below is the code for the Page Default.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="Accounts._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Accounts</title>
<script type='text/javascript' language="javascript">
function fnBindGrid()
{
// The SQL Query used here is only for demonstration purpose. It is not a
// good idea to have the SQL queries on the client side.
PostXmlHttp('AjaxAccess.asmx/WsBindGrid', 'CBBindGrid',
'SQL=SELECT * FROM ACCOUNTTYPES');
}
function CBBindGrid()
{
//State Description
//0 The request is not initialized
//1 The request has been set up
//2 The request has been sent
//3 The request is in process
//4 The request is complete
try
{
if (xmlHttp.readyState==4)
{
// The Invisible Div rendered while calling the Ajax Post or Get
// method is hidden so that the user can proceed with other clicks.
document.all.item("HiddenDiv").style.display = "none";
ParseXml(xmlHttp.responseText);
if(returnParseXml != null)
{
if(Trim(returnParseXml.childNodes[0].nodeValue) == 'Success')
alert('Saved Successfully');
else
{
var myData = [,];
myData = eval(returnParseXml.childNodes[0].nodeValue);
var htmlTable;
if(myData.length > 0)
{
var rows = myData.length;
var columns = myData[0].length;
//loop through the rows
htmlTable = '<TABLE>';
for( i = 0 ; i < rows ; i++)
{
htmlTable += '<TR>';
//loop through the columns
for( j = 0 ; j < columns ; j++ )
{
htmlTable += '<TD>';
htmlTable += myData[i][j];
htmlTable += '</TD>';
//alert(myData[i][j]);
}
htmlTable += '</TR>';
}
htmlTable += '</TABLE>';
CustTable.innerHTML = htmlTable;
}
}
}
else
alert('Failed: Return XML not found');
}
}
catch(e)
{
alert(e.Message);
}
}
function fnSaveAccountType()
{
PostXmlHttp('AjaxAccess.asmx/WsSaveAccountType', 'CBSaveAccountType',
'AccountType='+Trim(document.all.item("txtAccountType").value));
}
function CBSaveAccountType()
{
//State Description
//0 The request is not initialized
//1 The request has been set up
//2 The request has been sent
//3 The request is in process
//4 The request is complete
try
{
if (xmlHttp.readyState==4)
{
// The Invisible Div rendered while calling the Ajax Post or Get
// method is hidden so that the user can proceed with other clicks.
document.all.item("HiddenDiv").style.display = "none";
ParseXml(xmlHttp.responseText);
if(returnParseXml != null)
{
if(Trim(returnParseXml.childNodes[0].nodeValue) == 'Success')
{
alert('Saved Successfully');
fnBindGrid();
}
else
alert(returnParseXml.childNodes[0].nodeValue);
}
else
alert('Failed: Return XML not found');
}
}
catch(e)
{
alert(e.Message);
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="gvAccountTypes" runat="server"></asp:GridView>
<div id='CustTable'></div>
<input id="txtAccountType" type="text" />
<input id="Button1" type="button" value="Save"
onclick="fnSaveAccountType()" />
</div>
</form>
</body>
</html>
Below is the code for the Class Default.aspx.cs:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using BO;
using BLL;
namespace Accounts
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//Common.Script.RenderCommonScript(Page, "CommonScript");
//Common.Script.RenderAjaxScript(Page,"AjaxScript");
gvAccountTypes.DataSource =
AccountTypesManager.GetAccountTypeList();
gvAccountTypes.DataBind();
}
}
}
Phase VI added a common webservice file to the project. I name this as AjaxAccess.asmx.cs
Below is the code for the class
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using BO;
using BLL;
namespace Accounts
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Common.Script.RenderCommonScript(Page, "CommonScript");
Common.Script.RenderAjaxScript(Page,"AjaxScript");
//Binding data using our custom list class from codebehind.
//gvAccountTypes.DataSource =
// AccountTypesManager.GetAccountTypeList();
//gvAccountTypes.DataBind();
}
}
}
Below is the code for the class
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace Accounts
{
public class Common
{
public Common()
{
//
// TODO: Add constructor logic here
//
}
public class Script
{
public Script()
{
}
#region Render Common Script
public static void RenderCommonScript(System.Web.UI.Page oPage,
string ScriptName)
{
oPage.RegisterStartupScript(ScriptName, @"
<script type='text/javascript' language="'javascript'">
function Trim(StringValue)
{
try
{
if (StringValue == null)
return '';
//Remove leading spaces, tabs and carriage
//returns
while ((StringValue.substring(0, 1) == ' ') ||
(StringValue.substring(0, 1) == '\n') ||
(StringValue.substring(0, 1) == '\t') ||
(StringValue.substring(0, 1) == '\r'))
{
StringValue = StringValue.substring(1,
StringValue.length);
}
//Remove trailing spaces, tabs and carriage
//returns
while ((StringValue.substring(
StringValue.length-1,
StringValue.length) == ' ') ||
(StringValue.substring(
StringValue.length-1,
StringValue.length) ==
'\n') || (StringValue.substring(
StringValue.length-1, StringValue.length)
== '\t') || (StringValue.substring(
StringValue.length-1,
StringValue.length) == '\r'))
{
StringValue = StringValue.substring(0,
StringValue.length-1);
}
}
catch(e)
{
alert('Error Occured while Triming the
String:\n' + e.message);
}
return StringValue;
}
</script>");
}
# endregion
#region Render AJAX SCRIPT
public static void RenderAjaxScript(System.Web.UI.Page oPage,
string ScriptName)
{
string renderAjaxScript = string.Empty;
renderAjaxScript = @"
// This is an Invisible Div Rendered on the page in order to
// avoid user input while the page is busy retriving data.
// This will be shown and hidden on the page consecutively as
// and when required.
<div id='HiddenDiv' style='DISPLAY: none; LEFT: 0px;
POSITION: absolute; TOP: 0px; WIDTH=100%; HEIGHT=100%'>
<table align='center' width='100%' height='100%'
cellpadding='0' cellspacing='0'>
<tr>
<td width='100%' height='100%' align='center'
valign='middle'><IMG alt='' src='";
// The PreLoaderImage tag in the webconfig holds the
// Physical path of the preloader image.
// The loading image is usually displayed while the
// webservice is busy retriving the data from the
// database.
renderAjaxScript = renderAjaxScript +
ConfigurationSettings.AppSettings[
"PreLoaderImage"].ToString();
renderAjaxScript = renderAjaxScript + @"'>
</td>
</tr>
</table>
</div>";
oPage.Response.Write(renderAjaxScript);
oPage.RegisterStartupScript(ScriptName, @"
<script type='text/javascript' language="'javascript'">
var xmlHttp;
function PostXmlHttp(URL, CallBackFunction, Parameters)
{
xmlHttp=null;
try
{ // Firefox, Opera 8.0+, Safari
xmlHttp=new XMLHttpRequest();
}
catch (e)
{ // Internet Explorer
try
{
xmlHttp=new
ActiveXObject(
'Msxml2.XMLHTTP');
}
catch (e)
{
try
{
xmlHttp=new
ActiveXObject(
'Microsoft.XMLHTTP');
}
catch (e)
{
alert('Your browser
does not support
AJAX!');
return;
}
}
}
try
{
if (xmlHttp!=null)
{
xmlHttp.onreadystatechange =
eval(CallBackFunction);
}
if(document.all.item('HiddenDiv'))
{
// The Invisible Div appears on
// the user screen to avoid
// extra clicks by the user.
document.all.item(
'HiddenDiv').style.display = '';
}
xmlHttp.open('POST',URL,true);
xmlHttp.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded;
charset=UTF-8');
xmlHttp.send(Parameters);
}
catch(e)
{
alert('Error Occured while Posting
XmlHttp Values:\n' + e.message);
}
}
function GetXmlHttp(URL, CallBackFunction, Parameters)
{
xmlHttp=null;
try
{ // Firefox, Opera 8.0+, Safari
xmlHttp=new XMLHttpRequest();
}
catch (e)
{ // Internet Explorer
try
{
xmlHttp=new ActiveXObject(
'Msxml2.XMLHTTP');
}
catch (e)
{
try
{
xmlHttp=new
ActiveXObject(
'Microsoft.XMLHTTP');
}
catch (e)
{
alert('Your browser
does not support
AJAX!');
return;
}
}
}
try
{
if (xmlHttp!=null)
{
xmlHttp.onreadystatechange =
eval(CallBackFunction);
}
if(document.all.item('HiddenDiv'))
{
// The Invisible Div appears on
// the user screen to avoid
// extra clicks by the user.
document.all.item(
'HiddenDiv').style.display = '';
}
xmlHttp.open('POST',URL,true);
xmlHttp.send(null);
}
catch(e)
{
alert('Error Occured while Getting
XmlHttp Values:\n' + e.message);
}
}
var returnParseXml
function ParseXml(text)
{
// code for IE
if (window.ActiveXObject)
{
var doc=new
ActiveXObject('Microsoft.XMLDOM');
doc.async='false';
doc.loadXML(text);
}
// code for Mozilla, Firefox, Opera, etc.
else
{
var parser=new DOMParser();
var doc=parser.parseFromString(
text,'text/xml');
}// documentElement always represents the root
// node
returnParseXml = doc.documentElement;
}
</script>");
}
# endregion
}
}
}
Below is the code for AjaxAccess.asmx.cs:
using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.ComponentModel;
using BLL;
using BO;
namespace Accounts
{
/// <summary>
/// Summary description for AjaxAccess
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class AjaxAccess : System.Web.Services.WebService
{
[WebMethod]
public string WsSaveAccountType(string AccountType)
{
try
{
AccountTypes oAccountTypes = new AccountTypes();
oAccountTypes.AccountType = AccountType;
AccountTypesManager.SaveAccountType(oAccountTypes);
return "Success";
}
catch (Exception ex)
{
return "Failed: " + ex.Message;
}
}
[WebMethod]
public string WsBindGrid(string SQL)
{
try
{
return AccountTypesManager.GetGridData(SQL).GridData;
}
catch (Exception ex)
{
return "Failed: " + ex.Message;
}
}
}
}
Web Config Entries
<appSettings>
<add key="DatabaseServer" value="(local)" />
<add key="Database" value="ACCOUNTS" />
<add key="DbUserId" value="sa" />
<add key="DbPassword" value="sa" />
<add key="PreLoaderImage" value="Images/loading.gif" />
</appSettings>
Points of InterestWhile retriving data to fill up the table dynamically, I was creating a two dimensional array (basically, its a string representing the array structure) and transporting it to the client side using a webservice. Upon reaching the client side, JavaScript would interpret this as only string and not array. I went crazy trying to find the problem. I found this really cool key word in JavaScript: "eval". Very Handy to use. eval did the trick for me. The eval() function evaluates a string and executes it as if it was script code.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||