最近,看着公司自定义的控件,觉得自己应该学习这些东西,这样有助于自己基础知识的巩固,于是试着还原公司的代码,看自己是否有这样的功力。
公司的控件大致有这样的用途:将控件放一个容器中,通过给自己定义控件绑定数据库中表的字段,然后根据业务的需求自动生成增删改查Sql语句,程序员可以将重点放在业务的分析处理中,尽可能的少写SQL语句。先不说这样的方法好不好,抱着学习的思想,我于是试着还原这些代码。
现在想一下实现如此的功能:在自定义的一个容器控件中,放入自定义控件,有TextBox,Label,CheckBox等,然后,点击Button,自动生成AddSql、UpdateSql、SearchSql语句,然后直接执行这些语句,减少Sql语句的编写量,加快项目进展(这里Sql语句优化不考虑)。
首先,控件开始考虑吧,我这里以TextBox为例子阐述自己的思路。
对于TextBox,大家估计都比较熟悉,转到定义后可以看见
TextBox : WebControl, IPostBackDataHandler, IEditableTextControl, ITextControl
TextBox控件继承了一个WebControl类,实现了三个接口,非常漂亮的设计。
WebControl类,微软官方的解释为:提供所有 Web 服务器控件的公共属性、方法和事件。通过设置在此类中定义的属性,可以控制 Web 服务器控件的外观和行为。由此可见,Web服务器控件应该都会继承或则个类了喔。
为了防止Sql注入,微软强烈建议我们使用带参数的Sql语句,即如果是Sql Server 数据库,参数名应该是"@Name",如果是Oracle数据库,参数名应该是":Name"(似乎微软自身似乎不支持My Sql,如果支持的话,应该是"?")。因此,自动生成的AddSql与UpdateSql语句中,应该包括字段名,字段值;自动生成的SearchSql语句,应该更加复杂点,我们手写Sql语句时,会写 Name='刘德华' 或 Name like '%刘' 或 Name like '刘%' 或 Name like '%刘%',因此需要判断匹配符所在的位置。
分析到现在,我们的xTextBox(自定义控件的名字)额外的属性有字段名、字段类型、查询类型、填充类型。
首先定义查询类型类,因为类型是确定的,所以用枚举,代码如下
public enum QueryTag
{
Exact,//精准查询 Name='刘德华'
Dark,//模糊查询 Name like '%刘%'
Start,//%前置模糊查询 Name like '%刘'
End//%后置模糊查询 Name like '刘%'
}
现在分析,如何保存这些中间值(因为这些值本身不是太重要,我们需要得到的是处理后的值),这是值得考虑的。我们在学习三层架构的的时候,大都会把一个表的字段放在一个Model类中,这样处理的好处是我们只要得到一个Model,就可以从中取得我们需要的任一个值。因此,定义一个Field类,保存用以保存获取自定义控件的属性的值。那么自定义控件可能需要什么额外的属性呢?默认填充值得类型、数据库字段名、数据库字段值、查询标记。代码如下
那么,现在有一个问题就是如何在容器中区分wTextBox与TextBox?
其实,最先考虑的是根据类型名来区分
如果asp:TextBox控件的ID为 txtASP,则txtASP.GetType().Name的值为TextBox
如果wTextBox的ID为txtW,则txtW.GetType().Name为wTextBox
如果我区分控件,则需要如下书写
foreach (Control ctrl in testPanel.Controls)
{
switch (ctrl.GetType().Name)
{
case "wTextBox":
break;
case "wLable":
break;
case "wCheckBox":
break;
......
}
}
这种写法虽然可以很好的区分自定义控件以及微软的服务器控件,假设有10个自定义控件,则要10个case语句,原则上不好,因此,寻找另外一种方法。
那么,我就参考TextBox实现方式,即实现某一个接口,因此可以让所有的自定义控件实现这个接口,那么,实现了自定义接口的控件便是自定义控件。问题解决了,不用具体区分是wTextBox还是wLable了。这里,我定义一个接口IBoundControl,让所有自定义的控件都去实现它。那么这个接口应该有什么功能呢。首先,这个接口可以得到一个自定义控件所有的属性,即可以得到Field类,其次,我们在进行数据填充的时候,需要给控件赋值,则需要一个方法,给控件赋值的方法。Field代码代码如下
public class Field { // Fields public FillType FillType;//默认填充值 public DisplayFormat Format;//现实形式 普通字符串 还是日期格式等 public string Key;//数据库表的字段名 字段名 Name public string Name;//数据库表的字段名 字段名 Name public QueryTag QueryTag;//查询标记 生成查询语句用 public DbType Type;//字段类型 public object Value;//数据库表的字段传递的值 字段值 张柏芝 // Methods public Field() { this.Name = string.Empty; this.Key = string.Empty; this.Value = string.Empty; this.Type = DbType.String; this.QueryTag = QueryTag.Exact; this.Format = DisplayFormat.Normal; this.FillType = FillType.Null; } public Field(string strName, object strValue) { this.Name = strName; this.Value = strValue; } public Field(string strName, object strValue, DbType fieldType) { this.Type = fieldType; } public Field(string strName, string strKey, object strValue) { this.Key = strKey; } public Field(string strName, string strKey, object strValue, DbType fieldType) { this.Type = fieldType; } public override string ToString() { if (string.IsNullOrEmpty(this.Key)) { return this.Name; } return (this.Name + "-" + this.Key); } }
IBoundControl接口代码如下:
public interface IBoundControl
{
Field GetBoundField();
void SetValue(string strValue);
}
此时,xTextBox代码已经出来了,代码如下
public class xTextBox:TextBox,IBoundControl { #region 属性描述 [DefaultValue(""), Description("绑定的字段名称")] public string wFieldName { get { return (this.ViewState["wFieldName"] as string); } set { this.ViewState["wFieldName"] = value; } } [DefaultValue("String"), Description("绑定的字段类型")] public DbType wFieldType { get { string str = this.ViewState["wFieldType"] as string; return (DbType)Enum.Parse(typeof(DbType), str); } set { this.ViewState["wFieldType"] = value.ToString(); } } [DefaultValue("Null"), Description("填充类型")] public FillType wFillType { get { string str = this.ViewState["wFillType"] as string; return (FillType)Enum.Parse(typeof(FillType), str); } set { this.ViewState["wFillType"] = value.ToString(); } } [Description("字段查询标记"), DefaultValue("Exact")] public QueryTag wQueryTag { get { string str = this.ViewState["wQueryTag"] as string; return (QueryTag)Enum.Parse(typeof(QueryTag), str); } set { this.ViewState["wQueryTag"] = value.ToString(); } } #endregion public xTextBox() { this.wFieldName = ""; this.wFieldType = DbType.String; this.wQueryTag = QueryTag.Exact; this.wFillType = FillType.Null; } public Field GetBoundField() { return new Field { Name = this.wFieldName, Value = this.Text, Type = this.wFieldType, QueryTag = this.wQueryTag, FillType = this.wFillType }; } public void SetValue(string strValue) { this.Text = strValue; } }
前面已经讲过了,我们是把控件放在一个特定的容器,然后在在这个容器中读取数据的,那么容器的唯一需要的功能便是读取自定义控件。当然为了区别asp:Panel,我们仍需要定义一个接口,让容器控件实现这个接口,接口的功能只有一个,得到所有自定义控件。
public interface IBoundContainder
{
IList<IBoundControl> GetControls();
}
public class xPanel:Panel,IBoundContainder
{
public IList<IBoundControl> GetControls()
{
return this.GetControls(this);
}
public IList<IBoundControl> GetControls(Control container)
{
List<IBoundControl> list = new List<IBoundControl>();
foreach (Control control in container.Controls)
{
if (control.HasControls())
{
list.AddRange(this.GetControls(control));
}
else if (control is IBoundControl)
{
list.Add((IBoundControl)control);
}
}
return list;
}
}
到此,xPanel与xTextBox已经完成,接下来会讲解如何利用自定义控件根据业务需求去完成增、改、查、显示功能。