Introduction
Here
I am explaining how one can take the full advantage of master pages. Visit <a
href="http: www.odetocode.com="" articles="" 450.aspx"="">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 <st1:place><st1:placename>Employee
<st1:placetype>Territory. getParentData
will return the info related to the Parent source and getChildData will return
the info related to the child source, Key is the parameter passed to the method
by Parent Grid.
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.
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
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;
public event GridSelectionChanged GridChanged;
public event SecondryGridSelectionChanged ChildGridChanged;
#region Properties
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)
{
}
protected void FillChildInfo (object sender,
DetailGridEventArgument e)
{
}
}
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.
I have graduated from SirSyed university of engineering and technology in year 2005. Working on Microsoft Platform for 3 years.
I like to work on new technologies. Always keen to learn new orientation in software design. Design patterns is the area for which i am looking to work on.