Master Pages and ASP.NET AJAX (C#)
Part 7
Introduction
Over the past several years, more and more developers have been building AJAX-enabled
web applications. An AJAX-enabled website uses a number of related web
technologies to offer a more responsive user experience. Creating
AJAX-enabled ASP.NET applications is amazingly easy thanks to
Microsoft's ASP.NET AJAX framework.
ASP.NET AJAX is built into ASP.NET 3.5 and Visual Studio 2008; it is
also available as a separate download for ASP.NET 2.0 applications.
When building AJAX-enabled web pages with the ASP.NET AJAX framework, you must add precisely one ScriptManager control
to each and every page that uses the framework. As its name implies,
the ScriptManager manages the client-side script used in AJAX-enabled
web pages. At a minimum, the ScriptManager emits HTML that instructs the
browser to download the JavaScript files that makeup the ASP.NET AJAX
Client Library. It can also be used to register custom JavaScript files,
script-enabled web services, and custom application service
functionality.
If your site uses master pages (as it should), you do not necessarily
need to add a ScriptManager control to every single content page;
rather, you can add a ScriptManager control to the master page. This
tutorial shows how to add the ScriptManager control to the master page.
It also looks at how to use the ScriptManagerProxy control to register
custom scripts and script services in a specific content page.
Note: This tutorial does not
explore designing or building AJAX-enabled web applications with the
ASP.NET AJAX framework. For more information on using AJAX consult the ASP.NET AJAX videostutorials, as well as those resources listed in the Further Reading section at the end of this tutorial. and
Examining the Markup Emitted by the ScriptManager Control
The ScriptManager control emits markup that instructs the browser to
download the JavaScript files that makeup the ASP.NET AJAX Client
Library. It also adds a bit of inline JavaScript to the page that
initializes this library. The following markup shows the content that is
added to the rendered output of a page that includes a ScriptManager
control:
<script src="/ASPNET_MasterPages_Tutorial_08_CS/WebResource.axd?d=T8EVk6SsA8mgPKu7_sBX5w2 t=633363040378379010" type="text/javascript"></script>
<script src="/ASPNET_MasterPages_Tutorial_08_CS/ScriptResource.axd?d=SCE1TCh8B24VkQIU5a8iJFizuPBIqs6Lka7GEkxo-6ROKNw5LVPCpF_pmLFR-R-p_Uf42Ahmr_SKd8lwgZUWb2uPJmfX0X_H6oLA50bniyQ1
t=633465688673751480" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
if (typeof(Sys) === 'undefined') throw new Error('ASP.NET Ajax client-side framework failed to load.');
//]]>
</script>
<script src="/ASPNET_MasterPages_Tutorial_08_CS/ScriptResource.axd?d=SCE1TCh8B24VkQIU5a8iJFizuPBIqs6Lka7GEkxo-6ROKNw5LVPCpF_pmLFR-R-phT96yZPngppiP_VXlN4Vz2RuVtvwDiQzF9xt42dUCiYjL0UylAJoyYzStwvObx0U0
t=633465688673751480" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
Sys.WebForms.PageRequestManager._initialize('ScriptManager1', document.getElementById('form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls([], [], [], 90);
//]]>
</script>
<script type="text/javascript">
//<![CDATA[
Sys.Application.initialize();
//]]>
</script>
The <script src="url"></script> tags instruct the browser to download and execute the JavaScript file at url. The ScriptManager emits three such tags; one references the file WebResource.axd, while the other two reference the file ScriptResource.axd.
These files do not actually exist as files in your website. Instead,
when a request for either one of these files arrives at the web server,
the ASP.NET engine examines the querystring and returns the appropriate
JavaScript content. The script provided by these three external
JavaScript files constitute the ASP.NET AJAX framework's Client Library.
The other <script> tags emitted by the ScriptManager include inline script that initializes this library.
The external script references and inline script emitted by the
ScriptManager are essential for a page that uses the ASP.NET AJAX
framework, but is not needed for pages that do not use the framework.
Therefore, you might reason that it is ideal to only add a ScriptManager
to those pages that use the ASP.NET AJAX framework. And this is
sufficient, but if you have many pages that use the framework you'll end
up adding the ScriptManager control to all pages - a repetitive task,
to say the least. Alternatively, you can add a ScriptManager to the
master page, which then injects this necessary script into all content
pages. With this approach, you do not need to remember to add a
ScriptManager to a new page that uses the ASP.NET AJAX framework because
it is already included by the master page. Step 1 walks through adding a
ScriptManager to the master page.
Note: If you plan on including
AJAX functionality within the user interface of your master page, then
you have no choice in the matter - you must include the ScriptManager in
the master page.
One downside of adding the ScriptManager to the master page is that the above script is emitted in every
page, regardless of whether its needed. This clearly leads to wasted
bandwidth for those pages that have the ScriptManager included (via the
master page) yet don't use any features of the ASP.NET AJAX framework.
But just how much bandwidth is wasted?
- The actual content emitted by the ScriptManager (shown above) totals a little over 1KB.
- The three external script files referenced by the
<script>
element, however, comprise roughly 450KB of data uncompressed; in a
website that uses gzip compression, this total bandwidth can be reduced
near 100KB. However, these script files are cached by the browser for
one year, meaning that they only need to be downloaded once and then can
be reused in other pages on the site.
In the best case, then, when the script files are cached, the total
cost is 1KB, which is negligible. In the worst case, however - which is
when the script files have not yet been downloaded and the web server is
not using any form of compression - the bandwidth hit is around 450KB,
which can add anywhere from a second or two over a broadband connection
to up to a minute for users over dial-up modems. The good news is that
because the external script files are cached by the browser, this worst
case scenario occurs infrequently.
Note: If you still feel uncomfortable placing the ScriptManager control in the master page, consider the Web Form (the <form runat="server">
markup in the master page). Every ASP.NET page that uses the postback
model must include precisely one Web Form. Adding a Web Form adds
additional content: a number of hidden form fields, the <form>
tag itself, and, if necessary, a JavaScript function for initiating a
postback from script. This markup is unnecessary for pages that don't
postback. This extraneous markup could be eliminated by removing the Web
Form from the master page and manually adding it to each content page
that needs one. However, the benefits of having the Web Form in the
master page outweigh the disadvantages from having it added
unnecessarily to certain content pages.
Step 1: Adding a ScriptManager Control to the Master Page
Every web page that uses the ASP.NET AJAX framework must contain
precisely one ScriptManager control. Because of this requirement, it
usually makes sense to place a single ScriptManager control on the
master page so that all content pages have the ScriptManager control
automatically included. Furthermore, the ScriptManager must come before
any of the ASP.NET AJAX server controls, such as the UpdatePanel and
UpdateProgress controls. Therefore, it's best to put the ScriptManager
before any ContentPlaceHolder controls within the Web Form.
Open the Site.master master page and add a ScriptManager control to the page within the Web Form, but before the <div id="topContent">
element (see Figure 1). If you are using Visual Web Developer 2008 or
Visual Studio 2008, the ScriptManager control is located in the Toolbox
in the AJAX Extensions tab. If you are using Visual Studio 2005, you
will need to first install the ASP.NET AJAX framework and add the
controls to the Toolbox. Visit the ASP.NET AJAX download page to get the framework for ASP.NET 2.0.
After adding the ScriptManager to the page, change its ID from ScriptManager1 to MyManager.
Step 2: Using the ASP.NET AJAX Framework from a Content Page
With the ScriptManager control added to the master page we can now
add ASP.NET AJAX framework functionality to any content page. Let's
create a new ASP.NET page that displays a randomly selected product from
the Northwind database. We'll use the ASP.NET AJAX framework's Timer
control to update this display every 15 seconds, showing a new product.
Start by creating a new page in the root directory named ShowRandomProduct.aspx. Don't forget to bind this new page to the Site.master master page.
Recall that in the Specifying the Title, Meta Tags, and Other HTML Headers in the Master Page tutorial we created a custom base page class named BasePage that generated the page's title if it was not explicitly set. Go to the ShowRandomProduct.aspx page's code-behind class and have it derive from BasePageSystem.Web.UI.Page). (instead of from
Finally, update the Web.sitemap file to include an entry for this lesson. Add the following markup beneath the <siteMapNode> for the Master to Content Page Interaction lesson:
<siteMapNode url="~/ShowRandomProduct.aspx" title="Master Pages and ASP.NET AJAX" />
The addition of this <siteMapNode> element is reflected in the Lessons list (see Figure 5).
Displaying a Randomly Selected Product
Return to ShowRandomProduct.aspx. From the Designer, drag an UpdatePanel control from the Toolbox into the MainContent Content control and set its ID property to ProductPanel. The UpdatePanel represents a region on the screen that can be asynchronously updated through a partial page postback.
Our first task is to display information about a randomly selected
product within the UpdatePanel. Start by dragging a DetailsView control
into the UpdatePanel. Set the DetailsView control's ID property to ProductInfo and clear out its Height and Width
properties. Expand the DetailsView's smart tag and, from the Choose
Data Source drop-down list, choose to bind the DetailsView to a new
SqlDataSource control named RandomProductDataSource.
Configure the SqlDataSource control to connect to the Northwind database via the NorthwindConnectionString (which we created in the Interacting with the Master Page from the Content Page tutorial). When configuring the select statement choose to specify a custom SQL statement and then enter the following query:
SELECT TOP 1 ProductName, UnitPrice
FROM Products
ORDER BY NEWID()
The TOP 1 keyword in the SELECT clause returns only the first record returned by the query. The NEWID() function generates a new globally unique identifier value (GUID) and can be used in an ORDER BY clause to return the table's records in a random order.
After completing the wizard, Visual Studio creates a BoundField for
the two columns returned by the above query. At this point your page's
declarative markup should look similar to the following:
<asp:UpdatePanel ID="ProductPanel" runat="server">
<ContentTemplate>
<asp:DetailsView ID="ProductInfo" runat="server" AutoGenerateRows="False"
DataSourceID="RandomProductDataSource">
<Fields>
<asp:BoundField DataField="ProductName" HeaderText="ProductName"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
SortExpression="UnitPrice" />
</Fields>
</asp:DetailsView>
<asp:SqlDataSource ID="RandomProductDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" SelectCommand="SELECT TOP 1 ProductName, UnitPrice FROM Products ORDER BY NEWID()"></asp:SqlDataSource>
</ContentTemplate>
</asp:UpdatePanel>
Figure 5 shows the ShowRandomProduct.aspx page when viewed through a browser. Click your browser's Refresh button to reload the page; you should see the ProductName and UnitPrice values for a new randomly selected record.
Automatically Displaying a New Product Every 15 Seconds
The ASP.NET AJAX framework includes a Timer control that performs a postback at a specified time; on postback the Timer's Tick
event is raised. If the Timer control is placed within an UpdatePanel
it triggers a partial page postback, during which we can rebind the data
to the DetailsView to display a new randomly selected product.
To accomplish this, drag a Timer from the Toolbox and drop it into the UpdatePanel. Change the Timer's IDTimer1 to ProductTimer and its Interval property from 60000 to 15000. The Interval from
property indicates the number of milliseconds between postbacks;
setting it to 15000 causes the Timer to trigger a partial page postback
every 15 seconds. At this point your Timer's declarative markup should
look similar to the following:
<asp:UpdatePanel ID="ProductPanel" runat="server" onload="ProductPanel_Load">
<ContentTemplate>
...
<asp:Timer ID="ProductTimer" runat="server" Interval="15000">
</asp:Timer>
</ContentTemplate>
</asp:UpdatePanel>
Create an event handler for the Timer's Tick event. In this event handler we need to rebind the data to the DetailsView by calling the DetailsView's DataBind
method. Doing so instructs the DetailsView to re-retrieve the data from
its data source control, which will select and display a new randomly
selected record (just like when reloading the page by clicking the
browser's Refresh button).
protected void ProductTimer_Tick(object sender, EventArgs e)
{
ProductInfo.DataBind();
}
That's all there is to it! Revisit the page through a browser.
Initially, a random product's information is displayed. If you patiently
watch the screen you'll notice that, after 15 seconds, information
about a new product magically replaces the existing display.
To better see what's happening here, let's add a Label control to the
UpdatePanel that displays the time the display was last updated. Add a
Label Web control within the UpdatePanel, set its ID to LastUpdateTime, and clear its Text property. Next, create an event handler for the UpdatePanel's LoadLoad event is fired on every full or partial page postback.) event and display the current time in the Label. (The UpdatePanel's
protected void ProductPanel_Load(object sender, EventArgs e)
{
LastUpdateTime.Text = "Last updated at " + DateTime.Now.ToLongTimeString();
}
With this change complete, the page includes the time the currently
displayed product was loaded. Figure 6 shows the page when first
visited. Figure 7 shows the page 15 seconds later after the Timer
control has "ticked" and the UpdatePanel has been refreshed to display
information about a new product.
Step 3: Using the ScriptManagerProxy Control
Along with including the necessary script for the ASP.NET AJAX
framework Client Library, the ScriptManager can also register custom
JavaScript files, references to script-enabled Web Services, and custom
authentication, authorization, and profile services. Usually such
customizations are specific to a certain page. However, if the custom
script files, Web Service references, or authentication, authorization,
or profile services are referenced in the ScriptManager in the master
page then they are included in all pages in the website.
To add ScriptManager-related customizations on a page-by-page basis
use the ScriptManagerProxy control. You can add a ScriptManagerProxy to a
content page and then register the custom JavaScript file, Web Service
reference, or authentication, authorization, or profile service from the
ScriptManagerProxy; this has the effect of registering these services
for the particular content page.
Note: An ASP.NET page can only
have no more than one ScriptManager control present. Therefore, you
cannot add a ScriptManager control to a content page if the
ScriptManager control is already defined in the master page. The sole
purpose of the ScriptManagerProxy is to provide a way for developers to
define the ScriptManager in the master page, but still have the ability
to add ScriptManager customizations on a page-by-page basis.
To see the ScriptManagerProxy control in action, let's augment the UpdatePanel in ShowRandomProduct.aspx
to include a button that uses client-side script to pause or resume the
Timer control. The Timer control has three client-side methods that we
can use to achieve this desired functionality:
_startTimer() - starts the Timer control
_raiseTick() - causes the Timer control to "tick," thereby posting back and raising its Tick event on the server
_stopTimer() - stops the Timer control
Let's create a JavaScript file with a variable named timerEnabled and a function named ToggleTimer. The timerEnabled variable indicates whether the Timer control is currently enabled or disabled; it defaults to true. The ToggleTimer function accepts two input parameters: a reference to the Pause/Resume button and the client-side id value of the Timer control. This function toggles the value of timerEnabled, gets a reference to the Timer control, starts or stops the Timer (depending on the value of timerEnabled),
and updates the button's display text to "Pause" or "Resume". This
function will be called whenever the Pause/Resume button is clicked.
Start by creating a new folder in the website named Scripts. Next, add a new file to the Scripts folder named TimerScript.js of type JScript File.
Next, add the following scrip to the TimerScript.js file:
var timerEnabled = true;
function ToggleTimer(btn, timerID)
{
// Toggle the timer enabled state
timerEnabled = !timerEnabled;
// Get a reference to the Timer
var timer = $find(timerID);
if (timerEnabled)
{
// Start timer
timer._startTimer();
// Immediately raise a tick
timer._raiseTick();
btn.value = 'Pause';
}
else
{
// Stop timer
timer._stopTimer();
btn.value = 'Resume';
}
}
We now need to register this custom JavaScript file in ShowRandomProduct.aspx. Return to ShowRandomProduct.aspx and add a ScriptManagerProxy control to the page; set its ID to MyManagerProxy.
To register a custom JavaScript file select the ScriptManagerProxy
control in the Designer and then go to the Properties window. One of the
properties is titled Scripts. Selecting this property displays the
ScriptReference Collection Editor shown in Figure 10. Click the Add
button to include a new script reference and then enter the path to the
script file in the Path property: ~/Scripts/TimerScript.js.
After adding the script reference the ScriptManagerProxy control's declarative markup is updated to include a <Scripts> collection with a single ScriptReference entry, as the following snippet of markup illustrates:
<asp:ScriptManagerProxy ID="MyManagerProxy" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/TimerScript.js" />
</Scripts>
</asp:ScriptManagerProxy>
The ScriptReference entry instructs the
ScriptManagerProxy to include a reference to the JavaScript file in its
rendered markup. That is, by registering the custom script in the
ScriptManagerProxy the ShowRandomProduct.aspx page's rendered output now includes another <script src="url"></script> tag: <script src="Scripts/TimerScript.js" type="text/javascript"></script>.
We can now call the ToggleTimer function defined in TimerScript.js from the client script in the ShowRandomProduct.aspx page. Add the following HTML within the UpdatePanel:
<input type="button" id="PauseResumeButton"
value="Pause"
onclick="ToggleTimer(this, '<%=ProductTimer.ClientID %>');" />
This displays a button with the text "Pause". Whenever it is clicked, the JavaScript function ToggleTimer is called, passing in a reference to the button and the id value of the Timer control (ProductTimer). Note the syntax for obtaining the id value of the Timer control. <%=ProductTimer.ClientID%> emits the value of the ProductTimer Timer control's ClientID property. In the Control ID Naming in Content PagesID value and the resulting client-side id value, and how ClientID returns the client-side id. tutorial we discussed the differences between the server-side
Figure 11 shows this page when first visited through a browser. The
Timer is currently running and updates the displayed product information
every 15 seconds. Figure 12 shows the screen after the Pause button has
been clicked. Clicking the Pause button stops the Timer and updates the
button's text to "Resume". The product information will refresh (and
continue to refresh every 15 seconds) once the user clicks Resume.
Summary
When building AJAX-enabled web applications using the ASP.NET AJAX
framework it is imperative that every AJAX-enabled web page include a
ScriptManager control. To facilitate this process, we can add a
ScriptManager to the master page rather than having to remember to add a
ScriptManager to each and every content page. Step 1 showed how to add
the ScriptManager to the master page while Step 2 looked at implementing
AJAX functionality in a content page.
If you need to add custom scripts, references to script-enabled Web
Services, or customized authentication, authorization, or profile
services to a particular content page, add a ScriptManagerProxy control
to the content page and then configure the customizations there. Step 3
examined how to use the ScriptManagerProxy to register a custom
JavaScript file in a specific content page.
Happy Programming!