How to rotate JavaScript ads during ASP.NET AJAX requests
AJAX, ASP.NET, JavaScript, UI By Dave Ward. Updated January 8, 2009A common question that I find myself fielding lately is how to handle the problem of rotating JavaScript based advertisements. As AJAX becomes ubiquitous on ad supported sites, we must take care that decreasing page views don’t also mean proportionally decreasing revenues.
Many find their way to ScriptManager.RegisterStartupScript, use that to re-inject the script during a partial postback, and expect that doing so will refresh the ad. Unfortunately, that doesn’t work when dealing with JavaScript based ads such as Google’s AdSense.
In this post, I’m going to show you why RegisterStartupScript does not solve the problem, the alternative that I suggest, and how to use that method to also defer loading of advertisements until after your content.
How JavaScript ads are displayed
After being spoiled by high level libraries like ASP.NET AJAX and jQuery, it’s easy to forget how tedious it is to write cross-browser DOM manipulation code. If you do manage to write something from scratch that works well, you’ll usually find that it ends up being quite large.
For this reason, advertising units embedded via JavaScript includes usually use a series of document.write statements to output raw HTML.
For example, I added the Lake Quincy ad in my sidebar with this JavaScript:
<script type="text/javascript"> lqm_channel = 1; lqm_publisher = 199; lqm_zone = 1; lqm_format = 7; </script> <script type="text/javascript" src="http://a.lakequincy.com/s.js"> </script>
Depending on which advertisement is returned from Lake Quincy’s server, s.js will use document.write to eventually render something like:
<a target="_top" href="http://a.lakequincy.com/c.ashx?..."> <img border="0" src="http://a.lakequincy.com/img/foo.png"/> </a>
This HTML renders in the same location that the <script> element is placed.
Google AdSense, Amazon Widgets, and just about every other advertising service that I’ve encountered all use this document.write method.
Why doesn’t RegisterStartupScript work?
There are two reasons why this document.write method makes rotation difficult when using ASP.NET AJAX’s server-side JavaScript methods.
First, the ScriptManager’s RegisterStartupScript method does not allow control over positioning the JavaScript it emits. Since these JavaScript based ads literally dump HTML code right where they are located, being unable to target them is a big problem.
You can attempt to circumvent this with absolute CSS positioning, but it adds a lot of unnecessary complexity to what should otherwise be simple.
Second, most browsers ignore document.write statements after the initial page layout has completed. Even if you found a way to get the script asynchronously injected at the precise location you desire, it would do absolutely nothing in most browsers.
Between these two problems, it becomes apparent that we need to find something altogether different.
So, what does work?
The answer to this dilemma is simple: the iframe.
One very useful property of the iframe is that its contents are created within the scope of a completely new DOM. For our purposes, the benefit of this is that it provides a way to execute document.write statements at any time after the initial page rendering.
Additionally, iframes are as easily positioned as a <div> and the separate DOM protects against unruly ad content breaking out of its intended location.
Encapsulating the ad within an iframe
To move the ad into an iframe, first we need to move the script blocks to an external HTML file. I called this one LQ-336×280.html. This naming scheme helps me keep track of which file corresponds to each advertiser and unit size:
<html> <head> <meta name="ROBOTS" content="NOINDEX, NOFOLLOW" /> <title>LQ-336x280</title> </head> <body style="margin: 0; padding: 0;"> <script type="text/javascript"> lqm_channel = 1; lqm_publisher = 199; lqm_zone = 1; lqm_format = 7; </script> <script type="text/javascript" src="http://a.lakequincy.com/s.js"> </script> </body> </html>
The one thing in that HTML you might not be familiar with is the meta tag. That particular tag instructs search engine spiders to completely ignore the file. It’s my understanding that it’s important to include this to avoid running afoul of Google’s policies, in the same vein as using nofollow on paid links.
Finally, we just have to replace the inline script blocks with an iframe referencing the HTML include file we just created:
<iframe id="AdFrame" width="336" height="280" src="LQ-336x280.html" frameborder="0" scrolling="no"></iframe>
The ad is now encapsulated within the iframe, with its own DOM.
Using that iframe to rotate the ad content
Now that we’ve got the document.write code into an iframe, cycling the ad content is as simple as reloading the iframe. This can be accomplished by re-setting its src attribute:
// Get a reference to the iframe. var adFrame = document.getElementById('AdFrame'); // Force it to refresh, by setting its location to // whatever it currently is. adFrame.src = adFrame.src;
This code allows us to rotate the advertisement at any arbitrary time, independent of the main page’s loading and rendering.
Using this during ASP.NET AJAX partial postbacks
Having figured out a fundamental method for accomplishing the rotation, now we can get back to the original question of how to rotate ads during an UpdatePanel’s async postback.
For example, let’s say that we want to rotate ads during every partial postback. After all, that’s how often they would’ve changed had there been no UpdatePanel.
The easiest way is to handle the PageRequestManager’s EndRequest event:
// Add an initialization handler to run once // per page load. Sys.Application.add_init(AppInit); function AppInit() { // Get a reference to the PageRequestManager. var prm = Sys.WebForms.PageRequestManager.getInstance() // Add an end request handler to rotate the ad. // after every partial postback. prm.add_endRequest(function() { $get('AdFrame').src = $get('AdFrame').src; }); }
Optionally, you could perform the rotation at BeginRequest instead. I’ve found that ads receive a slight boost in their CTR when rotated before the request completes, instead of after. It depends on what behavior you want to optimize, engagement or CTR.
Bonus: Deferred loading
Because inline script elements block rendering until they’ve finished downloading and executing, a few slow ad servers may have a tremendous impact on your site’s overall performance.
Now that we’ve gained the ability to render our advertisement anytime we want to, another handy use for this is to defer loading of the ad.
To implement deferred loading, we need to first revise the iframe element in our main page to omit the src attribute:
<iframe id="AdFrame" width="336" height="280" frameborder="0" scrolling="no"></iframe>
Then, dynamically set the attribute later in the page load. For example, you could set it inside the Application.Init handler that we created earlier, revising it to:
Sys.Application.add_init(AppInit); function AppInit() { // This initiates the ad's deferred load, late in the // page's overall loading cycle. $get('AdFrame').src = 'LQ-336x280.html'; var prm = Sys.WebForms.PageRequestManager.getInstance() prm.add_endRequest(function() { $get('AdFrame').src = $get('AdFrame').src; }); }
Now, the page will load completely before the ad’s JavaScript include is evaluated. Depending on the layout of your page, this can dramatically decrease the perceived page load time.
Conclusion
Do apply this technique ethically. Just because you can rotate ads as you desire, that doesn’t mean you should. For instance, if you cycle CPM ads as a result of trivial operations that wouldn’t previously have resulted in an impression, you’ll be cheating your advertisers.
If you’d like to see this in action, just take a look at the source for my sidebar ad. It’s implemented exactly as shown above, to take advantage of deferred loading.
The iframe does have a couple small disadvantages, but I think their flexibility far outweighs any drawbacks. What do you think?
Similar posts
What do you think?
I appreciate all of your comments, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums or Stack Overflow instead.
If you're replying to another comment, use the threading feature by clicking "Reply to this comment" before submitting your own.
2 Mentions Elsewhere
- Dew Drop - January 6, 2009 | Alvin Ashcraft's Morning Dew
- Omri Tintpulver » Blog Archive » Properly tracking AJAX or AHAH page loads with Google Analytics and jQuery UI Tabs



Excellent post as always!
I really want to implement this for all the advertising scripts that have to be on my website, these being Google Adwords, and some Statcamp ones..
There are about 4-5 of these scripts injected into every page on my site. This can cause a huge delay on the page loading, which because the site makes heavy use of the JQuery accordion widget, the site can take ages to wrap up the accordion which looks ugly. Being able to have document.ready() fire and then all the slow marketing scripts load would be a great improvement.
Basically can I use an iFrame as a lazy loading technique?
The scripts will be firing from a separate page, if I named my delayed-script-pages that will be loaded by the iFrame the same as the parent page would the stats be adversely affected do you think?
Really all we are bothered about are conversions and page views…
Many thanks, keep up the posts!
Mark
It depends a lot on how the particular ad units are rendering and on how you’re gathering stats.
If you’re using ads that redirect through a stats tracking “landing page”, then it should still work the same.
If you’re using the outbound tracking in something like Google Analytics, you can embed it at the end of the HTML ad page but omit the (in the GA example) _trackPageview() call so that you don’t double up the stats.
Awesome, I was looking into doing something like this to offset the horrible dzone voting links. Those things suck in the entire internet when you include them on your page, and quite often their services get overloaded. It would be really nice to have those deferred loaded. I’m definitely going to try to implement this when I get the time. Thanks man!
Great, but doesn’t putting the ad into an iframe prevent the advertiser from targetting the ads and gathering stats?
By targeting, I’m assuming you mean contextual targeting like AdSense?
I’d be interested in finding out definitively, but my experience has been that it doesn’t hurt anything. I’m not sure if it’s because their client-side code knows to traverse up parents in the DOM or if they base the ads more on previous spidering than current content, though I suspect the later.
Most of the larger advertisers document.write an iframe of their own anyway, so that they can access tracking cookies within the scope of their own domain (to track you across sites and target ads).
At least for Google AdSense, this help topic indicates that AdSense is “not optimized to serve ads within a separate IFRAME”. You may end up with less targeted ads or public service ads.
I temporarily added an AdSense block between the post and comments here, within an iframe.
It looks to be targeting correctly for me. I’d be interested to know if anyone else is getting PSAs or junk ads instead.
Thanks! I used your solution to fix Google AdSense banners.
The banners would take a while to load therefore delaying $(document).ready(…) –
This made my startup scripts load shortly after the banner had finished and some quick and savvy users would start using the page before it was ready, potentially running into script errors.
what about long pages? when a users initiates ajax call while being at the bottom of the page, it would not make much sense to refresh an ad at the top of the page.
I used to have a page where I tracked location of the mouse – if it was in focus of the page and then refreshed only those ads that were in vicinity of the mouse but only if user had moved the mouse within last 5-6 seconds – meaning he still was at his computer.
I figured out this technique a while back, but quickly abandoned it because in any IE Web browser, it makes an annoying “click” noise every time you change he src value on an iframe, as if you navigated the web browser by clicking a link.
I definitely do not want clicks happening at any time other than when the user specifically creates it by navigating to another page.
The way I found around it, while still using iframes (because there are lots of benefits to using iframes) is to create the iframe inside a div tag, and instead of just changing the src property, I rewrite the entire iframe html and rebuild the div using innerHTML.
It sounds like a lot more work, but it works great.
That’s a great suggestion. I’ve changed my sidebar ad on this site to utilize that method of deferred loading instead of directly changing the iframe’s src.
Have you checked for memory leak issues when rotating ads this way? Overwriting the innerHTML property, without properly destroying the iframe first, will likely have that drawback.
Update: I had to abandon the innerHTML method, due to some kind of caching issue in Firefox. Initial visits with an unprimed cache weren’t loading advertising at all for some reason.
Dave,
IE’s memory leaks occur when DOM references are saved in JavaScript variables or properties, and those references are not broken/nullified before the page unloads.
In theory, you should be able to overwrite the innerHTML property as many times as you like, as long as the advertisement JavaScript is well-behaved. The iframe is the same thing as loading a page in a new window and then when you overwrite it, it’s like closing the window.
The last time I looked at advertising.com code, it didn’t seem to do anything crazy that would leak memory. Mainly just document.write and some simple variables, but I didn’t notice any cases where they were storing references to DOM objects.
-Todd
This is a great post that contains a wonderful solution to a very tricky problem. I’ve been looking for something like this for quite a while now.
Google analytics shows my site averaging about 250k page views per month, while my hosting provider indicates that we’re over 3.5M requests per month. The AJAX is great for our users, but not so good for advertising.
@Terence: There is a HUGE difference between page views (your number) and requests (hosting provider’s number).
A request is any ping to the server for information, including the page, every image, every script request, stylesheet, web service call, favicon request, etc., etc.
The only way to truly analyze your usage is to look at the server logs. SmarterTools has a nice package called SmarterStats to analyze server logs, and it’s free for people with a single domain. A very, very good application, with great polish — even including Silverlight graphs and charts.
Or, you can use Log Parser if you really want to rip apart the logs using a SQL syntax.
Good luck!
-Todd
In this case, using a stats analyzer like SmarterStats isn’t going to help much. When relying heavily on UpdatePanels, server based stats will under-report the true engagement of the user the same as any other pageview based metric.
Actually, SmarterStats can report that too!
UpdatePanels still make calls to the server like any page request, whether you’re using Page Methods or calling a web service in an ASMX file.
Those requests are viewable on SmarterStats reports, and you should be able to match up request counts in SmarterStats to what the hosting provider has.
SmarterStats can do that because it analyzes the *server logs*, unlike stuff like Google Analytics, which only tally page requests and can’t capture web service calls.
It reports POST requests the same as a GET pageview?
For what it’s worth, it’s possible to track AJAX requests in Google Analytics. You just need to make a call to _trackPageview().
It has lots of different reports, so I’m not sure how to answer that. Every “ping” to the server can appear on a report, so I guess so.
Many web sites don’t even use GETs, as I believe it is not enabled by default in ASP.NET, so for many that may be a moot point.
It has some nice drill-down features (which it calls “data mining”) that can reveal lots of detail typically not available in traffic anaylsis software.
Just to be clear, every website uses GET requests. Anytime you type a URL in your browser or click an <a> link (but not a LinkButton), this is a GET request.
You can get a better feel for that by watching the requests in something like Live HTTP headers or Fiddler.
Thank you so much for that. I hadn’t even heard of _trackPageView() before, so I set out in search of it.
Much to my dismay, none of the questions on the google help pages told me exactly where to place the call, when tracking an AJAX partial postback. There were clear instructions for tracking Javascript, and downloads, and such, but nothing really clear on the AJAX postback.
But of course, this very blog post contained all of the information that I needed. I simply added the call to _trackPageView() to my endRequest handler and it’s been working great ever since!
Thank you, yet again, for sharing such useful information.
Thanks for a great post, especially the part in the comments about _trackPageView(). We’re now using that effectively with jQuery UI Tabs to track our asynchronous page loads through Google Analytics:
http://so.ca/?p=144
I was talking about AJAX requests!
Very useful topic. As usual great post Dave.
Very useful post
I have tried the document.write method and really makes rotation difficult when using ASP.NET AJAX’s server-side JavaScript methods.
I tried your examples and succeded to make it work
Amazing
I tried also the banner in ur site with the javascript code
Could you possibly provide a similar solution for those of us using asp.net MVC?
The fundamental solution would be the same (i.e. iframes, .src = .src, etc). When/how to trigger that depends on what sort of AJAX you’re using in your MVC app.
Thanks Dave. I can use jquery or microsoft ajax.
The problem is things like “Sys.WebForms.PageRequestManager.getInstance()” have no meaning in MVC.
Actually, I’m refreshing the page content via JQuery and have access to an “onComplete” funtion but I can’t figure out what the heck to do on complete in terms of your code for web forms.
All the code should be the same, down to and including the section titled “Using that iframe to rotate the ad content”. Put that JavaScript in your OnComplete and it will work.
Dave, thanks for the prompt responses.
Your suggestion is something I had already tried. What happens is the Google ad “flashes” just as though it was being refreshed with a new page load but the ad content never changes.
Is there a location where this code is actually in place and serving new Google ads on a ajax refresh?
I haven’t used the iframe for AdSense in awhile (I’ve stopped using AdSense on any of my sites), so I don’t have a live test of that anymore.
If the ad is refreshing, it sounds like you’ve got it right. It’s entirely possible that they’ve done something to neutralize the rotation since then. I’d be interested to hear what you find out.
Awesome! two birds with one frame.
Exactly what I was looking for…
Great Post! Quick question though. If we wanted to randomize the images on the advertisement that were on our server, how would we go about that. For example all of my add’s are png format. I want to rotate them using ajax. Thanks
You wouldn’t need to use something like this to rotate simple images. Serving the image through an ASHX handler that automatically randomizes the image returned is probably the most common way (should be easy to find an example), and it works pretty well.
Is google ok with this practice?
What if you refreshed the ad every x seconds? That seems like a no-no to me.
I don’t know what Google’s official policy is.
If you’re running CPM ads, it’s almost definitely fraud to rotate them on a timer. I wouldn’t recommend anyone do that, of course.
If you’re running CPC or CPA ads and find that rotating them helps improve their performance, everyone wins.
This is really awesome. Is it possible to rotate ad after 3or 5 seconds on the page already loaded on the client’s site.
Yes, you can do that by using JavaScript’s setTimeout. Sticking to the elements in the example above, this would rotate the ad every five seconds: