Dealing with "Sys.InvalidOperationException: Handler was not added through the Sys.UI.DomEvent.addHandler method."
August 13, 2007
In the ListSearch extender I recently had a bug where you got an exception when leaving a page that had a ListBox on it, where the ListBox was the target of both a CascadingDropDown extender and a ListSearch extender.
The reason was pretty dumb– I was calling $clearHandlers(listBox) in the dispose method on the ListSearch Extender, and this was removing the handlers that the CascadingDropDown had set up. So when the CascadingDropDown’s dispose method tried to clean up its event subscriptions, the ASP.NET AJAX runtime complained that it was not subscribed.
The solution was replace calls to $addHandlers and $clearHandlers with individual calls to $addHandler and $removeHandler for each individual event in the ListSearch’s initialize and dispose methods.
Unfortunately after implementing this solution I still got the same error … after a lot of head-scratching, it turned out to be a simple typo. I was calling $addHandler with ‘keydown’ as the event name, and calling $removeHandler with ‘keyDown’ as the event name.
A couple of things for you to check if you get the same error.
Dynamically creating ASP.NET AJAX controls
June 26, 2007
In my previous post I showed how you can cache large HTML elements, such as SELECTs in the web browser’s document cache. What if you want to dynamically create and attach ASP.NET AJAX Control Extenders to the dynamically created SELECT?
I’ve already shown how you can call extenders from JavaScript, but if you want to create and delete them from scratch, you can use functions such as these:
function AddListSearchTo(list) { list.listSearch = $create(AjaxControlToolkit.ListSearchBehavior, { 'promptCssClass' : 'listSearchPrompt' }, {}, {}, list); } function RemoveListSearchFrom(list) { if(list.listSearch ) { list.listSearch .dispose(); list.listSearch = null; } }
I use an expando on the target SELECT to point to the ListSearch Extender that I’ve dynamically created, which means that I can easily get hold of it if I want to remove it.
As you can see you can easily pass initial properties too. The $create function is a shortcut to Sys.Component.create
I use these functions when dynamically fetching HTML SELECTs from the server, in response to user input — I’ve rolled my own CascadingDropDown. When I get the new SELECT back from the server the first thing I do is remove the existing extender from the existing SELECT, then I replace the old SELECT with the new one that I’ve just received, and then I dynamically instantiate a new ListSearch extender:
function OnGetListSucceded(list) { var theListDiv = $get('TheListDiv'); // Contains TheList var theList = $get('TheList'); // An HTML SELECT RemoveListSearchFrom(theList); theListDiv.innerHTML = list; theList = $get('TheList'); AddListSearchTo(theList); }
Sometimes we have to do things that we know are wrong.
For example, in my current project we have to present a list of customers so that the user can select one or more of them. I work for a large company that has been around for many many years, and the list of customers can exceed 15,000 for some countries.
But I can’t decide which ones should be shown, and which ones should not be shown. Instead I need to show the complete list to the user. What is painful is that it can take a bit of time for this list to be sent down to the browser. It would be much better would be if the customer list could be cached on the browser’s document cache. This is what this blog post is about.
Note that in order for this to work, the list is presented as a simple HTML SELECT, and the selected items need to be processed correspondingly on the server using the Request.Form[SelectName] property.
In the ASPX page, I define my customer list like this:
<div id="ListDiv"> <select id="List" multiple="multiple" size="15" disabled="disabled"> <option>Loading List ...</option> <option>This may take a moment the first time...</option> </select> </div>
Note that the SELECT is surrounded by a DIV, so that I can replace the DIV contents with a new list.
Then I have some JavaScript that fires when the page loads, and asynchronously fetches the list:
<script language="javascript" type="text/javascript"> // This is called by the ASP.NET AJAX Framework automatically function pageLoad() { var wRequest = new Sys.Net.WebRequest(); wRequest.set_url('ListHandler.ashx?Version=<%=ListVersion.Value%>)'); wRequest.set_httpVerb("GET"); // GETs can be cached, POSTs can not wRequest.add_completed(OnFetchListCompleted); wRequest.invoke(); }
The pageLoad function gets invoked automatically. It simply fires off an HTTP GET request to a handler, passing as a parameter the value of the ListVersion hidden field. By changing the value of this field, the server-side code can control when the browser-cached contents are expired, and a new version is fetched (since changing the URL will mean there is no browser-cached version).
When the response comes back, it replaces the contents of the DIV with the value that is returned from the handler:
function OnFetchListCompleted(executor, eventArgs) { var listDiv = $get('ListDiv'); if(executor.get_responseAvailable()) { var list = executor.get_responseData(); if(list && list.startsWith('<select')) { listDiv.innerHTML = list; } } } </script>
The handler simply generates the appropriate content:
<%@ WebHandler Language="C#" Class="ListHandler" %> using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Collections.Generic; public class ListHandler : IHttpHandler { public void ProcessRequest (HttpContext context) { context.Response.Clear(); context.Response.ContentType = "text/html"; context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.Cache.SetExpires(DateTime.Now.AddMonths(1)); context.Response.Cache.SetSlidingExpiration(true); HtmlTextWriter htmlTextWriter = new HtmlTextWriter( context.Response.Output); ListBox listBox = GetListBox(); listBox.RenderControl(htmlTextWriter); htmlTextWriter.Flush(); context.Response.End(); } private static ListBox GetListBox() { ListBox listBox = new ListBox();
...
return listBox;
}
...
Note the bolded directives to enforce the browser-side caching of the response.
I have a complete example here.
You might be wondering why I’ve used the Sys.Net.WebRequest mechanism with the HttpHandler. Why not simply use a Web Service? That would indeed be simpler, however there is no easy way to set the appropriate cache expiration headers, although it is possible if you are willing to use reflection — the PageFlakes guys use it to speed up their pages.
When a ListBox that has an associated ListSearch Extender is first used, the ListSearch Extender does some one-time initialization.
It delays this work this for very good reasons, as Ted Glaza explains here.
The extender needs to decide whether it can use the very fast binary search when the user types, or whether it must use the slower linear search. It does this by determining if the list is sorted, which can take a long time if there are thousands and thousands of entries in the ListBox.
The best thing would be to somehow give the ListSearch Extender a hint as to whether the ListBox is already sorted or not.
You can do this by making a couple of changes to the ListSearch extender. If you’d like to see this behaviour become a standard part of the ListSearch Extender, please vote for it and I’ll add it if there is sufficient interest.
Modifying the ListSearch Extender is extremely simple. First download the AJAX Control Toolkit, and then open the AjaxControlToolkit.sln
Open the behaviour JavaScript file “ListSearchBehavior.js”. At the top of the file you’ll see some properties.
Add a couple of new property:
// Properties
this._isSortedHintSet = false;
this._isSortedHint = false;
…
These indicate whether the user has told the extender if the list is sorted or not, and if so, whether it is sorted.
Next find the “_isSorted” function, and at the start of it (after the documentation comment), add this code:
if(this._isSortedHintSet) {
return this._isSortedHint;
}
…
Finally you need to expose this property so that it can be set from outside the class, although you could actually set the _isSortedHintSet and _isSortedHint from JavaScript, this is considered very bad form since the underscore prefix indicates they are private.
Define a getter and setter accessor at the end of the bevahior, where you’ll find the other accessors:
get_isSortedHint : function() {
/// <value type=”AjaxControlToolkit.ListSearchPromptPosition”>
/// Where the prompt should be positioned relative to the target control.
/// Can be Top (default) or Bottom
/// </value>
return this._isSortedHint;
},
set_isSortedHint : function(value) {
if (!this._isSortedHintSet || this._isSortedHint != value) {
this._isSortedHintSet = true;
this._isSortedHint = value;
this.raisePropertyChanged(’isSortedHint’);
}
}
If you add these two functions after the existing last function (which is “set_raiseImmediateOnChange” in the current build), then you must be sure to add a comma after the closing brace of this last function:
…
},
get_isSortedHint : function() {
….
Build the solution, and make sure projects that use the AJAX Control Toolkit now reference the AjaxControlToolkit.dll in AjaxControlToolkit\AjaxControlToolkit\bin\Debug
Now you can set this property in JavaScript when your page first loads, so that the extender does not have to work out whether the list is sorted:
<form id="form1" runat="server"> <asp:ScriptManager ID="SM1" runat="server" /> <asp:ListBox ID="LB1" runat="server"> <asp:ListItem>Hello</asp:ListItem> </asp:ListBox> <cc1:ListSearchExtender ID="LSE1" runat="server" TargetControlID="LB1"></cc1:ListSearchExtender> <script language="javascript" type="text/javascript"> function pageLoad(sender, args) { var extender = $find('LSE1'); if(extender) { extender.set_isSortedHint(true); } } </script> </form>
This is the complete code (no changes to the code-behind file).
The pageLoad function is called automatically by the ASP.NET AJAX Framework (see http://ajax.asp.net/docs/overview/AJAXClientEvents.aspx) when all controls within the page have been loaded.
The function first finds the ListSearch Extender, and then gives it a hint that the list that it is targeting is actually sorted.
If you wanted to be able to set the property from the server side, instead of from JavaScript, you can edit the ListSearchExtender.cs file, and add the appropriate declaration of the property:
[ExtenderControlProperty]
[ClientPropertyName("isSortedHint")]
[DefaultValue("")]
public string IsSortedHint
{
get { return GetPropertyValue("isSortedHint", ""); }
set { SetPropertyValue("isSortedHint", value); }
}
Hiding the ListSearch Extender Prompt Message
June 19, 2007
You can hide the ListSearch Extender’s prompt message, so that it does not show, by setting the style to “display:none”, like this.
Define a CSS class in a StyleSheet”StyleSheet.css”:
.DontShowListSearchPrompt
{
display:none;
}
Then in your ASPX use the Style for the ListSearch Prompt:
<head runat=”server”>
<title>Don’t show ListSearch Extender Prompt</title>
<link href=”StyleSheet.css” rel=”stylesheet” type=”text/css” />
</head>
<body>
<form id=”form1″ runat=”server”>
<asp:ScriptManager ID=”SM1″ runat=”server” />
<asp:ListBox ID=”LB1″ runat=”server”>
<asp:ListItem>Hello</asp:ListItem>
</asp:ListBox>
<cc1:ListSearchExtender ID=”LSE1″ runat=”server”
PromptCssClass=”DontShowListSearchPrompt“
TargetControlID=”LB1″>
</cc1:ListSearchExtender>
</form>
</body>
</html>
Automatically attaching Extenders to Controls using Control Adapters - demo posted
February 28, 2007
I get a lot of hits on this article I posted last year, showing how you can use ASP.NET Control Adapters to automatically attach Control Extenders to a specific kind of ASP.NET control throughout an existing web site, without modifying any of the existing pages.
It uses a Control Adapter to create and attach extenders to the target controls, and also an HttpModule to ensure that all pages have a ScriptManager or ScriptManagerProxy.
Anyway, I finally got around to uploading a demo project showing this. It is slightly different to the article, since it shows how to attach a DropShadow extender to all UpdatePanels on your site. Not something you will necessarily want to do, but you get the point.
The demo project is here. It consists of a class project containing the Control Adapter and Http Module, and then a sample web site with a .browsers file to cause the adapter to be used, and a modified web.config to let the Http Module do its stuff.
Calling AJAX Control Extenders from JavaScript code
February 28, 2007
I am a big fan of declarative programming. It reduces the risk of errors, saves on mundane coding and lets me say what I want to do and someone else can figure out the how (although I like to understand the how too :-).
That said, when you find yourself doing strange and unnatural things simply to be able to use declarative programming then you know you’ve gone too far.
I found myself in this kind of situation recently. I have a very large GridView and for one of the columns I wanted to display a ModalPopup whenever the user clicked on the corresponding item in each row, where they could select information related to that item.
What I could have done is to hook up an instance of the ModalPopup in the template, so that for each row a new ModalPopup would be created.
Instead what I did was to define a single ModalPopup outside of the GridView, targeted at a dummy hidden LinkButton (with no label):
<asp:LinkButton ID=”DummyHiddenLinkButton” runat=”server”></asp:LinkButton>
<cc1:ModalPopupExtender ID=”SelectYxzModalPopupExtender“
runat=”server”
TargetControlID=”DummyHiddenLinkButton”
PopupControlID=”SelectYxzPanel”
OkControlID=”SelectYxzOKButton” >
</cc1:ModalPopupExtender>
Then in the template I hooked up the onclick method of the item to invoke a JavaScript function passing as a parameter key information related to the item.
Inside my JavaScript function I first initialize the Panel with information related to the item (in my case by invoking a page method), and then I programatically cause the ModalPopup to show:
function OnItemLinkClicked(someId) {
PageMethods.SomePageMethod(someId,
OnSomePageMethodSucceded,
OnSomePageMethodFailed);
}
function OnSomePageMethodSucceded(result) {
var extender = $find(’SelectYxzModalPopupExtender’);
var someList = $get(’SomeListInTheModalPanel’);
if(!extender || !someList) { …
}
someList.options.length = 0;
for(var i = 0; i < result.length; i++) {
var option = document.createElement(’OPTION’);
option.text = result[i].Text;
option.value = result[i].Value;
someList.options.add(option);
}
extender.show();
}
I’ve shown my call to a backend page method for completeness. I’m invoking a page method to get an array of structs related to the item that the user clicked, and then using the resulting array to populate a SELECT that I display within the modal popup.
But the interesting stuff is in bold. Here I am programatically finding my ModalPopup extender (using the $find shortcut to the Sys.Application.findComponent method), and then invoking its show method to cause it to display. The hidden LinkButton that is its “official” target never actually gets used.
I discovered what methods were available on the ModalPopup extender by looking at its source, which is available for all the control extenders when you download the AJAX Control Toolkit. Methods that are prefixed with an underscore are internal methods and I’d never call those, but I’m pretty comfortable calling the other methods, such as the handy show method above.
Last Tuesday I gave a presentation in Geneva on ASP.NET AJAX and the AJAX Control toolkit, and this morning I created a one hour screencast of the presentation available here (25MB) zipped, or here (33MB) as a WMV.
I tried to make it useful for someone that has at least dabbled in ASP or ASP.NET, and wants a quick bootstrap into Microsoft’s AJAX offerings.
It includes introductions to:
- AJAX
- ASP.NET AJAX including coding demos of the UpdatePanel (including a look behind the scenes), Web Services, and the JavaScript extensions
- The AJAX Control Toolkit including coding demos of using a couple of the control extenders
- Creating AJAX Control Extenders including a coding demo of creating your own extenders
It is encoded at quite a high level of compression so the sound is a little distorted but perfectly understandable.
Note:I recorded this around 6am this morning, and around 55 minutes into it my three year old and five year old sons decided to make their own (very sweet) contributions to the screencast — please forgive my momentary distraction, and when you hear me tell my three year old that he can take the money he found: he is talking about two small coins I gave him yesterday. This is not an attempt at bribary (honest!).
Many thanks to Atif Aziz and Dominique Kuster for organizing last week’s presentation, and Dicomp Acadamy Suisse Romande for hosting it.
Dealing with “Assembly x contains a Web resource with name x, but does not contain an embedded resource with name x. “
February 22, 2007
I got the go-ahead to move the ListSearchExtender from the prototype branch of the AJAX Control Toolkit to the development branch, and everything was going swimmingly until I got this error when running:
Assembly ‘AjaxControlToolkit, Version=1.0.10201.0, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e’ contains a Web resource with name ‘AjaxControlToolkit.ListSearchBehavior.js’, but does not contain an embedded resource with name ‘AjaxControlToolkit.ListSearchBehavior.js’.
I checked, and yes my ListSearchBehavior.js file did have its Build Action property set to Embedded Resource so the solution wasn’t that easy.
Next I fired up the ildasm tool that comes with the .NET SDK and double-clicked on the manifest. Here I got a clue as to what was happening. It showed that the embedded resource was called AjaxControlToolkit.ListSearchBehavior.ListSearchBehavior.js whereas all the others were called things like AjaxControlToolkit.DropShadow.DropShadowBehavior.js
I went digging in my code and sure enough, I’d screwed up my assembly and ClientScriptResource attributes. This is the corrected version (the bold bits used to say ListSearchBehavior):
[assembly: WebResource("AjaxControlToolkit.ListSearch.ListSearchBehavior.js", "text/javascript")]
namespace AjaxControlToolkit
{
…
[ClientScriptResource("AjaxControlToolkit.ListSearchBehavior", "AjaxControlToolkit.ListSearch.ListSearchBehavior.js")]
…
public class ListSearchExtender : ExtenderControlBase
Hopefully this post will save others that are banging their heads against the wall with the same error.
Initializing PopupBehavior in your AJAX Control Extender initialize method won’t work
February 21, 2007
In the ListSearchExtender which I am currently working to contribute to the AJAX Control Toolkit I use the PopupBehavior to display a DIV next to the ListBox (rendered as a SELECT) which initially has a message such as “Type to search…”. This is then replaced as the user types characters, with the characters they have typed.
If the user sets the ListBox to have the default focus (via the form’s defaultFocus property) then I want to show the Prompt DIV immediately, which I initially tried doing in my control extender’s initialize method. This didn’t work, and the reason is that there is a second initialization pass that takes place for the PopupBehavior, that is called by the framework, which hides the element (my DIV) associated with the PopupBehavior. This second pass takes place after my extender’s initialize method is called.
The workaround I used was to register a method to be called after all controls are initialized. That would be the Sys.Application.load method:
this._applicationLoadDelegate = Function.createDelegate(this, this._onApplicationLoad);
Sys.Application.add_load(this._applicationLoadDelegate);
My delegate gets called after all scripts have been loaded and the objects in the application have been created and initialized.
Article: Cross-browser ASP.NET AJAX Control Extender support - trickier than I thought
February 12, 2007
I’ve just had an article on DotNetSlackers published on porting ASP.NET AJAX Control Extenders to multiple browsers.
Porting a control extender that I am working to contribute to the ASP.NET AJAX Control Toolkit turned out to far more involved than I initially thought it would be. If you are a hardcore JavaScript developer then a lot of this may be familiar. But if you are like me, and JavaScript is just one of many technologies you are using, read on to learn from my mistakes.
More at:
http://dotnetslackers.com/articles/ajax/CrossBrowserAjaxControlExtenderTips.aspx
Geneva Presentation on 20th Feb: Enhance Your Web Applications by Using and Building ASP.NET AJAX Control Extenders
January 24, 2007
I’ll be presenting in Geneva, Switzerland at 7:00pm on 20th Feb:
In a presentation consisting almost entirely of live demonstrations, you’ll not only see how you can easily use Microsoft’s AJAX Control Extenders to make your web sites more responsive and interactive, but also how you can package up your existing JavaScript into reusable control extenders. This is not just about eye-candy, but rather about making your web applications easier and quicker to use.
Full details here: http://www.dotmugs.ch/events/event.aspx?eid=53
Anyone can attend, at no charge. This is an update of a well received similar presentation I delivered in Zurich last year.
Damian
Article: The UpdatePanel opened: what happens behind the scenes?
December 19, 2006
I’ve just had an article published on DotNetSlackers about what happens behind the UpdatePanel.
“The UpdatePanel is one of the coolest features of Microsoft’s ASP.NET AJAX. It magically lets you enhance your web sites with AJAX goodness, with very little cost to you as a developer. If you are like me though, you’ll be feeling just a little bit uncomfortable simply dragging the UpdatePanel onto your web form, and then letting it do its stuff. We want to understand what it is doing on our behalf.
In this article I’ll walk through what happens in the browser when an UpdatePanel updates. You’ll see what events are fired, what intervention points there are where you can intercept the action, and generally get a feel for the sequence of events.”
More at http://dotnetslackers.com/articles/atlas/The_UpdatePanel_opened_what_happens_behind_the_scenes.aspx
The presentation I gave last night in Zurich on ASP.NET AJAX and Control extenders on was very well attended, – some great feedback too. Thank you to all those that turned out so close to Christmas!
Atif gave a very good presentation before mine, on REST, JSON, Jayrock and more. Some thought-provoking stuff.
Many thanks also to Atif for organizing the event, and Digicomp/Matt Fasola for hosting it.
My presentation can be downloaded here. The real content was in the demos though.
Presentation for Swiss .NET group on 18th Dec 2006: Enhance Your Web Applications by Using and Building ASP.NET AJAX Control Extenders
December 6, 2006
I’ll be presenting in Zurich, Switzerland at 7:30pm on 18th Dec:
In a presentation consisting almost entirely of live demonstrations, you’ll not only see how you can easily use Microsoft’s AJAX Control Extenders to make your web sites more responsive and interactive, but also how you can package up your existing JavaScript into reusable control extenders. This is not just about eye-candy, but rather about making your web applications easier and quicker to use.
Full details here: http://www.dotmugs.ch/events/event.aspx?eid=50
Anyone can attend, at no charge.
Damian
Tip/Trick: Using ScriptManager ScriptPath to load MicrosoftAjax.js from file system
November 27, 2006
This isn’t particularly tricky but it can be non-obvious.
If you don’t want to use the embedded resources to serve the Microsoft AJAX JavaScript runtime, but instead you want to have them served from the file system, to make JavaScript debugging more pleasant, then this is what you need to do:
Create a directory structure under your web application’s root directory
You’ll need to create a directory structure that corresponds to the attributes of the assembly that normally contains the script files. In your case you’ll can create a subdirectory structure in your web site like this:
Scripts\Microsoft.Web.Extensions\1.0.61025.0
The top directory doesn’t have to be called Scripts you can call it anything you wish. Since MicrosoftAjax.js is in the Microsoft.Web.Extensions assembly, this is why the second directory level is called Microsoft.Web.Extensions. The final directory is the version number of the DLL. You can see this from your web.config:
<compilation debug="false"> <assemblies> <add assembly="Microsoft.Web.Extensions, Version=1.0.61025.0, ..."/> </assemblies> </compilation>
Copy the ASP.NET AJAX JavaScript runtime files
When you installed ASP.NET AJAX you’ll have had the ASP.NET AJAX JavaScript source files installed, by default into:
C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\ScriptLibrary\Debug
and
C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\ScriptLibrary\Release
Copy MicrosoftAjax.js from the Debug directory into your Scripts\Microsoft.Web.Extensions\1.0.61025.0 directory, and rename it to Microsoft.Web.Resources.ScriptLibrary.MicrosoftAjax.debug.js. Then do the same for MicrosoftAjaxTimer.js and MicrosoftAjaxWebForms.js following the same pattern of adding “Microsoft.Web.Resources.ScriptLibrary.” to the start of the filename, and then adding “.debug” in front of the “.js”.
Also copy the same files from the Release directory but this time rename them without adding the “.debug” in front of the “.js”.
Now the source files will be picked up whether or not you are running in debug mode.
Set the ScriptPath on your ScriptManager
Finally, set the ScriptPath property on ASP.NET ScriptManager to tell it to source the ASP.NET AJAX runtime from your new directory:
<asp:ScriptManager ID="sm1" runat="server" ScriptPath="~/Scripts"> </asp:ScriptManager>
Verifying that the JavaScript files are being served from files
If you do a “View Source” in Internet Explorer you should see the JavaScript files being included from the appropriate location:
<script src="Scripts/Microsoft.Web.Extensions/1.0.61025.0/ Microsoft.Web.Resources.ScriptLibrary.MicrosoftAjax.js" type="text/javascript"></script>
You can download the free TraceJS program here. Run it, select the Internet Explorer instance you are interested in, and you’ll be able to see all JavaScript it executes. If you get an error starting it up, it is likely you do not have Microsoft’s Active Scripting Debug API installed. You can download the Microsoft Script Debugger, which will install it, here.
Sometimes I get the germ of an idea and I won’t let go until I have implemented it. In this case I wanted to see every line of JavaScript code that was being executed inside Internet explorer. There are plenty of library routines that let you instrument your code, but I wanted something different.
I wanted to actually hook into Internet explorer somehow. A debugger API seemed like the obvious way to go. But which debugger the API? There are so many to choose from. In the end I discovered that the Active Scripting API was what I needed. Unfortunately it’s been a long time since anyone has used that it API. Especially from .NET. There were a smattering of articles on the web but none did exactly what I wanted.
In order to use it from .NET I needed a type library in order to generate a COM Interop assembly. Eventually I found one in an old version of the Internet explorer SDK. Having generated to the Interop assembly I found I needed to make some corrections. So I generated an IL file corrected it and regenerated the assembly.
For a long time when I first used the API it did not seem to be working. I could not connect to Internet Explorer. The call to connect failed every time. I eventually discovered that it had actually worked the first time and that subsequent attempts to connect were failing because there was already a debugger attached. DOH!
From there on it was pretty plain sailing. As soon as I attached the debugger I requested a breakpoint. I received a call back when the breakpoint occurred and then logged all the information about the current statement including the filename, the function name, the current stack depth, the line number, and of course the current chunk of JavaScript that was being executed. This is the callback code that gets executed when a breakpoint occurs:
// Called on breakpoint, which is for each statement in the target app public void onHandleBreakPoint(IRemoteDebugApplicationThread prpt, tagBREAKREASON br, IActiveScriptErrorDebug pError) { try { // Properties of the current breakpoint which we will be fetching string fileName; string functionName; string code = ""; // Various interfaces and structs used for active script debugging IEnumDebugStackFrames enumDebugStackFrames; tagDebugStackFrameDescriptor debugStackFrameDescriptor; IDebugCodeContext debugCodeContext; IDebugDocumentContext debugDocumentContext; IDebugDocument debugDocument; // Get the function name from the first stack frame prpt.EnumStackFrames(out enumDebugStackFrames); uint stackFrameCount; enumDebugStackFrames.RemoteNext(1, out debugStackFrameDescriptor, out stackFrameCount); if (stackFrameCount != 1) { return; } debugStackFrameDescriptor.pdsf.GetDescriptionString(0, out functionName); // Get the file name debugStackFrameDescriptor.pdsf.GetCodeContext(out debugCodeContext); debugCodeContext.GetDocumentContext(out debugDocumentContext); debugDocumentContext.GetDocument(out debugDocument); debugDocument.GetName(tagDOCUMENTNAMETYPE.DOCUMENTNAMETYPE_URL, out fileName); // If we are not in the same file and function as previous // breakpoint, then find the stack depth if (fileName != lastFile || functionName != lastFunction) { stackDepth = 0; while (stackFrameCount == 1) { tagDebugStackFrameDescriptor tmpDebugStackFrameDescriptor; enumDebugStackFrames.RemoteNext(1, out tmpDebugStackFrameDescriptor, out stackFrameCount); stackDepth++; } } // The DebugDocumentText lets us get at the document text IDebugDocumentText ddt = debugDocument as IDebugDocumentText; if (ddt == null) { return; } // Find out the location of the chunk of code is currently // being stepped through uint charPosition = 0; uint nrChars = 0; ddt.GetPositionOfContext(debugDocumentContext, out charPosition, out nrChars); // Find out what line it is on uint lineNr = 0; uint offsetInLine; ddt.GetLineOfPosition(charPosition, out lineNr, out offsetInLine); // Get the chunk of code that is being stepped through. if (nrChars >= 0) { if(nrChars > codeBuffer.Length - 1) { nrChars = (uint)codeBuffer.Length - 1; } uint charsReturned = 0; ddt.GetText(charPosition, codeBufferPtr, IntPtr.Zero, ref charsReturned, nrChars); code = new String(codeBuffer, 0, (int)charsReturned); } // Tell any listeners about the new trace message TraceEventArgs traceEventArgs = new TraceEventArgs(fileName, functionName, stackDepth, (int)lineNr, code); OnTraceStep(traceEventArgs); } catch (Exception ex) { Console.WriteLine(ex); } finally { try { // Resume the process, indicating we want to break at the next available point debuggableApplication.RemoteDebugApplication.ResumeFromBreakPoint(prpt, tagBREAKRESUME_ACTION.BREAKRESUMEACTION_STEP_INTO, tagERRORRESUMEACTION.ERRORRESUMEACTION_AbortCallAndReturnErrorToCaller); } catch (Exception ex) { Console.WriteLine("Error resuming: " + ex); } } }
Of course this has an impact on performance, but it does let you see every line of code that has run. It can be quite illuminating to trace through the Microsoft ASP.NET AJAX runtime and see exactly how much work it does on your behalf. You can also use it to see if your JavaScript code is doing what you think it is doing, without having to step through it in the debugger.
For example this is the trace from the first update panel example in the ASP.NET AJAX documentation.
I put a simple graphical interface on top of the program:
You can download the program here, and the complete source is available here.
I can imagine that this could be extended to include filtering, displaying parameters, code coverage and possibly performance metrics but as it stands I think it’s already quite useful to see every line of code that has run without having to manually step through the debugger. I’m happy to contribute it to CodePlex if anyone wants to take it further.
ScreenCast: How to create and use an ASP.NET AJAX Control Extender
November 17, 2006
In this 13 minute video I walk through creating and deploying an example ASP.NET AJAX Control Extender.
The example extender automatically disables buttons when they are clicked, to stop people clicking them twice (for example on a purchase form). Implementing this isn’t as obvious as you might think, and involves cloning.
I do absolutely everything from scratch including writing the server-side and browser-side code, and creating a web site to test the extender.
The video can be viewed/downloaded here.
Please make sure you have sound playing since I talk through a lot of what I am doing.
They have confusingly similar names, but how can ASP.NET AJAX Control Extenders and ASP.NET Control Adapters be usefully combined?
Imagine that you have discovered a new ASP.NET AJAX Control Extender that adds a “must have” behaviour to a standard ASP.NET control, for example a ListSearch Extender that lets users search incrementally within ASP.NET ListBox controls. You are under strict instructions to apply the extender to all instances of the ListBox control throughout your large existing web site that was created long before ASP.AJAX lept onto the scene. What can you do?
One option would be to spend a few days going through the whole site adding the AJAX extender to your pages.
A second option might be invest five minutes reading this article. You’ll see how you can use Control Adapters to automatically add the AJAX behavior to all instances of the ASP.NETcontrol, without modifying a single page.
First though, a quick introduction to Control Adapters and Control Extenders.
What are ASP.NET Control Adapters?
ASP.NET Control adapters “shadow” a target ASP.NET control type and let you intercept the target’s event lifecycle, substituting your own code for the the standard control implementation. They are configured declaratively in a “.browser” file, with no coding required to deploy them.
The poor target control doesn’t even know that it’s event lifecycle has been hijacked and possibly replaced. Which is good — you can enhance a standard ASP.NET control such as a ListBox silently just by adding the control adapter in a browser file. A typical use for control adapter is to render the target control using different HTML than it uses by default, such as using CSS instead of tables.
What are ASP.NET AJAX Control Extenders?
ASP.NET AJAX Control Extenders are something completely different. A Control Extender typically adds a specific kind of JavaScript behaviour to specific kinds of controls. The cool thing about extenders is that they can be used and configured on the server side, using the standard ASP.NET designer.
Combining Adapters and Extenders could lead to a world of pain
A Control Adapter could totally confuse a Control Extender, by rendering HTML that is not at all what the Control Extender expected.
For example in my ListSearch Extender I assume that the ListBox and DropDown controls are rendered using the HTML SELECT tag. If they are rendered as something else by a Control Adapter that targets them, then my Extender will break.
Control Adapters can pull the rug from the feet of Control Extenders by generating different HTML to that which the Extender expects … the only thing to do here is to code definsively and check that the generated HTML DOM object is as expected.
Control Adapters can automatically add Control Extenders to specific controls
A more productive way of combining the use of Control Adapters and Control Extenders might be to intercept the lifecycle of ASP.NET Controls to automatically add the Extender to the page when the control is used.
This example is based off of the ListSearch extender I introduced previously. In fact the example involves modifying the project created in that article.
There are two steps:
- Creating and deploying a ControlAdapter to add the Extender
- Ensuring that all pages have an AJAX ScriptManager by creating and deploying a custom HttpModule
Creating and deploying the ControlAdaper
Creating and deploying a control adapter is simplicity itself. You create the adapter by deriving from the System.Web.UI.WebControls.Adapters.WebControlAdapter class, overriding the methods that you are interested in. Then you tell the ASP.NET runtime about your adapter by creating a browser definition file in a special App_Browsers application subdirectory.
I added a new class to my existing ListSearch project. I called the class ListBoxAdapter. Because I wanted to add the ListSearchExtender I overrode the CreateChildControls method to add the ListSearchExtender as a child of the target ListBox.
This is the ListBoxAdapter code:
namespace ListSearch { public class ListBoxAdapter : System.Web.UI.WebControls.Adapters.WebControlAdapter { protected override void CreateChildControls() { ListSearchExtender listSearchExtender = new ListSearchExtender(); listSearchExtender.TargetControlID = this.Control.ID; listSearchExtender.PromptText = "Click to search ..."; listSearchExtender.PromptCssClass = "listSearch"; this.Control.Controls.Add(listSearchExtender); base.CreateChildControls(); } } }
In order to ensure that my new ControlAdapter was picked up on the web site, I right clicked on the web site using the Solution Explorer, selected “Add New Item” and then chose “Browser File” from the list of templates. I said OK when asked if I wanted to create the special App_Browsers subdirectory.
I used these contents for the browser file:
<browsers> <browser refID="Default"> <controlAdapters> <adapter controlType ="System.Web.UI.WebControls.ListBox" adapterType="ListSearch.ListBoxAdapter" /> </controlAdapters> </browser> </browsers>
This file basically says that I want to use my new ListBoxAdapter with the standard ListBox class, and I want to use it for all browsers. There is a great deal more to browser files than this — for example you could apply the adapter to specific browser types that you know it will work with.
Having built the control adapter and set up the browser file, all instances of the ListBox will now have an associated ListSearchExtender so that they can be incrementally searched. There is however a problem. Before any ASP.NET AJAX Control Extenders can be used, there must be one (and only one) ScriptManager instance on the page.
Using an HttpModule to ensure all pages have an AJAX ScriptManager
An HttpModule is an instance of a class that sits outside of the standard ASP.NET page framework. It operates at a lower level, and can interact with the processing of an Http request by the IIS web server. To create the module I added a new Class called ScriptManagerAddModule to the ListSearch project, with these contents:
using System; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using Microsoft.Web.UI; namespace ListSearch { public class ScriptManagerAddModule : IHttpModule { public void IHttpModule.Dispose() { } // This is where you can indicate what events in the request processing lifecycle you want to intercept public void IHttpModule.Init(System.Web.HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(HttpApplication_PreRequestHandlerExecute); } void HttpApplication_PreRequestHandlerExecute(object sender, EventArgs e) { HttpApplication httpApplication = sender as HttpApplication; if (httpApplication != null) { Page page = httpApplication.Context.CurrentHandler as Page; if (page != null) { // When standard ASP.NET Pages are being used to handle the request then intercept the // PreInit phase of the page's lifecycle since this is where we should dynamically create controls page.PreInit += new EventHandler(Page_PreInit); } } } void Page_PreInit(object sender, EventArgs e) { Page page = sender as Page; if (page != null) { // ScriptManagers must be in forms -- look for forms foreach (Control control in page.Controls) { HtmlForm htmlForm = control as HtmlForm; if (htmlForm != null) { // Look for an existing ScriptManager or a ScriptManagerProxy bool foundScriptManager = false; foreach (Control htmlFormChild in htmlForm.Controls) { if (htmlFormChild is ScriptManager || htmlFormChild is ScriptManagerProxy) { foundScriptManager = true; break; } } // If we didn't find a script manager or a script manager proxy then add one if (!foundScriptManager) { htmlForm.Controls.Add(new ScriptManager()); } } } } } } }
As you can see from the comments, the module intercepts the PreRequest phase of the processing of Http requests.
In the PreRequest event handler it checks if the Http Handler that is processing the request is the standard System.Web.UI.Page class (which implements IHttpHandler) . If it is then it hooks into the PreInit phase of the Page’s event lifcycle.
In the PreInit event handler it checks if the page’s form already has a ScriptManager or ScriptManagerProxy (new to the second beta of ASP.NET AJAX) and if not, it adds a ScriptManager. This way a page can add its own ScriptManager with its own specific properties (for example to enable partial rendering), otherwise it could let the HttpModule add a minimalistic ScriptManager.
In order to deploy the new ScriptManagerAddModule the application’s web.config must be modified to add the new module to the ScriptModules section:
<httpModules> <add name="ScriptManagerAddModule" type="ListSearch.ScriptManagerAddModule"/> </httpModules>
Summary
In this article you have seen how you can deploy an AJAX Control Extender to an existing web site to modify the behaviour of specific kinds of ASP.NET Controls, without modifying a single page.
Do this by first creating and deploying an ASP.NET Control Adapter which “shadows” instances of the target control type, and adds an instance of the ASP.NET AJAX Control Extender to the page for each instance of the target control type.
Secondly create and deploy an HttpModule which intercepts the PreInit phase of each page, and adds an AJAX ScriptManager to the page if it doesn’t already have one.
Now your users can enjoy site-wide enhancements without you having to change a line of code (or aspx), and if you want to remove functionality for any reason you can simply delete the “.browser” file that hooks up the Control Adapter.
About the author
I am an independent consultant based out of Geneva, Switzerland currently specializing in ASP.NET/C#/SQL Server.
In my spare time I recently created the PromptSQL SQL Intellisense tool, acquired in April 2006 by Red-Gate Software, and released as SQL Prompt 2.0.
I also created the J-Integra Java-COM bridge, which involved writing an entire DCOM/DCE-RPC protocol stack over TCP/IP in pure Java. I sold J-Integra to Intrinsyc Software in Jan 2001 and served as their Chief Software Architect until Jan 2004. J-Integra’s many customers include BEA Software - they bundle J-Integra with their WebLogic application server.
I can deliver ad-hoc and longer term consulting, and am keen to speak at conferences and seminars in English or French - please contact me for a list of previous speaking engagements.
Contact me by email by adding damian in front of atadore.com, with an @ in between.
Convert your scraps of script to reusable AJAX Control Extenders
November 13, 2006
If you have been developing web applications over the last few years, then chances are you’ll have put together a whole set of JavaScript code to enhance your users’ experience when using your sites, adding behavior that HTML cannot provide.
In this article you’ll see how Microsoft’s AJAX Control Toolkit gives you a fantastic opportunity for you to repackage that JavaScript code into reusable components that can be used within the server-side ASP.NET framework, so that you can easily share your JavaScript behavior with other developers without them having to get into the guts of your JavaScript code and script libraries.
If you want to run through the steps I describe here, you’ll need to download and install the ASP.NET AJAX kit, and the AJAX Control Toolkit. There is a download link at the end of the article, from which you can download the complete code.
Before diving into the details of the AJAX Control Toolkit, I’d like to first share with you a situation will probably strike a chord.
A couple of years ago I had a user of our web site that complained that the standard behavior of the ASP.NET ListBox control, which by default renders as an HTML SELECT, was broken.
As far as she was concerned when she typed a sequence of characters there should be an incremental search within the contents of the ListBox, rather than the default behavior which simply jumps to the first element that starts with each character. If you type “mi” in a ListBox then you’ll end up at “IBM” rather than “Microsoft”:
So I put together some JavaScript code which added the behavior she was looking for:
I added a couple of event handlers to detect when the user gives the SELECT focus, and to detect when they typed characters within the SELECT. When the user gives the SELECT the focus I create a DIV with a prompt that tells them that they can search, and then when they type characters I replace the prompt message with the characters that were typed, and I select the first item that starts with the characters that were typed.
This had nothing to do with ASP.NET AJAX, or the Atlas Control Toolkit – it was just plain old JavaScript.
There are a few issues with this implementation:
- Firstly, whenever any I or any other developers want to use this, they’ll have to hook up the JavaScript event-handlers, which can be painful when dealing with server-side controls such as the ASP.NET ListBox, which generates the SELECT.
- Secondly, if you wanted to customize the behavior in some way, perhaps to use a different prompt message, or to apply a CSS style to the DIV, then you’d have to edit the JavaScript. Yuck.
- Finally, if I wanted to ensure that this behavior worked properly across all browsers then I’d have to do a lot of work – for example different browsers have subtly different ways of hooking up event handlers.
Microsoft’s ASP.NET AJAX and the Atlas Control Toolkit address all of these issues.
Of course you probably won’t have implemented exactly this behavior, but chances are you’ll have written other scraps of JavaScript to do other things to make your users lives easier. I’ll show you how easy it is to repackage code into an AJAX Control Extender.
Creating an AJAX Control Extender
Once you’ve installed the AJAX Control Toolkit, you’ll need to double-click on the AjaxControlExtender.vsi file in the AjaxControlExtender subdirectory under the kit. Doing this will install a Visual Studio Template which you can use to create AJAX Control Extenders.
If you then fire up Visual Studio 2005, and hit File|New|Project you will be presented with the option of creating a new ASP.NET AJAX Control Project. Select this option and give your project a suitable name – I’ve used ListSearch. Once you hit OK you should find yourself with a project with three files:
- The first file, ListSearchBehavior.js, is where JavaScript code that actually implements your behavior will sit.
- The second file, ListSearchDesigner.cs, is used to ensure that the extender works well within the Visual Studio design-time environment. You can safely ignore this file.
- The third and final file, ListSearchExtender.cs, is the server-side of the equation. This is where you can specify what kind of ASP.NET control your extender will target, and what properties your extender will expose.
The Server Side
If we start first on the server side in ListSearchExtender.cs, then there were two things wanted to do. I wanted to indicate what kind of ASP.NET control my extender would target, and I wanted to indicate the properties my extender exposes.
In my case I wanted my ListSearch extender to target both ASP.NET ListBoxes and also ASP.NET DropDowns. Both of these classes derive from the ListControl class. I modified the generated class to specify the appropriate target control instead of the default, which is the top level Control class:
[Designer(typeof(ListSearchDesigner))] [ClientScriptResource("ListSearch.ListSearchBehavior", "ListSearch.ListSearchBehavior.js")] [TargetControlType(typeof(ListControl))] public class ListSearchExtender : ExtenderControlBase {
I also wanted to indicate what properties I wanted to be available on my Extender. A couple of useful properties might be the text of the prompt message, and the CSS class to be used for the prompt message. These properties will be available on the server-side on the ASP.NET design surface. There is an example property created by default. I deleted it and replaced it with the two properties I wanted to use. The first was called PromptText, and looks like this:
[Description("The prompt text displayed when user clicks the list")]
[DefaultValue("Type to search")]
[ExtenderControlProperty()]
public string PromptText
{
get
{
return GetPropertyStringValue("PromptText");
}
set
{
SetPropertyStringValue("PromptText", value);
}
}
I used the standard ComponentModel Description attribute so that the property has a description when people use it in the designer, and I marked it with the ExtenderControlProperty attribute so that the AJAX Control Toolkit knows to transfer it down to my JavaScript code.
I also indicated the default value. Please note that this is not the default value that the property will get automatically set by the AJAX Control Toolkit if the user does not specify a value in the ASP.NET designer. You’ll still need to initialize this property to the default value in the JavaScript code. What the DefaultValue attribute does, is tell the AJAX Control Toolkit runtime what the default value you have given the property is on the browser side, so that it knows when it doesn’t need to pass the property value down to the browser - in other words, an optimization.
The implementation of the properties calls into the AJAX Control Runtime, since it knows how to pass the property values over to the browser.
I also created a second property called PromptCssClass, along the same lines:
[Description("CSS class applied to prompt when user clicks list")]
[DefaultValue("")]
[ExtenderControlProperty()]
public string PromptCssClass
{
get
{
return GetPropertyStringValue("PromptCssClass");
}
set
{
SetPropertyStringValue("PromptCssClass", value);
}
}
That is all that I needed to do on the server side.
The Browser Side
In order to implement my browser-side functionality I edited the ListSearchBehavior.js file.
The first thing I did here is to set up the JavaScript side to handle the properties I just declared on the server side. The default boilerplate generated JavaScript has a handy TODO indicating where the properties have to be inserted. I replaced the default _myPropertyValue initialization with my two properties:
this._promptCssClass = null; this._promptText = 'Type to search';
Further down there is a second TODO where the accessors for my properties can be defined. Again, I replaced the generated example accessor with my own:
// Property Accessors get_PromptText : function() { return this._promptText; }, set_PromptText : function(value) { if (this._promptText != value) { this._promptText = value; this.raisePropertyChanged('PromptText'); } }, get_PromptCssClass : function() { return this._promptCssClass; }, set_PromptCssClass : function(value) { if (this._promptCssClass != value) { this._promptCssClass = value; this.raisePropertyChanged('PromptCssClass'); } }
These will be called by the framework when the behavior is first initialized, with the values specified in the designer on the server side..
Now came the fun part – actually implementing my behavior.
The initialize method is called by the framework to let my behavior initialize itself. The default implementation simply calls into the base class. In my case I wanted to also hook up event handlers on the generated HTML SELECT so that I know when the user clicks on the control, etc:
initialize : function() { ListSearch.ListSearchBehavior.callBaseMethod(this, 'initialize'); $addHandlers(this.get_element(), { 'focus' : this._onFocus, 'blur' : this._onBlur, 'keydown' : this._onKeyDown, 'keypress' : this._onKeyPress }, this); },
As of Beta 2 of the ASP.NET AJAX it is much easier to wire up event handlers. In prior versions a separate call to $addHandler had to be made to add each event, and each handler method had to be wrapped in its own delegate. The get_element call gets a reference to the HTML SELECT object, to which the event handlers are being hooked up. The second parameter indicates the names of the events, and the corresponding event handler methods. The final parameter indicates which object should be the this when the event handlers are called — if you leave it out then the SELECT DOM object itself will be the this.
Note that I’m calling a utility function called $addHandlers provided by ASP.NET AJAX to handle the adding of the event handlers. This means that ASP.NET AJAX has to deal with all the nastiness associated with different browser implementations of event hookup.
What goes up, must come down, and since I’ve set up all these event handlers in the initializer then it stands to reason that I should tear them down when the behavior unloads. Indeed this is what I do in the dispose method:
dispose : function() { $clearHandlers(this.get_element()); ListSearch.ListSearchBehavior.callBaseMethod(this, 'dispose'); },
Once again, the second ASP.NET AJAX beta has made life a lot easier. Whereas previously we had to call $removeHandler for each event handler, there is now one handy call that releases the lot.
What is left is the actual event handlers that do the work.
The _onFocus and _onBlur methods create and delete the DIV with the prompt text and the text typed so far:
_onFocus : function(e) {
// Create a DIV that shows the text typed so far
var element = this.get_element();
this._promptDiv = document.createElement('div');
this._promptDiv.innerText = this._promptText;
this._promptDiv.showingPromptText = true;
if(this._promptCssClass) {
this._promptDiv.className = this._promptCssClass;
}
this._promptDiv.style.left = element.offsetLeft +
element.offsetWidth + 10;
this._promptDiv.style.top = element.offsetTop;
document.body.appendChild(this._promptDiv, element);
},
_onBlur : function(e) {
// Remove the DIV showing the text typed so far
document.body.removeChild(this._promptDiv);
this._promptDiv = null;
},
This took care of creating and hiding the DIV, but I also needed to handle keystroke related events. The _onKeyDown is called before the _onKeyPress event, which I also handle. The reason we handle the _onKeyDown is to remove the prompt message, if I am showing it, and also to handle the user pressing the backspace key, since this isn’t passed to the keyPress event. Note that there are now symbolic names for the keys such as backspace so that you don’t have to hardcode the corresponding constant (8).
_onKeyDown : function(e) { var element = this.get_element(); var promptDiv = this._promptDiv; if(promptDiv.showingPromptText) { promptDiv.innerText = ""; promptDiv.showingPromptText = false; } // Backspace not passed to keyPressed event, so handle it here if(promptDiv.innerText.length > 0 && event.keyCode == Sys.UI.Key.backspace) { promptDiv.innerText = promptDiv.innerText.substring(0, promptDiv.innerText.length - 1); this._searchForTypedText(element, promptDiv.innerText); } },
The _onKeyPress simply adds the character that was typed to the promptDiv, and searches the target SELECT:
_onKeyPress : function(e) { // Add key pressed to the displayed DIV and search for it this._promptDiv.innerText += String.fromCharCode(e.charCode); this._searchForTypedText(this.get_element(), this._promptDiv.innerText); e.preventDefault(); e.stopPropagation(); },
By calling the preventDefault and stopPropagation methods on the event object we let ASP.NET AJAX handle the subtle differences in browser handling of the cancellation of event propagation so that the default behavior of going to the entry starting with the last character that was typed is not invoked.
Finally the _searchForTypedText method is a simple (but very inefficient!) search. Note that by wrapping the option text in the ASP.NET AJAX String type I could call its handy startsWith method.
_searchForTypedText : function(element, searchText) { var options = element.options; var text = searchText.toLowerCase(); for(var index = 0; index < options.length; index++) { var optionText = new String(options[index].text.toLowerCase()); if(optionText.startsWith(text)) { options[index].selected = true; return; } } },
I then built my new ListSearch extender project.
Using the new extender
To use my new AJAX Control Extender, I added a new “ASP.NET AJAX Enabled Project” to my solution, which I called “ListSearchWebSite”. I then went to the design view of the Default.aspx page that was created, and looked at the toolbox. In my case there was a tab created automatically called ListSearchExtender, but it was empty. I had to right-click on the tab, select “Choose Items …” and the click “Browse…” and browse to the ListSearch.dll that I built. Once I did this my ListSearchExtender appeared in the tab.
I dragged a ListBox Control onto the Default.aspx and went to its properties to add a few items to the ListBox under the Items property. I added “Compaq”, “Digital Equipment Corp”, “HP”, “IBM”, “Maplin”, “Microsoft” and “Motorola”.
I then dragged the ListSearchExtender over to the Default.aspx page and set its TargetControlID property to the ListBox’s ID:
Once I’d done this I could go to the properties of the ListBox itself and set the properties of the newly associated ListBoxExtender:
To make the prompt message display a little better, I added a StyleSheet.css item to the web project and set that as the StyleSheet property of the Default.aspx document. These are the simple contents of the style sheet — it just defines the listSearch CSS class used for the PromptCssClass property.
.listSearch { position:absolute; background-color:Orange; font-family:Lucida Console; font-weight:bold; }
Now I had my behavior packaged up in a simple reusable form that can easily be configured within ASP.NET, without other developers having to know anything about the JavaScript implementation under the hood.
Summary
The complete ASP.NET Solution I’ve shown above can be downloaded here.
You’ve seen how straight-forward it is to create AJAX Control Extenders and package up JavaScript behavior so that it can be reused by developers without their having to get to grips with JavaScript.
There is however one additional step you can take. You can contribute your Control Extenders to the AJAX Control Toolkit so that they become part of the standard kit. There is a complete test framework and more. Please leave a comment if you’d like me to cover this additional step (assuming they accept this control extender) in a new article.
About the author
I am an independent consultant based out of Geneva, Switzerland currently specializing in ASP.NET/C#/SQL Server.
In my spare time I recently created the PromptSQL SQL Intellisense tool, acquired in April 2006 by Red-Gate Software, and released as SQL Prompt 2.0.
I also created the J-Integra Java-COM bridge, which involved writing an entire DCOM/DCE-RPC protocol stack over TCP/IP in pure Java. I sold J-Integra to Intrinsyc Software in Jan 2001 and served as their Chief Software Architect until Jan 2004. J-Integra’s many customers include BEA Software - they bundle J-Integra with their WebLogic application server.
I can deliver ad-hoc and longer term consulting, and am keen to speak at conferences and seminars in English or French - please contact me for a list of previous speaking engagements.
Contact me by email by adding damian in front of atadore.com, with an @ in between.






