Personal Web & Sample Site
Skip Navigation Links
HOME |
About Me |
Articles
| Contact Us
Multi-tier and Mult-layer Web Application Design
Rating: 54 user(s) have rated this article Average rating: 5.0
Posted by: joet, on 9/22/2011, in category "ASP.NET"
Views: this article has been read 2629 times
Location: Houston, Tx, United States
Abstract: These two terms, multi-tier and multi-layer, actually have a subtle difference between them: Multi-tier is about physical separation and units of deployment, but multi-layers are about logical separation and units of design. I want to talk about these differences and show examples but I also want to discuss the many differences in developing a typical ASP.Net web application versus having to develop a web part or provide a custom solution in a SharePoint environment. Dataset vs Entities, when creating a webpart in SharePoint which technique is used? Do you know the difference?

 

This site will contain a variety of different things, but mainly I want to demonstrate the n-tier, multi-tier, or multi-layered infrastructure for web development I've used on this site. I also want to demonstrate the use of ASP.Net controls from the Visual Studio tool box and I also want to demonstrate programmatically creating some of those controls as well. These two terms, multi-tier and multi-layer, actually have a subtle difference between them: Multi-tier is about physical separation and units of deployment, but multi-layers are about logical separation and units of design. I want to talk about these differences and show examples but I also want to discuss the many differences in developing a typical ASP.Net web application versus having to develop a web part or provide a custom solution in a SharePoint environment. Dataset vs Entities, when creating a webpart in SharePoint which technique is used? Do you know the difference?

I also want to demonstrate other features and techniques such as masterpages, branding, user controls, membership & roles, Jquery, and ajax, and many other topics. For example, this site is done using CSS and there are no tables used for positioning elements on the page except for tabular data and most of the time I'll be using a data control e.g. GridView, ListView, or DataList or even programmatically creating the tables.

Let us proceed with the first topic and thats building out the data access layer (DAL) using datasets. I am using the Northwind sample database on a MS SQL 2008 server. I went through the trouble of re-creating the tables myself so that I could get more familliar with the configuration of the tables, relationships, and stored procedures. First, I dropped a dataset onto the designer surface. Visual Studio makes it easy to create the DAL by dropping the required controls that make up the dataset. A typed dataset serves as a strongly typed collection of data; in other words its comprised of a collection of strongly-typed data tables instances, each of which is in turn composed of strongly-typed dataRow instances. I will create a strongly-typed data table for each of the tables in my Northwind database I plan on using to present data in this site. Each one of these data tables is actually a table adapter class that functions as my data access layer consisting of various methods that are invoked from the presentation layer as objects.

So, after creating the first data table in my data set the first method I created was a simple select statement that I named "GetProducts". Here is the SQL sytax used:

 

Simple Select Statement

 

Now, before I move on, I am leaving out some important things here. First, the masterpage, navigation, breadcrumb, and membership & roles. This is not a tutorial but a demonstration of some of the things I've done so all I'm doing is touching on the main steps and adding comments describing some of those steps and sometimes adding explainations on the methodologies used to create these examples. But wait, there is also a very important issue that I need to discuss and often a lengthy topic of debate among architects and developers. This is the question of using datasets vs custom entities, or domain objects, and here lies the key in using datasets for a typical web application and custom entities for a web part thats pulling data from an external data source. Each has it's pros and cons but when using a dataset and passing data between the DAL and BLL layers, you will be accessing the data in the BLL using the ADO.Net methods. Using custom entities, all the data is wrapped in custom classes and collections of classes, so you would access data in the BLL in a more natural manner that has been customized for the particular data in question.

So my complete data set looks like this:


Complete DataSet

 

Keep in mind I've added many sub-queries and I have also modified the insert statement to include "SELECT SCOPE_IDENTITY()" in lieu of the default "@@IDENTITY". Go here to learn more.ve on to the business logic layer and allow me to show you how I configured this important part of a three tier architecture.

We now have a connection to the data store thats done through the use of a dataset which cleanly seperates the data access logic from the presentation logic. At this point all we have to do is write a few lines of code in the code-beside's page load event and we can pull whatever data we want within the parameters of the methods we've created in the dataset. Great, but what about logic? This is where the business logic layer comes into play. In Marco Bellinaso's" book entitled "ASP.NET 2.0 Website Programming", he recommends the base class approach, and for many reasons I'm inclined to agree with this approach. Create your namespace and base class for all pages in your web application to inherit from at the beginning of your project. This is crucial and can save you alot of heart ache down the road and of course allow you to make code modifications to all of your pages at the same time. So I will create a class file and name it "BasePage" and it will look like this:

*** The following code is based on Marco Bellinaso's book ASP.NET 2.0 Website Programming ***

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using jtwebnet;
using jtwebnetTableAdapters;

public class BasePage : System.Web.UI.Page
{
    protected override void OnPreInit(EventArgs e)
    {
        base.OnPreInit(e);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
    }
}

 

I will then change my default page code-behind to this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : BasePage
{
   
}

Actually the namespace is also changed on both the page and it's code-behind. I've also added the required using statements as well like this:

 

// The UI page
This is added to the top of the UI page //*** Inherits="jt.jtwebnet.UI.Default" %> ***//


// The code-behind now looks like this:


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 jt.jtwebnet;
using jt.jtwebnet.UI.BLL;

namespace jt.jtwebnet.UI
{
    public partial class Default : BasePage
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            this.Master.EnablePersonalization = true;
        }
    }
}

As for my business logic layer there is a class file for each of the data tables I created within the dataset but the one I will show here is for the products table:

 


*** The following code is based on the Scot Mitchell Tutorials @ www.asp.net ***
 
 
 

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;

using jtwebnetTableAdapters;

[System.ComponentModel.DataObject] public class ProductsBLL

{

private ProductsTableAdapter _productsAdapter = null;

protected ProductsTableAdapter Adapter

{ get

{ if (_productsAdapter == null) _productsAdapter = new ProductsTableAdapter();

return _productsAdapter; }

}

 

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, true)]

public jtwebnet.ProductsDataTable GetProducts()

{

return Adapter.GetProducts();

}

 

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)]

public jtwebnet.ProductsDataTable GetProductByProductID(int productID)

{

return Adapter.GetProductByProductID(productID);

}

 

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)]

public jtwebnet.ProductsDataTable GetProductsByCategoryID(int categoryID)

{ return Adapter.GetProductsByCategoryID(categoryID);

}

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)]

public jtwebnet.ProductsDataTable GetProductsBySupplierID(int supplierID)

{

return Adapter.GetProductsBySupplierID(supplierID);

} [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Insert, true)]

public bool AddProduct(string productName, int? supplierID, int? categoryID, string quantityPerUnit, decimal? unitPrice, short? unitsInStock, short?

unitsOnOrder, short? reorderLevel, bool discontinued)

{ // Create a new ProductRow instance

jtwebnet.ProductsDataTable products = new jtwebnet.ProductsDataTable();

jtwebnet.ProductsRow product = products.NewProductsRow();

product.ProductName = productName; if (supplierID == null) product.SetSupplierIDNull();

else

product.SupplierID = supplierID.Value;

if (categoryID == null) product.SetCategoryIDNull();

else product.CategoryID = categoryID.Value;

 

if (quantityPerUnit == null) product.SetQuantityPerUnitNull();

else product.QuantityPerUnit = quantityPerUnit;

 

if (unitPrice == null) product.SetUnitPriceNull();

else product.UnitPrice = unitPrice.Value;

 

if (unitsInStock == null) product.SetUnitsInStockNull();

else product.UnitsInStock = unitsInStock.Value;

 

if (unitsOnOrder == null) product.SetUnitsOnOrderNull();

else product.UnitsOnOrder = unitsOnOrder.Value;

 

if (reorderLevel == null) product.SetReorderLevelNull();

else product.ReorderLevel = reorderLevel.Value; product.Discontinued = discontinued;

// Add the new product

products.AddProductsRow(product); int rowsAffected = Adapter.Update(products);

// Return true if precisely one row was inserted,

// otherwise false

return rowsAffected == 1;

}

 

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, true)]

public bool UpdateProduct(string productName, int? supplierID, int? categoryID, string quantityPerUnit, decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short?

reorderLevel, bool discontinued, int productID)

{

jtwebnet.ProductsDataTable products = Adapter.GetProductByProductID(productID);

if (products.Count == 0)// no matching record found,

return false return false;

jtwebnet.ProductsRow product = products[0];

product.ProductName = productName;

if (supplierID == null) product.SetSupplierIDNull();

else

product.SupplierID = supplierID.Value;

 

if (categoryID == null) product.SetCategoryIDNull();

else product.CategoryID = categoryID.Value;

 

if (quantityPerUnit == null) product.SetQuantityPerUnitNull();

else product.QuantityPerUnit = quantityPerUnit;

 

if (unitPrice == null) product.SetUnitPriceNull();

else product.UnitPrice = unitPrice.Value;

 

if (unitsInStock == null) product.SetUnitsInStockNull();

else product.UnitsInStock = unitsInStock.Value;

 

if (unitsOnOrder == null) product.SetUnitsOnOrderNull();

else product.UnitsOnOrder = unitsOnOrder.Value;

 

if (reorderLevel == null) product.SetReorderLevelNull();

else product.ReorderLevel = reorderLevel.Value;

product.Discontinued = discontinued; // Update the product record

int rowsAffected = Adapter.Update(product); // Return true if precisely one row was updated, // otherwise false

return rowsAffected == 1; }

 

[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Delete, true)]

public bool DeleteProduct(int productID)

{ int rowsAffected = Adapter.Delete(productID); // Return true if precisely one row was deleted,

// otherwise false

return rowsAffected == 1;

}

}

 

So this brings me to the way I have all of these files organized under my root site folder. When you first create the dataset in visual studio it will alert you about creating a App_Code folder. This is a special folder that will contain all of your classes that get compiled on the fly, a process called edit and continue. Make a change to one of the classes and refresh the page and it automatically compiles the code making it easier and faster to test and debug your web pages. Pretty cool!

This is what my App_Code folder looks like:

JT Web Net App_Code Folder

 

Working with SharePoint I'm often asked to pull data into a web part from an external data store. How would you do that? I will say this much, I don't use ADO.Net like I've seen some ASP.Net web developers because this is bad practice. Have you heard of CAML or better yet, LINQ?

Thats it for now. Check out the other links and take a look at some other examples. This is a work in progress and I will continue to add more.

 


How would you rate this article?

User Feedback
Comment posted by Mike Default on Thursday, June 4, 2015 9:09 PM
Nice article and well written!

Post your comment
Name:
E-mail:
Comment:
Insert Cancel

Copyright © 2013 Jose M. Tamez
Last Updated August 18th 2013