面向对象属性和.NET UI控件的声明式数据加载






4.50/5 (4投票s)
本文详细介绍了一种新的实践,该实践使用从数据库检索的数据准备.NET业务对象,并通过中心化的类型属性与数据列与UI控件之间的映射,使用反射动态地将它们绑定到.NET UI控件。
引言
将业务对象绑定到ASP.NET UI控件的传统做法是,通过数据库包/存储过程检索内容,在数据层准备数据集/数组列表,然后将业务对象发送到UI。UI将通过表示代码中显式编写的数据绑定语句将对象数据绑定到每个控件。
背景
在数据访问层
DataAccess yourDAC = new DataAccess();
DataSet dsYourDetails = yourDAC.getDetails(sYourCriteria);
return dsYourDetails; //To the Business Layer
在业务层
public class YourBO
{
public String FirstName { get; set; }
public String LastName { get; set; }
}
我们在一个方法中将DataSet
从数据层获取到业务层
YourBO businessObj = PrepareBusinessObject(dsYourDetails);
return businessObj;//To the Presentation Layer
private YourBO PrepareBusinessObject(DataSet ds)
{
YourBO obj = new YourBO ();
obj.ImpactedChannels = ds.Tables[0].Rows[0]["FirstName"].ToString();
//any other properties......
return obj;
}
在表示层(ASP.NET页面)
txtFirstName.Text = businessObj.FirstName;
txtLastName.Text = businessObj.LastName;
ddlCities.Text = businessObj.City;
//any other controls……….
在上述实现中,流程中的一切都很好。这里唯一需要注意的是,在准备业务对象时,我们编写了DataColumn
名称,并为其显式设置了相应的属性。当DataColumn
的名称发生变化时,如果同一个属性在多个地方使用,那么我们需要到所有地方去修改属性和数据列的映射。这将需要相当大的努力。演示层的情况也类似。当页面控件增多,并且属性、控件和数据列之间的关系在多个页面和多个层中使用时,我们需要一种实现方式,这种方式所需的精力更少,是中心化的,并且对任何新开发人员都易于管理。
Using the Code
让我们再次回到上面给出的传统实现示例。对于这种情况,建议的解决方案仍然是对数据访问层没有任何更改。
在数据访问层
DataAccess yourDAC = new DataAccess();
DataSet dsYourDetails = yourDAC.getDetails(sYourCriteria);
return dsYourDetails; //To the Business Layer
在业务层
public class YourBO
{
[UI(UIField = "txtFirstName",
DataField = "FirstName", TableNo = 0, RowNo = 0)]
public String FirstName { get; set; }
[UI(UIField = "txtLastName",
DataField = "LastName", TableNo = 0, RowNo = 0)]
public String LastName { get; set; }
}
不要将“UI”这个声明性属性混淆。我们将在下一节再讨论它。现在,我们通过调用以下方法来准备业务对象:
private void SetObjectContent(object typeObject, Type objType, DataSet dsObject)
{
UIAttribute customAttr = null;
PropertyInfo[] properties = objType.GetProperties();
foreach (PropertyInfo property in properties)
{
customAttr = (UIAttribute)Attribute.GetCustomAttribute
(property, typeof(UIAttribute), false);
if (customAttr == null)
continue;
else
{
property.SetValue(typeObject,
Convert.ToString(dsObject.
Get(customAttr.TableNo, customAttr.RowNo,
customAttr.DataField)), null);
}
}
}
在从数据访问层获得dataset
后,我们需要在业务对象的适当位置调用上述方法。例如:
SetObjectContent(yourBOObj, typeof(YourBO), dsDetails);
该方法执行完成后,您的业务对象就准备好了。无需编写显式语句将DataSet
中的每个DataColumn
绑定并设置到业务对象的每个属性。这里节省了大量的精力。而这还不是全部。在表示层,我们需要在适当的位置调用此方法,以动态地将ASP.NET UI控件绑定到业务对象。
private void SetControlContent
(object typeObject, Type objType, Control containerControl)
{
PropertyInfo[] properties = objType.GetProperties();
UIAttribute uiAttribute = null;
foreach (Control control in containerControl.Controls)
{
if (control is TextBox)
{
TextBox txt = (TextBox)control;
uiAttribute = null;
foreach (PropertyInfo property in properties)
{
uiAttribute = ((UIAttribute)Attribute.GetCustomAttribute
(property, typeof(UIAttribute), false));
if (uiAttribute != null && uiAttribute.UIField == txt.ID)
{
txt.Text = property.GetValue(typeObject, null).ToString();
break;
}
}
}
else if (control is DropDownList)
{
DropDownList ddl = (DropDownList)control;
uiAttribute = null;
foreach (PropertyInfo property in properties)
{
uiAttribute = ((UIAttribute)Attribute.GetCustomAttribute
(property, typeof(UIAttribute), false));
if (uiAttribute != null && uiAttribute.UIField == ddl.ID)
{
ddl.Text = property.GetValue(typeObject, null).ToString();
break;
}
}
}
}
例如。在页面的适当部分,我们可以这样设置页面控件的属性。
Type objType = yourBOObj.GetType();
containerControl = Page; //this can be a UserControl/Panel/PlaceHolder etc.
typeObject = yourBOOBj;
SetControlContent(typeObject, objType, containerControl);
该方法执行完成后,您页面上的所有控件都将设置相应的业务对象属性,而无需编写显式语句。现在,我们仅通过为业务对象属性使用一个属性就实现了这种精力的节省。该属性允许为属性添加额外信息,这些信息可以通过反射访问并在不同层中高效使用。这是此实现的核心方面。让我们看看它的结构:
[global::System.AttributeUsage(AttributeTargets.All, Inherited = false,
AllowMultiple = true)]
public sealed class UIAttribute : Attribute
{
// See the attribute guidelines at
// http://go.microsoft.com/fwlink/?LinkId=85236
public UIAttribute()
{
}
// This is a positional argument
public UIAttribute(string uiField, string dataField, int tableNo, int rowNo)
{
this.UIField = uiField;
this.DataField = dataField;
this.TableNo = tableNo;
this.RowNo = rowNo;
}
public string UIField { get; set; }
public string DataField { get; set; }
public int TableNo { get; set; }
public int RowNo { get; set; }
}
扩展实现
我们可以将此实现扩展到不仅仅是简单的业务对象,还包括涉及继承、集合属性的复杂类型。无论类型定义如何,我们都需要自定义上述实现,以便从数据访问层实现控件数据和业务对象数据的动态加载。下面是一个示例实现,用于说明银行业务对象中的继承场景。为了区分集合属性(RowNo=1
)和非集合属性(RowNo=0
),我们可以使用属性值“RowNo
”或属性的任何用户定义参数来通知业务层在业务对象(IncidentBO
)属性中设置正确的数据。让我们将“ParentItemBO
”视为业务对象中集合属性的一部分。基类型“ParentItemBO
”中的属性将是虚拟的,其定义将在派生通道(如“Item1BO
、Item2BO
等”)中带有覆盖的属性声明。同样,每个Item都有其Child
。因此,我们需要一个基Child
类型,并且其中的属性将被标记为虚拟。所有ParentItem
特定的ChildItems
将通过覆盖属性声明从“ChildItemBO
”派生。
public class YourBO
{
public YourBO()
{
this.ItemCollection = new ArrayList();
}
public ArrayList ItemCollection { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
///
/// RowNo "1" indicates that this type will be part of the collections.
///
public class ParentItemBO
{
[TAUI(UIField = "", DataField = "dataColumn1", TableNo = 3, RowNo = 1)]
public virtual string Property1 { get; set; }
[TAUI(UIField = "", DataField = "dataColumn2", TableNo = 3, RowNo = 1)]
public virtual String Property2 { get; set; }
[TAUI(UIField = "", DataField = "dataColumn3", TableNo = 3, RowNo = 1)]
public virtual String Property3 { get; set; }
public virtual List ChildItems { get; set; }
}
public class ChildItemBO : ParentItemBO
{
[TAUI(UIField = "", DataField = "childDataColumn1", TableNo = 2, RowNo = 1)]
public virtual String ChildProperty1 { get; set; }
[TAUI(UIField = "", DataField = "childDataColumn2", TableNo = 2, RowNo = 1)]
public virtual string ChildProperty2 { get; set; }
}
这是一个具体的Item。
public class Item1BO : ParentItemBO
{
[TAUI(UIField = "txtProperty1", DataField = "dataColumn1",
TableNo = 3, RowNo = 1)]
public override string Property1 { get; set; }
[TAUI(UIField = "chkProperty1", DataField = "dataColumn2",
TableNo = 3, RowNo = 1)]
public override String Property2 { get; set; }
[TAUI(UIField = "ddlProperty1", DataField = "dataColumn3",
TableNo = 3, RowNo = 1)]
public override String Property3 { get; set; }
}
以下是具体的Child
项。这些是您在实现过程中在“ParentItemBO
”对象中标记的集合属性(ChildItems
)的一部分。
public class ChildItem1BO : ChildItemBO
{
[TAUI(UIField = "txtChildProperty1",
DataField = "childDataColumn1", TableNo = 2, RowNo = 1)]
public override String ChildProperty1 { get; set; }
[TAUI(UIField = "rdbChildProperty1",
DataField = "childDataColumn2", TableNo = 2, RowNo = 1)]
public override string ChildProperty2 { get; set; }
}
public class ChildItem2BO : ChildItemBO
{
[TAUI(UIField = "txtChildProperty2",
DataField = "childDataColumn1", TableNo = 2, RowNo = 1)]
public override String ChildProperty1 { get; set; }
[TAUI(UIField = "rdbChildProperty2",
DataField = "childDataColumn2", TableNo = 2, RowNo = 1)]
public override string ChildProperty2 { get; set; }
}
关注点
上述实现可用于在映射属性到UI控件方面需要大量精力且代码量最少的情况下。如果我们有一个集中的UI控件-数据库列-业务对象属性映射,那么三个层的维护将更加容易。