May 2004 - Posts

Google AdSense and ASP.NET View State
29 May 04 11:43 AM | Scott Mitchell

I recently decided to try out Google AdSense on a few personal sites I run - ScottOnWriting.NET, NBAWebLog.com, MP3Players101.com, skmMenu.com, and DataWebControls.com - mainly as an experiment to see the effectiveness of text advertising and what sort of revenue streams small, focused sites like these are capable of generating. (I'll be sure to post an analysis after having collected sufficient data.)

The nice thing about Google AdSense is that it's a breeze to setup. Once you have an AdSense account, you just choose a few options - colors for the text ads, if you want to serve text ads only or both text and image ads, and if you want to assign the ad to a “channel” (useful for tracking performance of ads on certain sites or pages) - and, based on these selections, you're given a snippet of client-side JavaScript code to add to the page(s) where you want to display the ads. AdSense automatically scans the content of your site to send targetted text ads, and you get some money whenever anyone clicks on one of your ads. The precise amount of money you receive depends on the ads served. Essentially, advertisers can bid on showing their ads for certain keywords, so your revenue depends on how much advertisers are willing to pay for your targetted customer's clicks.

In any event, I got Google AdSense up and running on all of the sites and all of them, save one (NBAWebLog.com), displayed targetted ads. NBAWebLog.com was displaying nothing but public service ads - Save the Rainforest, Become a Big Brother, and things like that. Reading up at AdSense, I learned that these public service ads are displayed until AdSense can successfully search and categorize the site's content. After these public service ads persisted for three days, I emailed the AdSense support staff, and asked them what was up. They came back and said that their spider could not classify my site due to a large amount of non-standard content near the beginning of the page.

I visited NBAWebLog.com, did a quick view source and found... a large amount of non-standard content - several KB of base64 encoded content in the __VIEWSTATE hidden form field. This view state information didn't need to be persisted - I wasn't handling postbacks at all from the front page - so I turned off view state from the @Page directive and everything worked out. I emailed back the Google AdSense staff, got my site respidered, and now it's showing the applicable ads.

On a slight tangent, I have an article on ASP.NET view state coming up on the MSDN ASP.NET Dev Center...

Latest MSDN Article: Making ASP.NET Web Sites Accessible
28 May 04 09:43 AM | Scott Mitchell

My latest MSDN article is now available, and focuses on Web site accessibility. Making ASP.NET Web Sites Accessible talks about accessibility guidelines - the WCAG and Section 508 - as well as Microsoft's quiet release of an ASP.NET HOTFIX that fixes a number of Section 508-related accessibility issues. The article also examines how to extend the ASP.NET Web controls to improve their accessibility. Specifically, the article illustrates how to improve the DataGrid class so that it will support sorting in browsers that do not support JavaScript.

In addition to this article, I also presented a talk on accessibility at the San Diego Day of .NET Conference back in April 2004. You can download the slides to this talk here.

Filed under:
DEV414: Black-Belt ASP.NET
27 May 04 03:53 PM | Scott Mitchell

Notes from Rob Howard's TechEd 2004 break-out session "Black-Belt ASP.NET: Tips and Tricks for your ASP.NET Applications." Rob is, as of the time of this writing, a Program Manager for Microsoft's Web Team. He has worked as an evangelist for Microsoft, did some great work on .NET Web services, etc. Quite the resume. The following are the notes from Rob's talk.

  • Building an Intermediate Page
    • Problem: Long running, expensive operation to prevent re-submit
    • Goal: Want to display temporary 'Please Wait' processing UI - client waits while server processes.
    • Solution: When user submits a long-running form, it submits to a different page, which displays the "Please Wait" graphic, while using an IFRAME to actually load the page that has the long-running query. Upon completion of the long running operation, the IFRAME is displayed in the page.
  • Set Focus
    • Rob presented a function SetFocus(), into which a user could pass in a Control ID and have the control receive focus. This function used a Page.RegisterStartupScript() call to add the necessary client-side JavaScript. (See also Andy Smith's FirstFocus control at MetaBuilders.)
  • OutputCaching
    • Tip: Make sure the HTML of your pages are not too large - impacts download speed. Aim to keep page size under 30 KB; Google's front page is 2 KB.
    • OutputCache can affect performance of application, even micro-caching (OutputCaching with a duration of just 1 second).
    • Demo: Showed page displaying a DataGrid with the contents of the Northwinds database. Used Application Test Center to view requests per second that could be handled by adding output caching. Adding a 60 second OutputCaching added requests per second being handled from 200 reqs./sec to over 500 reqs./sec. Changing to a 1 second duration still kept reqs./sec around 500. (Of course, practically, benefits of this approach can only be seen if there are more than 1 request per second.)
  • Per-Request Caching
    • Problem: There exists data that cannot be cached - data tied to user, for example.
    • Per-Request Caching: cache data for the lifetime of a request; different than Cache API.
    • Solution: Use HttpContext.Items collection; scoped to lifetime of HTTP request.
    • Benefit: Allows multiple server controls on a single page to share data among one another. For example, if various controls all access user data, can cache it in the HttpContext.Items collection so that only one control needs to actually access the data from the database. This technique is used with the ASP.NET Forums to provide better performance.
    • Used Application Test Center to show perf. gain of using HttpContext.Items; well, actually the demo didn't work, performance didn't improve. Bummer.
  • Reflection Caching
    • Problem: Reflection is expensive, 3-4 times the cost of normal object creation.
    • Solution: Cache the constructor of the class - 1/3 cost of non-cached. This technique is used in the ASP.NET Forums in teh Forums provider.
  • Returning Multiple Result Sets
    • Database operations are expensive and network I/O is costly.
    • Solution: You can batch result sets in one go, rather than requiring separate trips to the database. Use SqlDataReader.NextResult() to step through result sets.
    • Performance gains are most profound if the database and Web servers reside on separate machines.
    • Demo: Using Application Test Center, Rob showed not using multiple result sets provided about 40 reqs./sec; using multiple result sets improved the performance marginally, to 45 reqs./sec, or so.
  • SQL Record Paging
    • Problem: DataGrid default paging gets all database records, and only shows the specified subset - very inefficient.
    • Solution: Use DataGrid custom paging, and implement a stored procedure to get back the precise subset of records based on page number and records per page. (Rob's stored procedure built a temporary table, dumped the entire results in the database, and snipped out the appropriate set of records from the temp. table.)
  • Background Processing
    • Problem: Expensive operations can tie up the request thread.
    • Solution: Can use System.Threading.Timer to schedule background tasks. Can set a callback to happen every n milliseconds. Requires CLR AppDomain to be active. Uses thread from ASP.NET threadpool.
    • Implementation: Create an HTTP Module that has a static Timer class instance. In the module's Init event, create a new Timer class if it's null, specifying a callback function to call within some interval (like once every 10 seconds). The callback function should be passed in an HttpContext instance.
  • Database Cache Invalidation
    • Problem: Keep cache and database in sync.
    • Solution: Cache is invalidated on changes to the underlying data in the database. Will be a built-in feature of ASP.NET 2.0. Many implementations in ASP.NET 1.x today, but many have flaws. Current techniques with flaws include using an extended SQL stored procedure to send an HTTP request to the Web server - a potential bottleneck, since the SQL database is waiting on the Web server to respond (also doesn't work with Web Gardens). Another option is having an extended sproc touch a file on the Web server - another bottleneck.
    • A better approach uses a separate process that polls SQL server to see if the data has changed. (This is the technique used in ASP.NET 2.0's database cache invalidation.) Rob's demo used the background processing technique discussed above to poll the database once every 10 seconds.

As usual, Rob's talk was informative and interesting. The presentation is nice, as he gives a tip or trick, and then dives straight into a demo.

Filed under:
DEV392: Building Applications with Globalization in Mind
27 May 04 10:20 AM | Scott Mitchell

Notes from Michele Leroux Bustamante's TechEd 2004 break-out session "Building Applications with Globalization in Mind." (The slides and code for this talk should be available from her blog; see the comments for a direct link.) Michele is a globalization expert and has written an article on said topic on MSDN online (see Globalization Architecture for ASP.NET). For more information on globalization check out the Ask Dr. International or Microsoft .NET Internationalization sections on MSDN. The following is an outline of Michele's talk.

  • What is Globalization? (Separating culture dependencies from code; content translation).
  • Locale / Culture is represented by a language code and country code (i.e., en-US, en-UK, es-EC, etc.). There are ISO codes for language and country. RFC 3066 specifies tags for identifying languages.
  • What to Localize and How to Store Localized Content?
    • Static content - menu captions, control captions, Web page content, etc.
    • Dynamic content - content populated at runtime. Need to have location of content to be organized by culture, and things like queries or filenames may differ based on culture.
    • Things to consider:
      • Windows vs. Web app
      • How often the content changes
      • Where to place culture-specific information (resource files, XML files, database)

A Windows Forms assembly contains resource information. We can also have satelite assemblies that store resource information for a particular culture. These satelite assemblies can be dropped in independent of recompiling the application. In addition to satellite assemblies, some content will likely need to be placed in a database. The database table names should be named according to the ISO standard country/language codes - perhaps by suffixing the table name with code.

For Web apps, typically a site's pages are duplicated for globalized apps. That is, a set of Web pages for each locale supported. Usually implemented as subdirectories - www.yoursite.com/en/*, www.yoursite.com/es/*, etc. These directories would contain locale-specific translations, images, etc. A better approach is to use a single content base with satelite assemblies that contain the locale-specific information.

  • What are .NET Resources?
    • Resources are generated via an XML resource file (.resx).
    • Automatically generated by VS.NET for each Windows Form and Web Form
    • By default, compiled and embedded in project assembly
    • Can add custom resources to the project
    • Can build assemblies that contain shared resources
    • Can manually edit the .resx files via VS.NET
    • The resource information is stored in the assembly's manifest - can view this information via ILDASM.
  • Localizing Windows Forms applications
    • Can set a WinForm to Localizable via the Properties pane. This adds a ResourceManager in the code section.
    • Can also specify the language via the Language option in the Forms' properties pane.
    • The invariant culture is stored in the assembly - culture-specific information is stored in satelite assemblies. The satelitte assemblies are in appropriately named folders in the same directory as the project assembly.
    • Satelite assemblies contain only resources - no code.
    • Runtime follows a "resource fallback process" to find closest match - i.e., en-US rolls back to en rolls back to the invariant culture.
    • In addition to satelite assemblies with locale-specific information, could also have other assemblies with common error messages. To create these, build a Class Library project with no code, just resource information.
    • VS.NET / runtime are very picky on resource file naming conventions.
    • The satelite assembly used depends on the culture setting of the executing thread's culture information.
    • Can use the NeutralResourcesLanguageAttribute attribute to specify a default resource rather than relying on the runtime's fallback technique.
    • WinForms control properties automatically sotred in embedded resources. Allows translators toedit individual form layout without touching your code. Other content can be stored in shared resources.
  • Accessing Resources via System.Resources.ResourceManager
    • Access specific resource type based on a Form, strings, errors, etc. Specify the typeof ResourceManager and then can request resource entries by key name.
    • The GetString()/GetObject() methods gets the specific resource by name using the fallback technique. Can also use GetResourceSet() to get all resources for a particular culture.
    • The current thread's culture setting dictate resource selection via the CurrentUICulture and CurrentCulture properties. CurrentUICulture touches the satelite assembly to load resources; CurrentCulture does not access resource information, but dictates how things like date/time information is formatted, how currency is formatted, etc.
  • Localizing ASP.NET Web Applications
    • Option #1: Duplicate Content
      • Facilitate rapid 1.0 release, deal with translation later
      • Translate content for each culture in Web Forms, User Controls, HTML pages, XML files, etc.
      • Redirect user to localized set of pages via browser's accept language HTTP header
      • Duplicate content is error prone, since translators must edit content source.
      • Typically implemented via culture-specific directories - www.yoursite.com/en/*, www.yoursite.com/es/*, etc.
      • Can set culture and UI culture settings via <globalization> element in maching.config/Web.config; can also do it on a page-by-page basis via the @Page directive
    • Option #2: Single Content Base
      • One code base; low impact on future translations
      • Dynamic page content sotring locale-specific information in resource files, databases, XML files, etc.
      • XML resource files provided for Global.asax and each Web Form - embedded in main assembly. Michele recommends placing static page content in database rather than in resource for each Web page.
      • Browsers send an Accept-Language HTTP header; can use this to set the current culture on the thread rendering the ASP.NET page. Might also want to let user select culture, to override browser setting. Need to reset the thread's culture on each and every trip to the server.

Another great talk from Michele. Being an ASP.NET guy, I was most interested in the globalization for ASP.NET applications. The track looked at both WinForms and Web Forms applications, but focused more on WinForms, unfortunately (for me). Check out the blog's comments for a direct link to the slides and code.

The Cabanas - the Best Part about TechEd
26 May 04 01:28 PM | Scott Mitchell

My favorite aspect of TechEd, by far, are the Cabanas. There are over a dozen Cabanas located in one large room in the Convention Center that intersects two wings of the building where various talks are held. Each Cabana focuses on a different aspect of Microsoft technology - security, mobile devices, developer tools, etc. - and each is staffed by a small group of Microsoft employees that are there to answer questions, give presentations to groups of 10 to 20 folks, and basically interact with their customers. With the large sessions seating hundreds of individuals, it can be hard to ask a question or make a comment. The Cabanas, however, allow for direct interaction between Microsoft and TechEd attendees.

While the Cabanas are staffed by Microsoft folks, they are not a one-way mouthpiece for the company. People can come up to various Microsoftees working at the Cabanas and have a dialog. Ask questions. Bounce ideas around. Socialize. I even got to help someone on an ASP.NET problem. A fellow working at the Cabana was an expert at C#, but not at ASP.NET, and needed to help answer an attendee's question. So he walked around the Cabana area asking people if they knew ASP.NET and if they could help someone.

The Cabanas are also a great place to chill out. I don't care how intelligent and passionate you are about technology - after several hours of tech talks, for days on end, your brain starts to feel like soup. The Cabanas offer bean bag chairs and big ol' comfy couches to kick back at. I've seen a number of folks sleeping, likely having had too much fun in the nearby Gaslamp district the night before. To top it off, the Developer Tools Cabana even has three networked XBoxes (where I sank over an hour at yesterday), which can host up to 12 simultaneous players on Halo. To the right you can see Duncan Mackenzie enjoying the amenities.

I'm not at TechEd today, had training with a client this morning and have my ASP.NET class to teach this evening, but I will be there all day tomorrow... not sure if I'll be able to make it on Friday.

Filed under:
WSE 2.0 RTMs
24 May 04 05:01 PM | Scott Mitchell

Well, WSE 2.0 RTMed today, you can download it here. Just my luck that the class I teach tonight is the first in-depth WSE class, but all the demos and examples use WSE 2.0 Technology Preview! Drat.

Despite the setback for my students, it's good to see WSE 2.0 get out the door. I've yet to have had any chance to play with any bits more mature than WSE 2.0 TP, so I hope there is not too many breaking changes in the API between the TP and RTM.

Filed under:
First Day of TechEd
24 May 04 02:51 PM | Scott Mitchell

Today marks the start of TechEd 2004 here in San Diego. The temperature outside, unfortunately, is in the mid-60s all week. I usually wouldn't mind, but I hyped up San Diego so much in an earlier post that an out-of-towner who read that entry might feel a bit let down. Sadly I was unable to make it down to the Convention Center before noon - had some work I needed to get done that ended up taking a spot longer than I had anticipated. And once I wrap up this blog entry I'm going to be packing it up and heading back home, as I have a class to teach tonight.

Got some pictures of the Convention Center and Cabana areas, and will be posting them later. The Cabana areas are a nice venue - plush couches, XBoxes, Microsoft employees eager to talk with customers... much more intimate than the conference tracks that seat hundreds of attendees.

The Exhibitor Hall is also equally as impressive. Oodles of vendors hawking their wares using a variety of techniques. The most entertaining pitches are the companies that use magicians. It's hard to walk ten feet without being handed a slew of gizmos, brouchers, widgets, and business cards.

It's a drag that I've got to teach on Monday and Wednesday nights, since the events I'm most interested in attending happen in the evenings (the BOFs, smaller classes, etc.). It's nice to have a conference like this in your own back yard and all, but the downside is that you don't get to enjoy the after hours events nearly as much, since you have the work, family, and responsibilities that the other attendees got to leave behind for a week.

Oh well, best be making my way back home, I'll post some photos tomorrow.

Filed under:
Imagine Cup Nationals Results
23 May 04 10:58 PM | Scott Mitchell

As mentioned earlier, I was a judge for the nationals for Imagine Cup USA. The 1st place winner - which received $8,000 and an all-expense paid trip to Brazil to compete in the international competitions - was the team from UCLA working on Project PICKS, a “digital concierge” service. The concierge service could make recommendations for restaurants, shops, and entertainment based on a number of factors, such as the person's current geographical location, what activity they were interested in performing (going on a date, hanging out with friends, going on a business lunch, etc.), and their personality and age.

The second place winners, which took home $4,000, were from Washington University in St. Louis, Missouri. This team developed a system geared toward adaptive learning in a digital classroom. Two teams tied for third-place: a University of Illinois - Urbana Champaign team of one that designed a very usable Web services interface to LDAP, and a team from Arizona State University that created a system to better the lives of blind and visually impaired individuals by creating an interactive, mobile device that allowed the user to seemlessly interact with their environment through Web services.

For more information on the results, check out the official Imagine Cup US Nationals press release.

All in all, the presentations were wonderful, and there was obviously a lot of hard work put into the nine projects that made it to the nationals. It was an honor to serve as a judge at this competition and am positive the UCLA team will have an awesome showing at the internationals in July. Go Team USA! :-)

Tomorrow I'll be blogging from TechEd. I plan on taking the digital camera and showing to the world my complete lack of photojournalism skills.

A Disturbing Trend in RSS Syndication
21 May 04 10:26 AM | Scott Mitchell

While more and more “big media” Web sites are jumping aboard the RSS/ATOM bandwagon, what's disappointing is that many of them seem to be implementing RSS in a way I find disturbing. RSS has a <description> tag to give a synopsis about the content, so the consumer of the feed can determine whether or not he wants to read the content. Personally, I like it when the <description> contains the entire text of the syndicated content, as is done here on ScottOnWriting.NET.

Of course, many Web sites' revenues are realized by attracting eyeballs to view their ads, so these sites attempt to use syndication to garner a click-through to the Web site article. That's fine, I'm ok with that; I'm even ok with having ads in the RSS items (like a text blurb at the bottom of each item's description). What is annoying, though, is when a site omits the <description> altogether, providing just a <title> and <link>. Given this scenario, most RSS readers will automatically load the Web page specified in the <link>, making clicking on an RSS item in a reader tantamount to visiting the page.

This is a nuisance, and has prompted me to unsubscribe to those feeds that employ this technique. For example, U.S. News & World Report does this, and C|Net News just started doing this as well. While I respect the need for these sites to draw visitors to their Web site, I find it distasteful that they're omitting the content's synopsis in order to attain more page views.

Filed under:
Imagine Cup 2004 Nationals
21 May 04 08:50 AM | Scott Mitchell

The Imagine Cup 2004 Nationals for the United States is being held this weekend at the University of California - San Diego. I'll be one of seven judges at the competition on Sunday, with the winner being announced in the evening activities at Tech-Ed.

The Imagine Cup is a worldwide technology competition sponsored by Microsoft for college students. There are four “invitationals“:

  • Software Design - design a mobile device application using .NET that improves some aspect of everyday life.
  • Rendering - use DirectX 9.0 to show something cool in the world of 3D graphics.
  • Algorithm - an online competition for algorithm design and application.
  • Short Film - create a short film on the culture of innovation.

The nationals competition in San Diego is for the Software Design invitational. There are nine teams from nine universities around the country competing: UCLA, ASU, Boston University, Virginia Commonwealth University, University of South Carolina, Georgia State University, Washington University, UIUC, and George Mason University. The abstracts for these nine teams are available online as well.

The winners from San Diego will travel to Brazil for the Imagine Cup Internationals!

Should be fun and interesting to see what exciting applications these top teams have created.

Filed under:
A Stumper of an ASP.NET Question: SOLVED!
20 May 04 03:05 PM | Scott Mitchell

In my last blog entry I posed an ASP.NET / Repeater question that had me and a student of mine utterly stumped. I have been communicating with this student today, bouncing ideas back and forth, and he noted that if the Repeater's EnableViewState property was set to False, then the Button column raised the ItemCommand event, as expected. Now, all that the Repeater stores in its ViewState StateBag is the number of items in the Repeater (stored in a view state variable named _!ItemCount).

Basically, when the Repeater's DataBind() method is called, the following steps happen:

  1. The Repeater's base's Controls collection has its Clear() method called, cleaning out any controls in the Repeater's hierarchy.
  2. The child view state is cleared out.
  3. The CreateControlHierarchy() method is called, passing in True (indicating that the data should be bound from the DataSource).
  4. The ChildControlsCreated property is set to True. This property is a flag to indicate that the Repeater's control hierarchy has been built. Whenever the Repeater's Controls property is accessed, it first checks to make sure that the control hierarchy has been built by calling EnsureChildControls(). EnsureChildControls() checks to see if ChildControlsCreated is false; if it is, it calls CreateChildControls().

When a Button submits a Form, the Button's name and value attributes are sent along in the POST data to the Web server. After the LoadViewState stage in the page's life-cycle (which comes before the Load stage), the ProcessPostData stage transpires. This stage loads post data back into the appropriate Web controls, such as reloading the text from a textbox back into the Text property of a TextBox Web control. Specifically, the Page class's ProcessPostData() method enumerates all the POST names passed in and searches the control hierarchy for these controls to determine if they participate in this stage of the life-cycle. In accessing the control via FindControl(), the EnsureChildControls() method is called and the Repeater's CreateChildControls() method is invoked.

Now, what does the Repeater's CreateChildControls() method do? Well, it checks to see if the view state variable _!ItemCount is null or not. If it is not null, then it calls CreateControlHierarchy(), passing in False. This builds up the control hierarchy, creating the Button. This created Button is then the Button the Page class's ProcessPostData() method marks to have its Command event raised during the RaisePostBackEvent stage later on.

Hopefully the problem is becoming clearer now. After this ProcessPostData stage, the Load stage transpires, and the Repeater's DataBind() method is called. This clears out the control hierarchy and rebuilds it. Hence, the reference to the Button we had earlier has had it's RepeaterItem “detached” from the Repeater, replaced by a new RepeaterItem isntance from the call to DataBind(). Since it's this orphaned Button's Command event that is raised, the event cannot percolate up to the Repeater, and hence the Repeater's ItemCommand event does not fire.

The LinkButton works because clicking a LinkButton does not send the LinkButton's ID through the POST data. Rather, it passes this information along in the __EVENTARG hidden form field. This information, then, is not queried by the Page class until the RaisePostBackEvents stage, which happens after the Load event, after the Repeater's control hierarchy has been constructed.

So, one workaround is as follows: don't call a data Web control's DataBind() method each and every page load if you are using view state. If you are not using view state, then you'll need to call DataBind() on each and every page load.

My student, Matt, wrote in to share the following workaround as well (I've not tested it; might not work in all browsers):

My colleague Borys has proposed a fix. I don't think I can explain it
very well, but we think it has to do with a conflict between post data
and viewstate. I'll let the code speak for itself.

Private Sub DataList1_ItemDataBound(ByVal sender As Object, ByVal e As
System.Web.UI.WebControls.DataListItemEventArgs) Handles
DataList1.ItemDataBound

Dim b As Button = DirectCast(e.Item.Controls(1), Button)

Dim evTarget As String = b.UniqueID.Replace(":", "$")

Dim script As String = "__doPostBack('" + evTarget +
"','');return(false)"

b.Attributes.Add("onclick", script)

End Sub

Hope this helps someone, it was rather fun to examine and unearth the solution.

A Stumper of an ASP.NET Question
20 May 04 11:25 AM | Scott Mitchell

Last night a student of mine asked why the Button Web control in a Repeater's ItemTemplate does not raise the Repeater's ItemCommand event when clicked, but a LinkButton does. I was perplexed because from my understanding anytime a Command event is raised, a CommandEventArgs is bubbled up the control hierarchy. When the RepeaterItem class detects a bubbled CommandEventArgs, it stops the bubbling of the CommandEventArgs and starts bubbling up a RepeaterCommandEventArgs instance. Likewise, the Repeater listens for bubbled events, and in detecting a bubbled RepeaterCommandEventArgs, it raises its ItemCommand event. (This is the same sequence of steps that happens in the DataList and DataGrid.)

I asked this student to show me a code sample where this was the case at a break. He did and, sure enough, the problem existed as he described. Specifically, he had a Repeater like so:

<asp:Repeater runat=“server“>
<ItemTemplate>
<asp:Button runat=”server” CommandName=”foo” Text=”Button” />
<asp:LinkButton runat=”server” CommandName=”bar” Text=”LinkButton” />
</ItemTemplate>
</asp:Repeater>

Next, he created an event handler for the Repeater's ItemCommand event, and did a simple Response.Write() in the event handler. Finally, in the Page_Load event handler he added the code to bind the data to the Repeater on each postback. (That is, not just on the first page load...) Upon visiting the page, if the LinkButton was clicked, the ItemCommand event fired (as evidenced by the Response.Write() output, and by the debugger); clicking the Button, however, did not fire the ItemCommand event.

Clearly, this perplexed me, so I decided to spend some time researching this after class. The results I have found are very odd and run counter to my understanding of how the Repeater and ASP.NET works. I am posting this here in hopes that someone will know why this behavior exists. Let me describe my research.

I started by creating a simple ASP.NET Web page with the following HTML markup:

<asp:Repeater id="Repeater1" runat="server">
<ItemTemplate>
<asp:LinkButton ID="linkButtonTest" Runat="server" OnCommand="lCommand" CommandName="bar" Text="LinkButton"></asp:LinkButton>
<asp:Button OnCommand="bCommand" ID="buttonTest" Runat="server" CommandName="foo" Text="Button"></asp:Button>
<br />
</ItemTemplate>
</asp:Repeater>

Next I created a Page_Load event handler and BindData() method that looked like the following:

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
BindData()
End Sub

Private Sub BindData()
Dim a As New ArrayList
a.Add(1) : a.Add(2) : a.Add(3)
Repeater1.DataSource = a
Repeater1.DataBind()
End Sub

Notice that the data is bound to the Repeater on each and every Page_Load. If I placed the call to BindData() in Page_Load within an If statement so that it only ran when Not Page.IsPostBack, then the problems described above did not occur.

Following these two methods, I created event handlers for the Button and LinkButton's Command event, as well as an event handler for the Repeater's ItemCommand event.

Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
Response.Write(String.Concat("<b>ItemCommand</b>: The CommandName = ", e.CommandName, "<br />"))
End Sub

Protected Sub bCommand(ByVal sender As Object, ByVal e As CommandEventArgs)
Response.Write("Button Command event fired!<br />")
End Sub

Protected Sub lCommand(ByVal sender As Object, ByVal e As CommandEventArgs)
Response.Write("LinkButton Command event fired!<br />")
End Sub

Again, when running this demo the LinkButton, when clicked, would display “LinkButton Command event fired” andItemCommand event fired.” The Button Web control, however, would only display “Button Command event fired.” It was apparent that the Button's Command event was indeed firing, but the Repeater's ItemCommand was not. In an attempt to shed more light onto this I added code in the Command event handlers to print out the control that raised the event (the Button or LinkButton), along with its ancestors in the control hierarchy. The results stupefied me. For the LinkButton, the results were as expected:

Control Ascestors: Repeater1:_ctl1:linkButtonTest (System.Web.UI.WebControls.LinkButton) --> Repeater1:_ctl1 (System.Web.UI.WebControls.RepeaterItem) --> Repeater1 (System.Web.UI.WebControls.Repeater) --> _ctl0 (System.Web.UI.HtmlControls.HtmlForm) --> (ASP.test_aspx)

As you can see, the LinkButton was first printed, it's Parent was the RepeaterItem, it's Parent was the Repeater, it's parent was the Web Form, and it's Parent was the ASP.NET Page object. The Button results, however, were a shock:

Control Ascestors: _ctl0:buttonTest (System.Web.UI.WebControls.Button) --> _ctl0 (System.Web.UI.WebControls.RepeaterItem)

The Button's Parent, as expected, was the RepeaterItem, but the RepeaterItem has no Parent! This explained why the Repeater's ItemCommand event was not firing - the RepeaterItem could not bubble up the RepeaterCommandEventArgs up to the Repeater to raise the ItemCommand event. But why isn't there a parent for the RepeaterItem? The Button's Command event fires after Page_Load, where the control hierarchy is created (via the call to BindData(), which calls the Repeater's DataBind() method). So the RepeaterItem should indeed have a parent, namely the Repeater!

I decided to create a method to print out the entire control hierarchy. I then called this method both right after the Repeater was databound and in the Button Command event handler. The results were as expected: the entire control tree was displayed, showing that the RepeaterItem did indeed have the Repeater as its parent.

Repeater1 (Parent = _ctl0)
Repeater1:_ctl0 (Parent = Repeater1)
Repeater1:_ctl0:_ctl0 (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:linkButtonTest (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:_ctl1 (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:buttonTest (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:_ctl2 (Parent = Repeater1:_ctl0)
Repeater1:_ctl1 (Parent = Repeater1)
Repeater1:_ctl1:_ctl0 (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:linkButtonTest (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:_ctl1 (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:buttonTest (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:_ctl2 (Parent = Repeater1:_ctl1)
Repeater1:_ctl2 (Parent = Repeater1)
Repeater1:_ctl2:_ctl0 (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:linkButtonTest (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:_ctl1 (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:buttonTest (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:_ctl2 (Parent = Repeater1:_ctl2)

So each of the three RepeaterItems has a Parent in the Button Command event... but it seems like the Button does not have a RepeaterItem with a Parent. What in the world is happening here? And why does it work with the LinkButton but not the Button Web control? Why does it work if the Repeater's DataBind() method was not called on that page visit? Using Reflector, I've poured over the source code for the Repeater, Button, LinkButton, and Page class, but am at a loss. Does anyone have any ideas? Am I missing something simple?

My test case can be viewed online here (includes complete source code).

Thanks for any possible insight...

P.S.: If you can explain why this is happening, and if you're at Tech-Ed this year, I'll buy you a beer!! :-)

Exception Management Application Block Review on .NET Toolbox
18 May 04 04:07 PM | Scott Mitchell

Sadly it's been over five months since my last contribution to .NET Toolbox (RSS feed available here). The drought has ended, though, as I've posted a review about my second-favorite application block - the Microsoft Exception Management Application Block. (My favorite application block being, naturally, the Data Access Application Block.)

I'm going to try to update .NET Toolbox more regularly going forward, but one challenge I have is coming up with ideas on free .NET tools and utilities that I am interested in writing up a review for. All of the utilities I use on a day-to-day basis - Reflector, NDoc, the DAAB, Snippet Compiler, etc. - already have reviews. Any suggestions on utilities or tools that you can't live without that have yet to be covered on .NET Toolbox?

Filed under:
Used Books on Amazon.com
14 May 04 12:24 PM | Scott Mitchell

As most Amazon.com shoppers know by now, you can buy used books direct from other, ordinary folks out there through Amazon.com's Marketplace. Similarly, you can opt to sell your used books through Amazon.com, with Amazon taking a small commission off the top.

Buying used computer books makes perfect sense to me. These types of books are typically more expensive - $40-$60 a pop new - and have little value once the owner moves on to a new technology. Not surprisingly, you can save a large amount of money buying such books used, especially if the technology's outdated by a few years.

The reason I am blabbing on about this is because I have always wondered what percentage of Amazon shoppers buy new vs. buy used. Well, now I have an idea as to this figure. See, Amazon offers an Associates Account, where you can link to a book from a Web site and make a percentage of the sales price if a shopper clicks through your link to buy the book. (The books on the right-hand side of this page, for example, will direct you to Amazon.com passing in my referral ID.) A while ago, Amazon started giving comissions for people who bought used books as well. So, I can look at my Amazon Associates report and see how many people bought used vs. bought new.... and the numbers? 16.9% of people who bought a book clicking through a referral link of mine, bought used. 83.1% bought new.

These numbers, of course, only reflect the new vs. used purchases for computer books focused on Web development, so they might not be indicative of the entire set of books sold by Amazon. What surprised me, though, was that the purchase of used books was as low as it was. I assumed that more people would be buying computer books used than new, as virtually all of the books I buy from Amazon these days are used.

In closing, I'd like to address buying used vs. buying new from the prospective of an author. Ideally, all consumers would buy new copies of the book, as I don't see a cent in royalties for those who buy used. However, as I've stated before, you don't write books to become rich. Most computer trade books written in the Web development field don't surpass the advance amount, so it's kind of a moot point. Also, in some cases the price may be the deciding factor on whether or not a reader buys your book. They might pass on the cost of a new copy, but read it if they can get it used. I'd rather have my book read and not receive my royalty share, than not have it read at all.

Obligatory "I'll Be There" Post, and Things to do in San Diego!
13 May 04 12:48 PM | Scott Mitchell

For those who care, I'll be at Tech-Ed 2004. Tech-Ed is being hosted in San Diego, where I've been living for the last four years. San Diego really is one amazing and beautiful city, the nicest place I've ever been. The weather is amazing, although May typically has cloudy days... although it's been sunny a lot lately. The weather is moderate, it's usually in the 60s or 70s downtown this time of year, but can get down into the 50s sometimes at night. I'm usually comfortable with jeans and a t-shirt most times, switching to a long-sleeved t-shirt or sweatshirt at night when needed. If you are used to colder weather, you might want to bring shorts, but I rarely wear shorts myself until late June or early July.

If you'll be attending Tech-Ed, you'll no doubt enjoy San Diego's vibrant downtown, which is bustling with daytime and night time activity. There's a number of sites to check out when in downtown:

And these attractions are all within walking distance of the Convention Center, where Tech-Ed is being hosted! If you are a little more adventurous, you can catch a cab and visit:

  • Balboa Park - a huge park with museums, the San Diego Zoo, restaurants, golf courses, playgrounds, gardens, etc. In fact, you'll see Balboa park flying into San Diego if, looking forward, you're on the right side of the plane. (I'm getting married at the Marston House in Balboa Park in a month.) (About 3 miles from Convention Center)
  • Another great park with just natural beauty - no museums or tourist attractions - is Mission Bay Park. Lots of grass and beach along Mission Bay. At one end there's Belmont Park, with games, a rollercoaster, nightclubs, and the beach, all in one spot. (About 10 miles from the Convention Center)
  • Old Town - you can visit the Old Town park, tour the San Diego Mission, and drink margaritas all night long. (About 5 miles from the Convention Center)
  • Pacific Beach - this is where I live. It's mostly 20 something year olds... the place to go if you want to go out drinking and get a tatoo or body piercing, or if you want to drink on the beach (About 13 miles from the Convention Center).
  • Take a fairy (or cab) over to Coronado. Coronado looks like an island, but is connected to the continent further south of San Diego. There's a big bridge you can take over, as well as fairies. Coronado has many quaint shops and eateries, it very scenic, and has the highest number of retired admirals, per capita. (About 1 mile from the Convention Center)
  • There's also more family-oriented spots, like SeaWorld. (About 9 miles from the Convention Center)
  • If you want to visit Mexico, you can take the trolley ($4 one way down to Mexico, I believe, about a 30 minute ride) down to the border and walk over to Tijuana. Don't eat the food unless it's a very nice place or you'll be paying for it over the next 8 hours - trust me! :-) Tijuana's the only place I know of where you'll find gallon-sized drums of Viagra for sale. (About 16 miles from the Convention Center)

San Diego really in one amazing city - I hope you enjoy your stay here!

Ok, ok, enough fawning over San Diego. If you want to meet up during the weekend, let me know. I'll be at a Sams book signing on Tuesday the 25th from noon to 1:30.

Filed under:
More Posts Next page »

Archives

My Books

  • Teach Yourself ASP.NET 4 in 24 Hours
  • Teach Yourself ASP.NET 3.5 in 24 Hours
  • Teach Yourself ASP.NET 2.0 in 24 Hours
  • ASP.NET Data Web Controls Kick Start
  • ASP.NET: Tips, Tutorials, and Code
  • Designing Active Server Pages
  • Teach Yourself Active Server Pages 3.0 in 21 Days

I am a Microsoft MVP for ASP.NET.

I am an ASPInsider.