|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article demonstrates an approach for developing a library of server controls, called "action tags" here, separating the functionality of an action from its user interface. The tags themselves are objects inheriting from the A base class BackgroundTo some degree, this approach has been conceived with HTML scripters in mind. A couple of years ago I had the experience of migrating the department I had just joined, from ColdFusion to ASP, then to ASP.NET for web applications. In my brief time working with ColdFusion, I came to appreciate its appeal for scripters, using a tag approach consistent with HTML to provide functionality. There is a lot that a non-programmer can accomplish with that platform. Being a programmer, I prefer the power and flexibility provided with ASP.NET, but I ask the question: can ASP.NET be functional for non-programmers too, in the way ColdFusion is? Take for example, the action of binding the results of a query to a The base class - ActionTagControlAll action tags in this library will be based on the abstract class 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;}
}
The constructor for an // constructor
public ActionTagControl() : base() {
// default to the Click event; inheritors can change this
// through their own constructors
_triggerEvent = "Click";
}
The base class also defines a 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;}
}
One abstract method is defined in the base class: // inheriting ActionTags must supply an EventAction method
protected abstract void EventAction(Object o, EventArgs e) ;
The real work performed in the base class, and its usefulness, comes in its overriding of the 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);
}
The // 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;
}
I decided to write this Returning to the // 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);
These three lines deserve further explanation. The // 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);
}
At this point we have a base class that implements the association of an action tag with a triggering event of a user interface control. Inheriting classes need only override the Here is a simple but complete example of an action tag inheriting from public class SimpleAction : ActionTagControl {
static int i;
protected override void EventAction(Object o, EventArgs e) {
i++;
this.Page.Response.Write(i);
}
}
As with any custom server control, the tag becomes useable on a web page through the <%@ 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>
Inheriting class Example 1: ShowAndHideActionA more practical example of a 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;
}
}
}
Note that this inheriting The following is an example of a .aspx page that demonstrates the <%@ 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>
A more complex example of the use of the Inheriting class Example 2: SelectorAnother example of an The // create a constructor to override the _triggerEvent value
public Selector() : base() {
_triggerEvent = "SelectedIndexChanged";
}
To ensure that the associated panels (or controls) are all available on the page, the 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);
}
}
}
}
The 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);
}
}
}
An example of the usage of the Inheriting class Example 3: QueryThe 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();
}
}
As in the base class, There are many ways in which this This example .aspx page demonstrates the use of the <%@ 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"
/>
The source file query.aspx shows an additional example using a SummaryThis article demonstrates an alternative approach to sub-classing user interface controls for encapsulating reusable functionality: the development of a library of action tags. The implemented tags are based on an abstract
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||