Master Pages and Factory Pattern






4.35/5 (13 votes)
Master pages and factory patterns using ASP.NET
Introduction
Here I am explaining how one can take the full advantage of master pages. Visit http://www.odetocode.com/Articles/450.aspx to get the excellent knowledge of master page. There are plenty of articles explaining insights of Master Pages this is certainly not the one. Sometime you stuck in a situation where you have same GUI, like the one shown in figure, but different business scenario, you start thinking about user controls to maintain standardized look across all pages. What if I say using Master pages with Factory pattern makes you feel in heaven? Let's see how. I have used the clickable grid which was submitted by code Project user BlaiseBraye.
Background
Read about master pages before reading this article. It will help you understand this article.
Application Flow
When user selects order detail from the menu, parent grid will be populated with order list and child grid will be populated with the list of products included in that order, when user selects employee territory parent grid will be populated with list of employees and child grid will be populated with the list of territories for the selected employee. Also there are two content pages which show related info of the selected item.
Using the code
The
download code contains dbcontroller1
helper class. You need to change the
connection string there to connect to your SQL server. Backend database is
Northwind which is shipped with SQL server.
Overview
This application has a master page which consists of a menu control and two Grids parent and child. When I say parent and child it means child grid is dependent on parent grid for its source.
Let's take an example of Order and order detail tables in Northwind Database. We have a list of orders populated in one grid by selecting any of the order in parent grid child grid is populated with the list of products for that order. This scenario also applies for employee and employee territory case. You could have a lot of similar scenario in your application. What will you do? Create separate similar pages or create a user control. Though user control is a good choice but let's go with master pages.
Before going into the detail of the application let's explore what Factory pattern is?
Factory Method
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses (GOF). There is one more variation of Factory method which is called Parameterized factory Method, which states The factory method takes a parameter that identifies the kind of object to create. All objects the factory method creates will share the Product interface. This is what we are going for. Show below is the graphical representation of Parameterized Factory method. Factory methods eliminate the need to bind application-specific classes into your code. The code only deals with the Base interface; therefore it can work with any user-defined classes which have the same interface as Base.
Implementing Factory Method
We
have an abstract class Base which contains four abstract methods. Order and
Employee class will inherit from our abstract class Base. Below is the code for
our Base abstract class. BindParentGrid
will bind the parent grid with the
parent source which could be Order list or Employee list guided by user
selection. BindChildGrid
method will bind the child grid to the child source
i.e. Products or
///<summary>
/// This is the base abstract class. all business classes will inherit
/// from this class
/// This is used for factory pattern.
///</summary>
public abstract class Base
{
public abstract void BindParentGrid(
DataDigest.WebControls.GridView gvParent);
public abstract void BindChildGrid(
DataDigest.WebControls.GridView
gvChild, string ParentId);
public abstract DataSet getParentData();
public abstract DataSet getChildData(string Key);
}
We
derive two classes from our abstract class which controls the Grid binding i.e.
BIOrderMaster
and BIEmployeeMaster.Below
is the code of one of
our inherited class. We are eliminating the discussion of our Data Access
components, you can see the details in attached source code.
///<summary>
/// This will control the opertaion when user selects
/// OrderDetail from the menu
///</summary>
public class BIOrderMaster : Base
{
private DAccessOrder Orders = newDAccessOrder();
private DataSet dsParent = newDataSet();
public BIOrderMaster()
{
dsParent = Orders.getOrders();
}
public override void BindParentGrid(DataDigest
.WebControls.GridView gvParent)
{
if (dsParent == null) {
dsParent = Orders.getOrders();
}
string[] key = newstring[1];
key[0] = "OrderID";
gvParent.DataSource = null;
gvParent.Columns.Clear();
gvParent.DataSource = dsParent;
gvParent.DataKeyNames = key;
BoundField TtlCoNm = newBoundField();
TtlCoNm.DataField = "OrderID";
TtlCoNm.HeaderText = "Order ID";
gvParent.Columns.Add(TtlCoNm);
TtlCoNm.ItemStyle.Width = 600;
gvParent.DataBind();
}
public override void BindChildGrid(DataDigest
.WebControls.GridView gvChild,
string ParentId)
{
string[] key = newstring[1];
key[0] = "ProductID";
DataSet dsChild = Orders.getProducts(ParentId);
gvChild.SelectedIndex = -1;
gvChild.DataSource = null;
gvChild.Columns.Clear();
gvChild.DataSource = dsChild;
gvChild.DataKeyNames = key;
BoundField BrName = newBoundField();
BrName.DataField = "ProductName";
BrName.HeaderText = "Product Name";
gvChild.Columns.Add(BrName);
BrName.ItemStyle.Width = 600;
gvChild.DataBind();
}
public override DataSet getParentData()
{
return dsParent;
}
public override DataSet getChildData(string Key)
{
return Orders.getProducts(Key);
}
}
Whenever user selects a particular item from menu we
store page identity in session. Master Page passes page identity to our factory
class which returns appropriate object of Base type. A type is a name used to denote a particular
interface. We speak of an object as having the type "Window" if it
accepts all requests for the operations defined in the interface named
"Window."(GOF) Our derived classes BIOrderMaster
and BIEmployeeMaster
have the same type as they both shared the Base interface.
Our factory class is shown below:
Factory Class
///<summary>
/// Return object of type Base depending on the parameter
/// provided to its getobject method
///</summary>
public class Factory
{
public Factory()
{
}
publicBase GetObject(string type)
{
Base objbase = null;
switch (type)
{
case "Order Detail":
objbase = newBIOrderMaster();
break;
case "Employee Territorie":
objbase = newBIEmployeeMaster();
break;
default:
throw new Exception("Unknown Object");
}
return objbase;
}
}
Master Page
let's see what happens when user request Order detail page which is our one of two content pages. Order of events is important when we are working with master pages. Page load event for the page is fire before the page load event of the master page but page init event for the master page is fired before page init event of page. This is important and we will discuss it later in this article. For now we only need to know the order of events which are as follows:
- Master – initialize.
- Content- initialize.
- Content- load.
- Master – load.
- Content- render.
- Master- render.
Let's have a close look on master page code behind event by event
First we have create an object of our factory class and declared an object of type Base. You guys have also noticed that we have couple of events in our master page class. These events are used to notify our content page that grid selection in our master page is changed and they must take appropriate action to counter that change. I am not going in details of custom events and delegate as there are plenty of material available about delegates. (Its handy to know that delegates are the example of observer pattern). We also have couple of public properties.
private Factory Factory = newFactory();
private Base currentObject;
// to raise the event in content page whenever
// grid selection changes
public event GridSelectionChanged GridChanged;
public event SecondryGridSelectionChanged ChildGridChanged;
#region Properties
//Public properties exposed by the master page for
//content pages
public string mainTitle
{
set
{
pageTitle.Text = value;
}
}
publicBase myObject
{
get
{
return currentObject;
}
}
#endregion
At
the page init event we are requesting our factory for the appropriate Base type
object. Property myObject
returns this newly created
object. This property is used by content pages to get the current business
object at their page load event. Remember we talked about the order of events.
We call our factory class getObject
method in the page_init
event if we replace
the code to page load event we wont be able to get its reference from our
content page because Page_load
event for the content page occurs before the
page_load
event of master page.
protected void Page_Init(object sender, System.EventArgs e)
{
if (Session["PageName"] != null)
{
if (Session["PageName"].ToString() !=
"Home")
{
currentObject = Factory.GetObject(Session[
"PageName"].ToString());
}
}
}
At the page load event of master page we are
requesting currentObject
which is our Base type object to bind our parent
grid. Similarly on parent grid SelectedIndexChanged
event we are requesting
currentObject
to bind our child grid. In GV_TitleCompany_SelectedIndexChanged
we are also notifying our content page that a grid selection has changed. We
have extended eventArg
class to hold the information about the event, Datakey
of the grid in this case.
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
if (Session["PageName"] != null)
{
if (Session["PageName"].ToString()
!= "Home")
{
currentObject.BindParentGrid(
GV_TitleCompany);
}
else
{
myPanel.Visible = false;
Panel1.Visible = false;
ClearGrid();
}
}
else
{
myPanel.Visible = false;
Panel1.Visible = false;
ClearGrid();
}
}
}
protected void GV_TitleCompany_SelectedIndexChanged(
object sender, EventArgs e)
{
GridViewRow row = GV_TitleCompany.SelectedRow;
currentObject.BindChildGrid(GV_BranchName,
GV_TitleCompany.DataKeys[GV_TitleCompany
.SelectedIndex][0].ToString().Trim());
MasterGridEventArgument eventArgs = new
MasterGridEventArgument(GV_TitleCompany.DataKeys[
GV_TitleCompany.SelectedIndex].Value.ToString()
.Trim());
if (GridChanged != null)
{
GridChanged(this, eventArgs);
}
}
Now let's move on to one of our content page.
Order Page
public partial class Pages_Default : System.Web.UI.Page
{
privateBase Orders;
protectedvoid Page_Load(object sender, EventArgs e)
{
Pages_MasterGrid myMaster = (Pages_MasterGrid)
this.Master;
myMaster.mainTitle = "Order Information";
Orders = myMaster.myObject;
}
protected void Page_Init(object sender,
System.EventArgs e)
{
Pages_MasterGrid myMaster = (Pages_MasterGrid)
this.Master;
myMaster.GridChanged += FillControls;
myMaster.ChildGridChanged += FillChildInfo;
}
protected void FillControls(object sender,
MasterGridEventArgument e)
{
//Code to fill the controls with parent info
}
protected void FillChildInfo (object sender,
DetailGridEventArgument e)
{
//Code to fill the controls with child info
}
}
At the
page_init
event we are adding handlers for our custom events which will be
raised by Parent and Child grid in our Master page then at page load event we
are setting Page title through Public property exposed by our Master page and
also we are getting the currentObject
from our Master page. We attached
FillControl
function to our GridChanged
event and FillChildInfo
to our
ChildGridChanged
event. These functions will populate our controls contained in
Multiview
control. Complete code is present in attached source code.
Final say
That's all; we have implemented mater pages with Factory Pattern. You guys are free to made any changes and give healthy comments on this articles. Cheers.