65.9K
CodeProject 正在变化。 阅读更多。
Home

Action 标签

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (37投票s)

2003年10月6日

CPOL

8分钟阅读

viewsIcon

124709

downloadIcon

1720

一种将功能封装在服务器控件中,与用户界面分离的方法。

ActionTags

引言

本文介绍了一种开发服务器控件库的方法,这里称为“动作标签”,它将动作的功能与其用户界面分离开来。标签本身是继承自System.Web.UI.Control类的对象,并带有AttachTo属性,指示关联的用户界面控件。例如,执行查询的代码可以被封装为一个<Query>动作标签,然后附加到一个DataGridDropDownList控件上。或者,可以将会从Click事件触发的代码封装在一个标签中,然后附加到一个ButtonLinkButtonImageButton上。这是一种与通过子类化用户界面控件来添加功能不同的方法,并且可以提供一种将封装的代码与触发事件链接的一致方式。

将描述一个基类ActionTagControl,它定义了这些动作标签将如何工作。System.Reflection命名空间中的对象用于检查用户界面控件的事件和属性信息。本文还将展示三个工作示例,演示继承类,用于创建<ShowAndHideAction>标签、<Selector>标签和<Query>标签。

背景

在某种程度上,这种方法是为HTML脚本编写者构思的。几年前,我有幸将我刚加入的部门从ColdFusion迁移到ASP,然后迁移到ASP.NET来开发Web应用程序。在我短暂接触ColdFusion的经历中,我体会到了它对脚本编写者的吸引力,它使用了一种与HTML一致的标签方法来提供功能。非程序员可以使用该平台完成很多事情。作为一名程序员,我更喜欢ASP.NET提供的强大功能和灵活性,但我有一个问题:ASP.NET能否像ColdFusion那样,也对非程序员有用?

以将查询结果绑定到DataGridDropDownList控件的动作为例。虽然这是一个简单的操作,代码量不大,但仍然需要一些编程代码。除了几行代码,处理连接和数据集类以及页面事件仍然需要比HTML脚本编写者更适合程序员的概念理解。我能理解ColdFusion平台的吸引力,因为它提供了一个<Query>标签来处理这些细节,让脚本编写者无需关心。如果程序员开发类似的标签库,ASP.NET应该是一个适合非程序员的平台。本文提供了一种这样的方法。

基类 - ActionTagControl

本库中的所有动作标签都将基于抽象类ActionTagControl。基类提供了一种将动作分配给控件的一致方法。由于动作标签本身不提供用户界面,因此基类继承自System.Web.UI.Control而不是WebControl。触发控件的ID——也就是说,触发标签所代表的动作的控件——被实现为一个字符串属性AttachTo

protected string _attachTo = "";

// AttachTo property - the id of the

// control whose event will trigger this action

public string AttachTo {
    get {return _attachTo;}
    set {_attachTo = value;}
}

ActionTagControl继承类的构造函数通常会设置触发事件;如果没有指定,基类将在其自身的构造函数中将Click事件设置为默认值。

// constructor 

    public ActionTagControl() : base() {
        // default to the Click event; inheritors can change this

        // through their own constructors

            _triggerEvent = "Click";
    }

基类还定义了一个TriggerEvent公共属性,以便标签用户可以在适当的情况下覆盖该事件。

protected string _triggerEvent;

    // TriggerEvent property - the event which will trigger this action;

    // typically setting this would not be necessary as inheriting classes

    // will establish the default _triggerEvent in their constructors

    public string TriggerEvent {
        get {return _triggerEvent;}
        set {_triggerEvent = value;}
    }

基类中定义了一个抽象方法:EventActionEventAction方法是当在附加控件上触发事件时将执行的代码。该方法与System.EventHandler委托具有相同的签名。

// inheriting ActionTags must supply an EventAction method

protected abstract void EventAction(Object o, EventArgs e) ;

基类中实际执行的工作及其有用性来自于它对Control.OnInit事件的重写。在这里,会检查并定位AttachTo控件ID。如果找不到这样的控件,将抛出异常。如果控件找到但缺少适当的触发事件,也将抛出异常。OnInit函数的第一部分处理这些情况。

protected override void OnInit(EventArgs e) {

    // is an AttachTo attribute specified?

    if (_attachTo.Length == 0) {
        string msg = "The 'AttachTo' attribute "
            "is required.  Specify a control"
            " in the AttachTo attribute.";
        throw new Exception(msg);
    }

    // attempt to locate the specified control            

    Control c = LocateControl(_attachTo);
    if (c == null) {
        string msg = string.Format("A control with" +
           " id='{0}' could not be found.  Specify " +
           "a control in the AttachTo attribute.", _attachTo);
        throw new Exception(msg);
    }

OnInit事件中使用的LocateControl方法如下所示:

// look for the control identified by sID

protected Control LocateControl(String sID) {
    Control c;
            
    // look in this control's naming container first

    c = this.FindControl(sID);
            
    // if not found, look in the naming

    // container of the parent of this control

    if (c == null && this.Parent != null) {
        c = this.Parent.FindControl(sID);
    }
            
    // if still not found then look in

    // the naming container for the Page itself

    if (c == null && this.Page != null) {
        c = this.Page.FindControl(sID);
    }

    // at this point we either found the

    // control or c is null; return it in either case

    return c;
}

我决定编写这个LocateControl函数,而不是直接使用Control类的FindControl方法,是为了允许在动作标签在网页上的放置方式上具有更大的灵活性。FindControl会在调用它的控件的同一命名容器中按ID定位控件。如果页面上存在所需的控件,但它位于动作标签的命名容器之外,FindControl将无法识别它。LocateControl也定义为protected而不是private,以便继承类可以使用它。

回到OnInit方法——其接下来的三行代码演示了如何使用System.Reflection类来获取一个表示触发事件本身的对象的实例。

// attempt to get the attached control's

// triggering event through reflection

System.Type t = c.GetType();
BindingFlags bf = BindingFlags.Instance | 
     BindingFlags.Public | BindingFlags.NonPublic;
EventInfo ei = t.GetEvent(_triggerEvent, bf);

这三行代码值得进一步解释。BindingFlags对象允许设置影响反射搜索成员方式的选项。一旦控件的类型作为System.Type对象获得,其GetEvent方法就会用于获取一个System.Reflection.EventInfo对象。EventInfo对象提供了对事件元数据的访问,并公开了AddEventHandler方法。通过使用AddEventHandlerActionTagControlEventAction代码就与触发事件关联起来了。

// attempt to attach our EventAction to the appropriate control's event

if(ei != null) {
    ei.AddEventHandler(c, new System.EventHandler(this.EventAction));
} 
else {
    string msg = string.Format("The control '{0}' " +
               "does not have the {1} event. " +
               "Either specifiy a control with the " +
               "{1} event in the 'AttachTo' attribute, " +
               "or specify a different event in " +
               "the 'TriggerEvent' attribute.", 
               _attachTo, _triggerEvent);
    throw new Exception(msg);
}

此时,我们有了一个基类,它实现了动作标签与用户界面控件触发事件的关联。继承类现在只需要重写EventAction方法,就可以利用这种关联。

这是一个简单的但完整的动作标签示例,它继承自ActionTagControl,设计用于通过Click事件触发。由于Click已经在基类中设置为默认触发事件,因此子类不需要在其自己的构造函数中重写它。所有需要做的就是实现EventAction方法。

public class SimpleAction : ActionTagControl {
        
    static int i;
        
    protected override void EventAction(Object o, EventArgs e) {
        i++;
        this.Page.Response.Write(i);
    }        
}

与任何自定义服务器控件一样,该标签可以通过<%@ Register %>指令在网页上使用。然后将AttachTo属性赋值给ButtonLinkButtonImageButton或任何其他支持Click事件的用户界面控件的ID。

<%@ Register TagPrefix="actions" 
              Namespace="ActionTags" Assembly="ActionTags" %>
<html>
    <head><title>SimpleAction</title></head>
    <body>
        <form runat="server">
            <asp:Button id="btnTest" runat="server" 
                       text="Click Me!" />
            <actions:SimpleAction id="click1" 
                       runat="server" AttachTo="btnTest" />
        </form>
    </body>
</html>

继承类示例 1: ShowAndHideAction

一个更实用的基于Click事件的动作标签示例是ShowAndHideAction类。它定义了ShowHide属性,这些属性是逗号分隔的字符串,用于列出在触发Click事件时应该显示或隐藏的控件。这里的EventAction方法解析这些字符串,并相应地将每个单独控件的Visible属性设置为truefalse

protected override void EventAction(Object o, EventArgs e) {
    Control c;
            
    // parse the comma-delimited list of controls to hide

    if (_hide != "") {
        string[] hides = _hide.Split(',');
        for (int i=0; i<hides.Length; i++) {
            c = LocateControl(hides[i]);
            if (c != null) c.Visible = false;
        }
                
    }
            
    // parse the comma-delimited list of controls to show

    if (_show != "") {                
        string[] shows = _show.Split(',');
        for (int i=0; i<shows.Length; i++) {
            c = LocateControl(shows[i]);
            if (c != null) c.Visible = true;
        }
    }
                
}

请注意,这个继承的ActionTagControl使用了在基类中定义的受保护方法LocateControl

以下是一个.aspx页面的示例,演示了ShowAndHideAction附加到LinkButton控件上。

<%@ Register TagPrefix="actions" Namespace="ActionTags" 
                   Assembly="ActionTags" %>
<html>
    <head><title>ShowAndHideAction - Sample 1</title></head>
    <body>
        <form runat="server">
          <asp:LinkButton id="Hide" runat="server" 
                              text="Hide Text Boxes" />
          |
          <asp:LinkButton id="Show" runat="server" 
                              text="Show Text Boxes" />
          <br /><br />
                
          <asp:Textbox id="text1" runat="server" text="text1" />
          <asp:Textbox id="text2" runat="server" text="text2" />
          <asp:Textbox id="text3" runat="server" text="text3" />
                
          <actions:ShowAndHideAction 
             id="hide1" runat="server" attachTo="Hide"
             hide="text1,text2,text3" />
                
          <actions:ShowAndHideAction 
             id="show1" runat="server" attachTo="Show"
             show="text1,text2,text3" />
       </form>
    </body>
</html>

使用ShowAndHideAction标签的一个更复杂的示例在源文件showHide2.aspx中提供。

继承类示例 2: Selector

另一个ActionTagControl继承类的示例是Selector类。此操作设计用于由ListControl的选择触发,而不是按钮点击。列表中的每个项目都与一个Panel(或其他Control)相关联;Panel的可见性取决于它是否在列表中被选中。

SelectedIndexChanged事件在Selector类的构造函数中指定,覆盖了基类指定的Click事件。

// create a constructor to override the _triggerEvent value

public Selector()  : base() {
    _triggerEvent = "SelectedIndexChanged";
}

为确保所有相关的面板(或控件)都可以在页面上使用,OnInit方法被重写,其中包含查找每个控件的代码。如果找不到一个,将抛出异常。仍然会调用基类的OnInit方法,以确保动作标签与所需的ListControl相关联。

private Control[] _panels;

    // we'll override the OnInit method to locate each

    // panel (control) and throw an exception

    // if we can't find one

    // panels (controls) are identified

    // through the ListItem.Value property

    protected override void OnInit(EventArgs e) {
        // call the base method first

        base.OnInit(e);
            
        Control c = this.AttachedControl;
        if (c != null) {
            ListControl lc = (ListControl) c;
            _panels = new Control[lc.Items.Count];                
            for (int i=0; i<lc.Items.Count; i++) {
                _panels[i] = LocateControl(lc.Items[i].Value);
                if (_panels[i] == null) {
                    string msg = 
                         string.Format("Selector is attached to {0}," +
                         " which has a ListItem value of {1}.  " +
                         "A control with id='{1}' is not found.", 
                         c.ID, lc.Items[i].Value);
                    throw new Exception(msg);
                }
                    
            }
        }
            
    }

然后,此标签的EventAction简单地遍历附加控件的列表项,相应地设置相关面板的可见性。

protected override void EventAction(Object o, EventArgs e) {
    // loop through each of the items in the AttachedControl's item list

    // setting the selected panels' visibility to true or false

            
    Control c = this.AttachedControl;
    if (c != null) {
        ListControl lc = (ListControl) c;
        for (int i=0; i<lc.Items.Count; i++) {
            // set the panel's visibility

            _panels[i].Visible = (lc.Items[i].Selected);
        }
    }
}

在源文件panelSelector.aspx中提供了一个Selector动作标签用法的示例。请注意,附加的ListControlAutoPostBack属性应设置为true才能触发该事件。

继承类示例 3: Query

Query动作标签作为最后一个示例提供。它的目的是针对OLEDB数据源执行查询,并将结果绑定到任何支持DataSource属性的用户界面控件。Query标签继承自ActionTagControl,并定义了两个属性来支持每种功能:OLEDB连接字符串的标识和SQL语句。其构造函数定义PreRender作为其触发事件——因此,DataGridRepeater或其他支持DataSource的控件的PreRender事件就是执行SQL语句并绑定结果的地方。

    public class Query : ActionTagControl {
        
        private string _connectionString = "";
        private string _sql = "";
        
        public string ConnectionString {
            get {return _connectionString;}
            set {_connectionString = value;}
        }
       
        public string Sql {
            get {return _sql;}
            set {_sql = value;}
        }

        // create a constructor to override the _triggerAction value

        public Query()  : base() {
            _triggerEvent = "PreRender";
        }

        // the event action here is to execute

        // the query and bind the results

        protected override void EventAction(Object o, EventArgs e) {
            // get the connection and execute the query

            OleDbConnection con = new OleDbConnection(_connectionString);
            OleDbDataAdapter da = new OleDbDataAdapter();
            da.SelectCommand = new OleDbCommand(_sql, con);
            DataSet ds = new DataSet();
            da.Fill(ds);
            
            // bind the query results to the AttachedControl

            // we'll use reflection here to set the DataSource property;

            // an exception occurs if the target control does not have

            // a DataSource property.

            Type t = this.AttachedControl.GetType();
            PropertyInfo pi = t.GetProperty("DataSource");
            pi.SetValue(this.AttachedControl, ds, null);
            this.AttachedControl.DataBind();
            
            con.Close();            

        }
            
    }

与基类一样,这里使用System.Reflection类来探查附加控件是否具有DataSource属性,然后可以将其值设置为执行查询的结果。在这种情况下,将获取一个PropertyInfo对象并调用其SetValue方法。

Query动作标签有许多增强的可能性。例如,连接信息可以通过专门的连接文件获取,或者通过web.config文件作为应用程序参数获取。这个例子保持简单,以便专注于反射的元素,同时仍然具有功能性。

此示例.aspx页面演示了使用Query动作标签将数据从Microsoft Access表填充到DropDownList控件。

<%@ Register TagPrefix="actions" 
         Namespace="ActionTags" Assembly="ActionTags" %>
<html>
  <head><title>Query Examples</title></head>
  <body style="font-family: Tahoma;">
      <form>
          <asp:DropDownList id="dd1" runat="server" 
              DataTextField="Field1" DataValueField="Field2" />
      </form>
  </body>
</html>
    
<actions:Query id="myQuery" runat="server" attachTo="dd1" 
   connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data 
                Source=c:\inetpub\wwwroot\ActionTags\test.mdb"
   sql="SELECT Field1, Field2 FROM Table1"
/>

源文件query.aspx显示了使用DataGrid的额外示例。

摘要

本文介绍了一种替代子类化用户界面控件以封装可重用功能的方法:开发一个动作标签库。实现的标签基于具有AttachTo属性的抽象ActionTagControl类,并使用System.Reflection命名空间的EventInfo对象来定义一种将功能与触发事件相关联的一致方法。对于程序员来说,这种方法可以带来不需为相同功能对多个控件(例如ButtonLinkButtonImageButton)进行子类化的便利。开发和分发此类库也可能有助于使ASP.NET成为非程序员更实用的平台选择。

© . All rights reserved.