[原文源码下载]
原文发布日期:2007.01.18
作者:ssaud
翻译:webabcd
介绍
这里我讲解如何充分发挥母版页的优势。关于母版页的优点有大量的文章进行说明,参看 http://www.odetocode.com/Articles/450.aspx 此文当然和那些文章不相同。有时,你会经常碰到这样的场景:相同的用户界面(GUI),但是不同的业务逻辑,如图所示。因此,你会考虑使用用户控件来保持各个页面的标准外观。但如果你听我说明一下如何使用工厂模式设计的母版页,你就会感觉酷毙了!这里我使用了带单击功能的表格(Grid)。
背景
阅读此文之前你必须对母版页的相关知识有所了解,这样你能更好的理解此文
工作流程
当用户从菜单中选择订单详情时,父表格将会填充所有订单列表,子表格将会填充包含在此订单的产品列表。当用户选择职员时,父表格将会填充所有职员列表,子表格将会填充选中职员的地区列表。有2个内容页来显示选中项的相关信息。
关于代码
下载的代码包括dbcontroller1 helper类。你只需修改数据库链接字符串来链接到你的SQL server就可以了。后台数据库是SQL server自带的Northwind数据库。
概述
此应用程序包括一个母版页,其中有1个菜单控件和2个表格控件(父表格和子表格),其中子表格的数据源依赖父表格。
我们首先看看Northwind数据库中订单和订单明细表的例子。我们在父表格中填充订单列表,当选中其中一个订单时,子表格填充该订单包括的产品列表。这个场景同样适用于职员和职员地区的案例。同样,在应用程序中经常有类似的场景。你准备怎么做?创建大量独立的类似的页面?还是创建一个用户控件?尽管用户控件是个不错的选择,但这里我们用母版页来实现这个需求。
在进一步讲解之前,我们先来了解一下工厂模式到底是什么?
工厂方法
工厂方法(FactoryMethod)模式是类的创建模式,其用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中(GOF)。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
这就是我们所期望的。下图解释了参数化工厂方法。工厂方法消除了代码中和特定应用程序相关的类,代码仅仅定义了基类接口,因此它可以和与基类具有同样的接口的用户定义的类配合工作。
实现工厂方法
我们创建一个抽象类Base,其中包括了4个抽象方法。Order 和 Employee类继承抽象类Base。下面是Base类的代码。BindParentGrid将把Parent数据源绑定到父表格中,Parent数据源既可以是Order列表,也可以是Employee列表,这看用户的选择了。BindChildGrid方法Child数据源(产品或者地区)绑定到子表格中。getParentData返回Parent数据源信息,getChildData返回子数据源信息。Key是父表格传递给方法的参数。
///<summary>
/// 抽象类
///</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);
}
/// 抽象类
///</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);
}
我们从抽象类继承过来两个类BIOrderMaster 和 BIEmployeeMaster,来控制表格绑定。下面是其中一个继承类的代码。我们不再讨论数据访问组件,详细情况请直接看附件代码。
///<summary>
/// 具体类
///</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);
}
}
/// 具体类
///</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);
}
}
无论何时用户从菜单中选择一个特定的项时,我们把页标识(page identity)存到Session中。母版页把页标识传给工厂类,工厂类返回匹配的基类类型的对象。我们的具体类 BIOrderMaster 和 BIEmployeeMaster 具有相同的类型,因为他们共享基类的接口。我们的工厂类如下所示:
工厂类
///<summary>
/// 返回实现抽象基类的对象
///</summary>
public class Factory
{
public Factory()
{
}
public Base 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;
}
}
/// 返回实现抽象基类的对象
///</summary>
public class Factory
{
public Factory()
{
}
public Base 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;
}
}
母版页
我们看看当用户请求两个内容页之一——订单详情页时,将会发生什么。当我们用母版页工作时,事件的执行顺序很重要。内容页的Page load事件在母版页Page load事件之前发生,而母版页的page init事件在内容页的page init之前触发。这个顺序非常重要,我们将在后面讨论。目前,我们仅仅需要知道这些事件执行的顺序
·Master – initialize.
·Content- initialize.
·Content- load.
·Master – load.
·Content- render.
·Master- render.
我们挨个事件观察母版页后面的代码
首先,我们实例化一个工厂类对象,同时申明一个类型是Base的对象。我们注意到母版页代码中有好几个事件。这些事件用于通知内容页,当母版页中的选择改变时,内容页也必须做相应变化。我不想就自定义事件和委托做过多讲解,因为有很多这方面的资料可得。我们还定义了几个公共属性。
private Factory Factory = newFactory();
private Base currentObject;
public event GridSelectionChanged GridChanged;
public event SecondryGridSelectionChanged ChildGridChanged;
public string mainTitle
{
set
{
pageTitle.Text = value;
}
}
public Base myObject
{
get
{
return currentObject;
}
}
private Base currentObject;
public event GridSelectionChanged GridChanged;
public event SecondryGridSelectionChanged ChildGridChanged;
public string mainTitle
{
set
{
pageTitle.Text = value;
}
}
public Base myObject
{
get
{
return currentObject;
}
}
在page init事件中,我们要求工厂得到合适的Base类型对象,属性myObject返回这个新创建的对象。这个属性在内容页使用,用于在Page Load事件中得到当前具体对象。还记得我们说过的事件的执行顺序吧。在page_init事件中,我们调用factory类getObject方法。如果我们把代码替换到page load事件中,我们就不能内容页得到它的引用,因为,内容页的page load事件在母版页的page load事件之前发生。
protected void Page_Init(object sender, System.EventArgs e)
{
if (Session["PageName"] != null)
{
if (Session["PageName"].ToString() !=
"Home")
{
currentObject = Factory.GetObject(Session[
"PageName"].ToString());
}
}
}
{
if (Session["PageName"] != null)
{
if (Session["PageName"].ToString() !=
"Home")
{
currentObject = Factory.GetObject(Session[
"PageName"].ToString());
}
}
}
在母版页的page load事件中,我们请求Base类型对象currentObject绑定父表格。相似的,在父表格SelectedIndexChanged事件中,我们请求currentObject绑定子表格。在GV_TitleCompany_SelectedIndexChanged事件中,我们通知当前页,一个grid selection动作发生了。我们扩展了eventArg类,以处理事件消息——即表格的Datakey
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);
}
}
{
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);
}
}
现在,我们来看看其中的一个内容页
订单页面
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
}
protected void FillChildInfo (object sender, DetailGridEventArgument e)
{
// Code
}
}
{
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
}
protected void FillChildInfo (object sender, DetailGridEventArgument e)
{
// Code
}
}
在page_init事件中,我们增加自定义事件的处理逻辑,这些自定义事件将由母版页的父表格和子表格调用,然后在page load事件中,我们通过公共属性给Page Title赋值,我们也设置母版页的currentObject。我们给GridChanged事件添加了FillControl函数,以及给ChildGridChanged事件添加了FillChildInfo函数,这些函数将填充Multiview控件中所包括的控件。全部代码请参见源码
结语
这就是所有的内容,我们已经完成了母版页和工厂模式。欢迎大家对此文做出修改和评论。谢谢!