引言
在 Web 开发人员的最常见任务之中有一项任务是他们要反复执行的建立更新数据库表的简单窗体我们将创建一个列表页面和一个窗体页面列表页面中以表格形式显示记录窗体页面中带有用于各个数据库字段的适当的窗体控件许多开发人员还使用表示数据库表的业务对象将代码组织到分为多层的设计中如果以业务对象 (Document) 来表示数据库表 (Documents)许多窗体的代码看上去将如下所示
<script runat=server>
protected void Page_Load(Object Src EventArgs E) {
if (!IsPostBack) {
Document document =
DocumentsGetDocument(RequestQueryString[DocumentID]);
TitleText = documentTitle;
ActiveChecked = documentActive;
CreatedDateText = documentCreatedDateToString();
AuthorIDFindByValue(documentAuthorIDToString())Selected =
true;
// 等等
HtmlBodyText = documentHtmlBody;
}
}
protected void SaveButton_Click(Object Src EventArgs E) {
Document document =
DocumentsGetDocument(RequestQueryString[DocumentID]);
documentTitle = TitleText;
documentActive = ActiveChecked;
documentCreatedDate = ConvertToDateTime(CreatedDateText);
documentAuthorID = ConvertToInt(AuthorIDSelectedItemValue);
// 等等
documentHtmlBody = HtmlBodyText;
DocumentsUpdate(document);
}
</script>
简化和缩短窗体代码
在以上代码中对每个控件进行显式转换并将其设置为窗体控件的正确属性根据属性和窗体控件的数量这部分代码可能会变长并难以管理代码还应包含类型转换的错误更正和 ListControl这将进一步增加复杂性即使窗体是由代码生成工具(例如 Eric J Smith 的优秀的 CodeSmith)生成的当需要任何自定义逻辑关系时很容易引入错误
使用反射可以仅使用单行代码便将业务对象的所有属性绑定到相应的窗体控件从而减少代码的行数并增强可读性完成反射系统的建立后以上代码将简化为
protected void Page_Load(Object Src EventArgs E) {
if (!IsPostBack) {
Document document =
DocumentsGetDocument(RequestQueryString[DocumentID]);
FormBindingBindObjectToControls(document);
}
}
protected void Save_Click(Object Src EventArgs E) {
Document document =
DocumentsGetDocument(RequestQueryString[DocumentID]);
FormBindingBindControlsToObject(document);
DocumentsUpdate(document);
}
此代码可用于所有标准的 ASPNET 控件(TextBoxDropDownListCheckBox 等)和许多第三方控件(例如 Free TextBox 和 Calendar Popup)无论有多少业务对象属性和窗体控件这一行代码都能提供所需的全部功能只要窗体控件的 ID 与业务对象属性名相匹配
开始从反射中检索属性列表
首先我们需要检查业务对象的属性并查找与业务对象属性名具有相同 ID 的 ASPNET 控件以下代码构成了绑定查找的基础
public class FormBinding {
public static void BindObjectToControls(object obj
Control container) {
if (obj == null) return;
Type objType = objGetType();
PropertyInfo[] objPropertiesArray =
objTypeGetProperties();
foreach (PropertyInfo objProperty in objPropertiesArray) {
Control control =
containerFindControl(objPropertyName);
if (control != null) {
// 处理控件
}
}
}
}
在以上代码中方法 BindObjectsToControls 接受了业务对象 obj 和一个容器控件容器控件通常是当前 Web 窗体的 Page 对象如果所用版本是会在运行时更改控件嵌套顺序的 ASPNET x MasterPages您将需要指定窗体控件所在的 Content 控件这是在 ASPNET x 中FindControl 方法对嵌套控件和命名容器的处理方式导致的
在以上代码中我们获取了业务对象的 Type然后使用该 Type 来获取 PropertyInfo 对象的数组每个 PropertyInfo 对象都包含关于业务对象属性以及从业务对象获取和设置值的能力的信息我们使用 foreach 循环检查具有与业务对象属性名 (PropertyInfoName) 对应的 ID 属性的 ASPNET 控件的容器如果找到控件则尝试将属性值绑定到该控件
将对象属性值绑定到控件
过程中的大部分操作是在此阶段执行的我们需要用对象的属性值来填充找到的控件一种实现方法是为每种控件类型创建一个 if else 语句派生自 ListControl(DropDownListRadioButtonListCheckBoxList 和 ListBox)的所有控件都具有可以统一访问的公用接口所以可以将它们编组在一起如果找到的控件是 ListControl我们可以将其作为 ListControl 进行转换然后设置选定项
Control control = containerFindControl(objPropertyName);
if (control != null) {
if (control is ListControl) {
ListControl listControl = (ListControl) control;
string propertyValue = objPropertyGetValue(obj
null)ToString();
ListItem listItem =
listControlItemsFindByValue(propertyValue);
if (listItem != null) listItemSelected = true;
} else {
// 处理其他控件类型
}
}
不幸的是其他控件类型并不从父类中派生以下几个公用控件都具有 Text 字符串属性TextBoxLiteral 和 Label但该属性不是从公用父类中派生出来的所以需要分别转换每种控件类型我们还需要转换其他控件类型例如 Calendar 控件以便使用适当的属性(在 Calendar 的例子中是 SelectedDate 属性)要包含所有标准的 ASPNET 窗体控件并访问窗体控件的正确属性并不需要太多的代码行
if (control is ListControl) {
ListControl listControl = (ListControl) control;
string propertyValue = objPropertyGetValue(obj
null)ToString();
ListItem listItem = listControlItemsFindByValue(propertyValue);
if (listItem != null) listItemSelected = true;
} else if (control is CheckBox) {
if (objPropertyPropertyType == typeof(bool))
((CheckBox) control)Checked = (bool)
objPropertyGetValue(obj null);
} else if (control is Calendar) {
if (objPropertyPropertyType == typeof(DateTime))
((Calendar) control)SelectedDate = (DateTime)
objPropertyGetValue(obj null);
} else if (control is TextBox) {
((TextBox) control)Text = objPropertyGetValue(obj
null)ToString();
} else if (control is Literal)(
// 等等还可用于标签等属性
}
此方法完整地涵盖了标准的 ASPNET x 控件从这个角度来看我们拥有了功能齐全的 BindObjectToControls 方法但在起作用的同时此方法的应用范围会受到限制因为它仅考虑内置的 ASPNET x 控件如果要支持新的 ASPNET 控件或者要使用任何第三方控件我们必须在 FormBinding 项目中引用控件的程序集并将控件类型添加到 if else 列表
此问题的解决方案是第二次使用反射以查看各个控件的属性并找出控件是否具有与业务对象的属性类型对应的属性类型
用已知属性设置未知控件的值
如上所述有些控件共享字符串属性 Text大多数窗体控件以实质相同的方式使用此属性该属性用于获取和设置用户输入的数据有大量控件还使用了其他一些公用属性和属性类型以下是这些属性中的一些称为 SelectedDate 的 DateTime 属性它在许多日历和日期选取器控件中使用称为 Checked 的布尔属性它在布尔型控件中使用称为 Value 的字符串属性它常见于隐藏控件这四个属性(string Textstring Valuebool Checked 和 DateTime SelectedDate)是最常见的控件属性如果可以将系统设计成无论何种控件类型都绑定到这些属性那么我们的绑定方法将适用于使用那四个属性的任何控件
在以下代码中我们将第二次使用反射(这一次是对窗体控件使用而不是对业务对象使用)以确定它是否具有任何常用属性如果有则尝试将业务对象的属性值设置为控件的属性作为示例我们将对整个 PropertyInfo 数组进行迭代并查找称为 Text 的字符串属性如果控件具有该属性则将数据从业务对象发送到该控件的属性
if (control is ListControl) {
//
} else {
// 获取控件的类型和属性
//
Type controlType = controlGetType();
PropertyInfo[] controlPropertiesArray =
controlTypeGetProperties();
// 查找 Text 属性
//
foreach (Pro