今天突然想把某一功能封装成服务器控件,但这一功能又需要连接数据库,如果直接在控件里连接数据库,那么跟在UI层直接操作数据库是一样的,也就破坏了三层架构。
突然想到了在自定义控件里调用页面方法,但在控件里只能调用基类 System.Web.UI.Page 已声明的几个方法,比如:DataBind()。一个简单方法就是在自定义控件里调用基类已声明的方法,然后在具体页面重写此方法,达到功能的实现。这里简单介绍一下:
在自定义控件里可以这样调用
this.Page.DataBind();
在具体页面重写此方法:
public override void DataBind()
{
base.DataBind();
//Do something
}
{
base.DataBind();
//Do something
}
但很明显我今天要介绍的不是此方法。以下进入正题。
想用反射调用页面方法,如果有规范的方法名和参数列表,那就简单多了。这里自然想到用一接口来规范化。
一、创建新网站(ServerControlTest)
二、添加自定义服务器控件项目
三、定义并在页面实现接口
为了方便,直接在 Default.aspx.cs 文件中声明接口。注意:这里的接口是不包含在任何命名空间中的。
此时Default.aspx.cs 文件中的代码如下:
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page , ITestInterface
{
protected void Page_Load(object sender, EventArgs e)
{
}
//接口实现
#region TestInterface 成员
public string TestMethod(string str)
{
return "已调用接口:TestInterface —— " + str;
}
#endregion
}
//接口定义
public interface ITestInterface
{
string TestMethod(string str);
}
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page , ITestInterface
{
protected void Page_Load(object sender, EventArgs e)
{
}
//接口实现
#region TestInterface 成员
public string TestMethod(string str)
{
return "已调用接口:TestInterface —— " + str;
}
#endregion
}
//接口定义
public interface ITestInterface
{
string TestMethod(string str);
}
四、自定义控件的实现
我们将重载控件的 OnInit 事件,在此事件中调用页面方法 TestMethod。如果此页面未实现 ITestInterface 接口,我们将抛出异常。
控件的默认代码不变,添加以下代码:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Type myType = this.Page.GetType();
System.Reflection.TypeFilter myFilter = new System.Reflection.TypeFilter(MyInterfaceFilter);
// 返回页面名称为 ITestInterface 的接口
Type[] myInterfaces = myType.FindInterfaces(myFilter, "ITestInterface");
//已实现接口
if (myInterfaces.Length > 0)
{
//我们将调用方法得到的结果赋于控件的 Text 属性
//第一个参数是调用此TestMethod的对象,在这里自然是页面实例
//第二个参数是方法TestMethod的参数列表,类型必须一致,无参数时可传null
this.Text = myInterfaces[0].GetMethod("TestMethod").Invoke(this.Page, new object[] { "输入参数" }).ToString();
}
//未实现接口
else
{
throw new Exception("The page doesn't implement the interface 'ITestInterface' !");
}
}
public bool MyInterfaceFilter(Type typeObj, Object criteriaObj)
{
if (typeObj.ToString() == criteriaObj.ToString())
return true;
else
return false;
}
{
base.OnInit(e);
Type myType = this.Page.GetType();
System.Reflection.TypeFilter myFilter = new System.Reflection.TypeFilter(MyInterfaceFilter);
// 返回页面名称为 ITestInterface 的接口
Type[] myInterfaces = myType.FindInterfaces(myFilter, "ITestInterface");
//已实现接口
if (myInterfaces.Length > 0)
{
//我们将调用方法得到的结果赋于控件的 Text 属性
//第一个参数是调用此TestMethod的对象,在这里自然是页面实例
//第二个参数是方法TestMethod的参数列表,类型必须一致,无参数时可传null
this.Text = myInterfaces[0].GetMethod("TestMethod").Invoke(this.Page, new object[] { "输入参数" }).ToString();
}
//未实现接口
else
{
throw new Exception("The page doesn't implement the interface 'ITestInterface' !");
}
}
public bool MyInterfaceFilter(Type typeObj, Object criteriaObj)
{
if (typeObj.ToString() == criteriaObj.ToString())
return true;
else
return false;
}
五、为项目 ServerControlTest 添加引用
六、在 Default.aspx 中引用控件
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="ServerControl" Namespace="ServerControl" TagPrefix="cc1" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:ServerControl1 ID="ServerControl1" runat="server" />
</div>
</form>
</body>
</html>
<%@ Register Assembly="ServerControl" Namespace="ServerControl" TagPrefix="cc1" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:ServerControl1 ID="ServerControl1" runat="server" />
</div>
</form>
</body>
</html>
七、运行应用程序
页面将输出: 已调用接口:TestInterface —— 输入参数
至此已介绍完毕。不过其中有些注意的地方提一下。
在查找接口时 Type[] myInterfaces = myType.FindInterfaces(myFilter, "ITestInterface"); 接口名必须是完全限定名,如果接口是存放在某一命名空间中就必须注意了。如果你觉得这样太过于局限的话,那么你可以为控件增多一个属性,用来输入接口的完全限定名。
大家如果有什么更好的方法或建议,不妨提出来,大家一起学习学习。