Calling Web Services with ASP.NET AJAX

Microsoft’s ASP.NET technology brings an object-oriented and event-driven programming
model and unites it with the benefits of compiled code. However, its server-side processing
model has several drawbacks inherent in the technology:





Page updates require a round-trip to the server, which requires a page refresh.
Round-trips do not persist any effects generated by Javascript or other client-side
technology (such as Adobe Flash)
During postback, browsers other than Microsoft Internet Explorer do not support
automatically restoring the scroll position. And even in Internet Explorer, there is still a
flicker as the page is refreshed.
Postbacks may involve a high amount of bandwidth as the __VIEWSTATE form field
may grow, especially when dealing with controls such as the GridView control or
repeaters.
There is no unified model for accessing Web Services through JavaScript or other clientside technology.
Enter Microsoft’s ASP.NET AJAX extensions. AJAX, which stands for Asynchronous
JavaScript And XML, is an integrated framework for providing incremental page updates via
cross-platform JavaScript, composed of server-side code comprising the Microsoft AJAX
Framework, and a script component called the Microsoft AJAX Script Library. The ASP.NET
AJAX extensions also provide cross-platform support for accessing ASP.NET Web Services via
JavaScript.
This whitepaper examines the partial page updates functionality of the ASP.NET AJAX
Extensions, which includes the ScriptManager component, the UpdatePanel control, and the
UpdateProgress control, and considers scenarios in which they should or should not be utilized.
This whitepaper is based on the Beta 2 release of the Visual Studio 2008 and the .NET
Framework 3.5, which integrates the ASP.NET AJAX Extensions into the Base Class Library
(where it was previously an add-on component available for ASP.NET 2.0). This whitepaper also
assumes that you are using Visual Studio 2008 and not Visual Web Developer Express Edition;
some project templates that are referenced may not be available to Visual Web Developer
Express users.
Partial Page Updates
Perhaps the most visible feature of the ASP.NET AJAX Extensions is the ability to do a partial
or incremental page updates without doing a full postback to the server, with no code changes
and minimal markup changes. The advantages are extensive – the state of your multimedia (such
as Adobe Flash or Windows Media) is unchanged, bandwidth costs are reduced, and the client
does not experience the flicker usually associated with a postback.
The ability to integrate partial page rendering is integrated into ASP.NET with minimal changes
into your project.
Walkthrough: Integrating Partial Rendering into an
Existing Project
1. In Microsoft Visual Studio 2008, create a new ASP.NET Web Site project by going to
File –> New –> Web Site… and selecting “ASP.NET Web Site” from the dialog. You
can name it whatever you like, and you may install it either to the file system or into
Internet Information Services (IIS).
2. You will be presented with the blank default page with basic ASP.NET markup (a serverside form and an @Page directive). Drop a Label called Label1 and a Button called
Button1 onto the page within the form element. You may set their text properties to
whatever you like.
3. In Design view, double-click Button1 to generate a code-behind event handler. Within
this event handler, set Label1.Text to “You clicked the button!”.
Listing 1: Markup for default.aspx before partial rendering is enabled
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head
runat="server">
<title>Untitled
Page</title>
</head>
<body>
<form id="form1"
runat="server">
<div>
<asp:Label
ID="Label1" runat="server" Text="This is a
label!"></asp:Label>
<asp:Button ID="Button1"
runat="server" Text="Click Me" OnClick="Button1_Click"
/>
</div>
</form>
</body> </html>
Listing 2: Codebehind (trimmed) in default.aspx.cs
public partial class _Default : System.Web.UI.Page {
Button1_Click(object sender, EventArgs e)
{
clicked the button!";
} }
protected void
Label1.Text = "You
4. Press F5 to launch your web site. Visual Studio will prompt you to add a web.config file
to enable debugging; do so. When you click the button, notice that the page refreshes to
change the text in the label, and there is a brief flicker as the page is redrawn.
5. After closing your browser window, return to Visual Studio and to the markup page.
Scroll down in the Visual Studio toolbox, and find the tab labeled “AJAX Extensions.”
(If you do not have this tab because you are using an older version of AJAX or Atlas
extensions, refer to the walkthrough for registering the AJAX Extensions toolbox items
later in this whitepaper, or install the current version with the Windows Installer
downloadable from the website).
(Click to view full-size image)
a. Known Issue: If you install Visual Studio 2008 Beta 2 onto a computer that already has
Visual Studio 2005 installed with the ASP.NET 2.0 AJAX Extensions, Visual Studio
2008 will import the “AJAX Extensions” toolbox items. You can determine whether this
is the case by examining the tooltip of the components; they should say “Version
3.5.0.0”. If they say “Version 2.0.0.0,” then you have imported your old toolbox items,
and will need to manually import them by using the “Choose Toolbox Items” dialog in
Visual Studio. You will be unable to add Version 2 controls via the designer.
6. Before the <asp:Label> tag begins, create a line of whitespace, and double-click on the
UpdatePanel control in the toolbox. Note that a new @Register directive is included at
the top of the page, indicating that controls within the System.Web.UI namespace should
be imported using the asp: prefix.
7. Drag the closing </asp:UpdatePanel> tag past the end of the Button element, so that the
element is well-formed with the Label and Button controls wrapped.
8. After the opening <asp:UpdatePanel> tag, begin opening a new tag. Note that
IntelliSense prompts you with two options. In this case, create a <ContentTemplate>
tag. Be sure to wrap this tag around your Label and Button so that the markup is wellformed.
(Click to view full-size image)
9. Anywhere within the <form> element, include a ScriptManager control by doubleclicking on the ScriptManager item in the toolbox.
10. Edit the <asp:ScriptManager> tag so that it includes the attribute
EnablePartialRendering=”true”.
Listing 3: Markup for default.aspx with partial rendering enabled
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default" %> <%@ Register Assembly="System.Web.Extensions,
Version=1.0.61025.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
Namespace="System
.Web.UI" TagPrefix="asp" %> <!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" >
<head
runat="server">
<title>Untitled
Page</title>
</head>
<body>
<form id="form1"
runat="server">
<asp:ScriptManager
EnablePartialRendering="true"
ID="ScriptManager1"
runat="server"></asp:ScriptManager>
<div>
<asp:UpdatePanel ID="UpdatePanel1"
runat="server">
<ContentTemplate>
<asp:Label ID="Label1"
runat="server"
Text="This is a
label!"></asp:Label>
<asp:Button ID="Button1"
runat="server"
Text="Click Me"
OnClick="Button1_Click"
/>
</ContentTemplate>
</asp:Upda
tePanel>
</div>
</form>
</body> </html>
11. Open your web.config file. Notice that Visual Studio has automatically added a
compilation reference to System.Web.Extensions.dll.
a. What’s New in Visual Studio 2008: The web.config that comes with the ASP.NET Web
Site project templates automatically includes all necessary references to the ASP.NET
AJAX Extensions, and includes commented sections of configuration information that
can be un-commented to enable additional functionality. Visual Studio 2005 had similar
templates when ASP.NET 2.0 AJAX Extensions were installed. However, in Visual
Studio 2008, the AJAX Extensions are opt-out by default (that is, they are referenced by
default, but can be removed as references).
(Click to view full-size image)
12. Press F5 to launch your website. Note how no source code changes were required to
support partial rendering – only markup was changed.
When you launch your website, you should see that partial rendering is now enabled, because
when you click on the button there will be no flicker, nor will there be any change in the page
scroll position (this example does not demonstrate that). If you were to look at the rendered
source of the page after clicking the button, it will confirm that in fact a post-back has not
occurred – the original label text is still part of the source markup, and the label has changed
through JavaScript.
Visual Studio 2008 Beta 2 does not appear to come with a pre-defined template for an ASP.NET
AJAX-Enabled web site. However, such a template was available within Visual Studio 2005 if
the Visual Studio 2005 and ASP.NET 2.0 AJAX Extensions were installed. Consequently,
configuring a web site and starting with the AJAX-Enabled Web Site template will likely be
even easier, as the template should include a fully-configured web.config file (supporting all of
the ASP.NET AJAX Extensions, including Web Services access and JSON serialization –
JavaScript Object Notation) and includes an UpdatePanel and ContentTemplate within the main
Web Forms page by default. Enabling partial rendering with this default page is as simple as
revisiting Step 10 of this walkthrough and dropping controls onto the page.
The ScriptManager Control
ScriptManager Control Reference
Markup-Enabled Properties:
Property Name
AllowCustomErrorsRedirect
Type
Bool
AsyncPostBackError- String
Message
AsyncPostBackInt32
Timeout
EnableScriptGlobalization
EnableScriptLocalization
ScriptLoadTimeout
Bool
ScriptMode
Enum (Auto,
Debug, Release,
Bool
Int32
Description
Specifies whether to use the custom error
section of the web.config file to handle
errors.
Gets or sets the error message sent to the
client if an error is raised.
Gets or sets the default amount of a time a
client should wait for the asynchronous
request to complete.
Gets or sets whether script globalization is
enabled.
Gets or sets whether script localization is
enabled.
Determines the number of seconds
allowed for loading scripts into the client
Gets or sets whether to render release
versions of scripts
Inherit)
String
ScriptPath
Gets or sets the root path to the location of
script files to be sent to the client.
Code-Only Properties:
Property Name
AuthenticationService
IsDebuggingEnabled
IsInAsyncPostback
ProfileService
Scripts
Services
SupportsPartialRendering
Type
Description
AuthenticationService- Gets details about the ASP.NET
Manager
Authentication Service proxy that
will be sent to the client.
Bool
Gets whether script and code
debugging is enabled.
Bool
Gets whether the page is currently
in an asynchronous post back
request.
ProfileServiceGets details about the ASP.NET
Manager
Profiling Service proxy that will
be sent to the client.
Collection<ScriptGets a collection of script
Reference>
references that will be sent to the
client.
Collection<ServiceGets a collection of Web Service
Reference>
proxy references that will be sent
to the client.
Bool
Gets whether the current client
supports partial rendering. If this
property returns false, then all
page requests will be standard
postbacks.
Public Code Methods:
Method Name
SetFocus(string)
Type
Void
Description
Sets the focus of the client to a particular control
when the request has completed.
Markup Descendants:
Tag
Description
<AuthenticationService> Provides details about the proxy to the ASP.NET
authentication service.
<ProfileService>
Provides details about the proxy to the ASP.NET profiling
service.
<Scripts>
Provides additional script references.
<asp:ScriptReference>
Denotes a specific script reference.
<Service>
<asp:ServiceReference>
Provides additional Web Service references which will have
proxy classes generated.
Denotes a specific Web Service reference.
The ScriptManager control is the essential core for the ASP.NET AJAX Extensions. It provides
access to the script library (including the extensive client-side script type system), supports
partial rendering, and provides extensive support for additional ASP.NET services (such as
authentication and profiling, but also other Web Services). The ScriptManager control also
provides globalization and localization support for the client scripts.
Providing Alterative and Supplemental Scripts
While the Microsoft ASP.NET 2.0 AJAX Extensions include the entire script code in both debug
and release editions as resources embedded in the referenced assemblies, developers are free to
redirect the ScriptManager to customized script files, as well as register additional necessary
scripts.
To override the default binding for the typically-included scripts (such as those which support
the Sys.WebForms namespace and the custom typing system), you can register for the
ResolveScriptReference event of the ScriptManager class. When this method is called, the
event handler has the opportunity to change the path to the script file in question; the script
manager will then send a different or customized copy of the scripts to the client.
Additionally, script references (represented by the ScriptReference class) can be included
programmatically or via markup. To do so, either programmatically modify the
ScriptManager.Scripts collection, or include <asp:ScriptReference> tags under the
<Scripts> tag, which is a first-level child of the ScriptManager control.
Custom Error Handling for UpdatePanels
Although updates are handled by triggers specified by UpdatePanel controls, the support for
error handling and custom error messages is handled by a page’s ScriptManager control instance.
This is done by exposing an event, AsyncPostBackError, to the page which can then provide
custom exception-handling logic.
By consuming the AsyncPostBackError event, you may specify the
AsyncPostBackErrorMessage property, which then causes an alert box to be raised upon
completion of the callback.
Client-side customization is also possible instead of using the default alert box; for instance, you
may want to display a customized <div> element rather than the default browser modal dialog.
In this case, you can handle the error in client script:
Listing 5: Client-side script to display custom errors
<script type=”text/javascript”>
<!-Sys.WebForms.PageRequestManager.getInstance().add_EndRequest(Request_En
d);
function Request_End(sender, args)
{
if
(args.get_error() != undefined)
{
var errorMessage =
“”;
if (args.get_response().get_statusCode() ==
“200”)
{
errorMessage =
args.get_error().message;
}
else
{
// the server wasn’t the
problem...
errorMessage = “An unknown error
occurred...”;
}
// do something with the
errorMessage here.
// now make sure the system knows we
handled the
error.
args.set_errorHandled(true);
}
}
//
--> </script>
Quite simply, the above script registers a callback with the client-side AJAX runtime for when
the asynchronous request has been completed. It then checks to see whether an error was
reported, and if so, processes the details of it, finally indicating to the runtime that the error was
handled in custom script.
Globalization and Localization Support
The ScriptManager control provides extensive support for localization of script strings and user
interface components; however, that topic is outside of the scope of this whitepaper. For more
information, see the whitepaper, “Globalization Support in ASP.NET AJAX Extensions.”
The UpdatePanel Control
UpdatePanel Control Reference
Markup-Enabled Properties:
Property Name
ChildrenAsTriggers
Type
bool
RenderMode
enum (Block,
Inline)
enum (Always,
Conditional)
UpdateMode
Description
Specifies whether child controls automatically
invoke refresh on postback.
Specifies the way the content will be visually
presented.
Specifies whether the UpdatePanel is always
refreshed during a partial render or if it is only
refreshed when a trigger is hit.
Code-Only Properties:
Property Name
IsInPartialRendering
Type
bool
Description
Gets whether the UpdatePanel is
supporting partial rendering for the
current request.
ContentTemplate
ContentTemplateContainer
Triggers
ITemplate
Gets the markup template for the
update request.
Control
Gets the programmatic template for the
update request.
UpdatePanelGets the list of triggers associated with
TriggerCollection the current UpdatePanel.
Public Code Methods:
Method Name
Update()
Type
Void
Description
Updates the specified UpdatePanel
programmatically. Allows a server
request to trigger a partial render of an
otherwise-untriggered UpdatePanel.
Markup Descendants:
Tag
<ContentTemplate>
Description
Specifies the markup to be used to render the partial
rendering result. Child of <asp:UpdatePanel>.
<Triggers>
Specifies a collection of n controls associated with
updating this UpdatePanel. Child of <asp:UpdatePanel>.
<asp:AsyncPostBackTrigger> Specifies a trigger that invokes a partial page render for
the given UpdatePanel. This may or may not be a control
as a descendant of the UpdatePanel in question. Granular
to the event name.Child of <Triggers>.
<asp:PostBackTrigger>
Specifies a control that causes the entire page to refresh.
This may or may not be a control as a descendant of the
UpdatePanel in question. Granular to the object. Child of
<Triggers>.
The UpdatePanel control is the control that delimits the server-side content that will take part in
the partial rendering functionality of the AJAX Extensions. There is no limit to the number of
UpdatePanel controls that can be on a page, and they can be nested. Each UpdatePanel is
isolated, so that each can work independently (you can have two UpdatePanels running at the
same time, rendering different parts of the page, independent of the page’s postback).
The UpdatePanel control primarily deals with control triggers – by default, any control contained
within an UpdatePanel’s ContentTemplate that creates a postback is registered as a trigger for
the UpdatePanel. This means that the UpdatePanel can work with the default data-bound controls
(such as the GridView), with user controls, and they can be programmed in script.
By default, when a partial page render is triggered, all UpdatePanel controls on the page will be
refreshed, whether or not the UpdatePanel controls defined triggers for such action. For example,
if one UpdatePanel defines a Button control, and that Button control is clicked, all UpdatePanel
controls on that page will be refreshed by default. This is because, by default, the UpdateMode
property of the UpdatePanel is set to Always. Alternatively, you may set the UpdateMode
property to Conditional, which means that the UpdatePanel will only be refreshed if a specific
trigger is hit.
Custom Control Notes
An UpdatePanel can be added to any user control or custom control; however, the page on which
these controls are included must also include a ScriptManager control with the property
EnablePartialRendering set to true.
One way in which you might account for this when using Web Custom Controls is to override
the protected CreateChildControls() method of the CompositeControl class. By doing so,
you can inject an UpdatePanel between the control’s children and the outside world if you
determine the page supports partial rendering; otherwise, you can simply layer the child controls
into a container Control instance.
UpdatePanel Considerations
The UpdatePanel operates as something of a black-box, wrapping ASP.NET postbacks within
the context of a JavaScript XMLHttpRequest. However, there are significant performance
considerations to bear in mind, both in terms of behavior and speed. To understand how the
UpdatePanel works, so that you can best decide when its use is appropriate, you should examine
the AJAX exchange. The following example uses an existing site and, Mozilla Firefox with the
Firebug extension (Firebug captures XMLHttpRequest data).
Consider a form that, among other things, has a postal code textbox which is supposed to
populate a city and state field on a form or control. This form ultimately collects membership
information, including a user’s name, address, and contact information. There are many design
considerations to take into account, based on the requirements of a specific project.
(Click to view full-size image)
(Click to view full-size image)
In the original iteration of this application, a control was built that incorporated the entirety of
the user registration data, including the postal code, city, and state. The entire control was
wrapped within an UpdatePanel and dropped onto a Web Form. When the postal code is entered
by the user, the UpdatePanel detects the event (the corresponding TextChanged event in the
back-end, either by specifying triggers or by using the ChildrenAsTriggers property set to true).
AJAX posts all of the fields within the UpdatePanel, as captured by FireBug (see the diagram on
the right).
As the screen capture indicates, values from every control within the UpdatePanel are delivered
(in this case, they are all empty), as well as the ViewState field. All told, over 9kb of data is sent,
when in fact only five bytes of data were needed to make this particular request. The response is
even more bloated: in total, 57kb is sent to the client, simply to update a text field and a dropdown field.
It may also be of interest to see how ASP.NET AJAX updates the presentation. The response
portion of the UpdatePanel’s update request is shown in the Firebug console display on the left;
it is a specially-formulated pipe-delimited string that is broken up by the client script and then
reassembled on the page. Specifically, ASP.NET AJAX sets the innerHTML property of the
HTML element on the client that represents your UpdatePanel. As the browser re-generates the
DOM, there is a slight delay, depending on the amount of information that needs to be processed.
The regeneration of the DOM triggers a number of additional issues:
(Click to view full-size image)


If the focused HTML element is within the UpdatePanel, it will lose focus. So, for users
who pressed the Tab key to exit the postal code text box, their next destination would
have been the City text box. However, once the UpdatePanel refreshed the display, the
form would no longer have had focus, and pressing Tab would have started highlighting
the focus elements (such as links).
If any type of custom client-side script is in use that accesses DOM elements, references
persisted by functions may become defunct after a partial postback.
UpdatePanels are not intended to be catch-all solutions. Rather, they provide a quick solution for
certain situations, including prototyping, small control updates, and provide a familiar interface
to ASP.NET developers who might be familiar with the .NET object model but less-so with the
DOM. There are a number of alternatives that may result in better performance, depending on
the application scenario:


Consider using PageMethods and JSON (JavaScript Object Notation) allows the
developer to invoke static methods on a page as if a web service call was being invoked.
Because the methods are static, no state is necessary; the script caller supplies the
parameters, and the result is returned asynchronously.
Consider using a Web Service and JSON if a single control needs to be used in several
places across an application. This again requires very little special work, and works
asynchronously.
Incorporating functionality through Web Services or Page Methods has drawbacks as well. First
and foremost, ASP.NET developers typically tend to build small components of functionality
into user controls (.ascx files). Page methods cannot be hosted in these files; they must be hosted
within the actual .aspx page class. Web services, similarly, must be hosted within the .asmx
class. Depending on the application, this architecture may violate the Single Responsibility
Principle, in that the functionality for a single component is now spread across two or more
physical components which may have little or no cohesive ties.
Finally, if an application requires that UpdatePanels are used, the following guidelines should
assist with troubleshooting and maintenance.



Nest UpdatePanels as little as possible, not only within-units, but also across units of
code. For example, having an UpdatePanel on a Page that wraps a Control, while that
Control also contains an UpdatePanel, which contains another Control that contains an
UpdatePanel, is cross-unit nesting. This helps to keep clear which elements should be
refreshing, and prevents unexpected refreshes to child UpdatePanels.
Keep the ChildrenAsTriggers property set to false, and explicitly set triggering
events. Utilizing the <Triggers> collection is a much clearer way to handle events, and
may prevent unexpected behavior, helping with maintenance tasks and forcing a
developer to “opt-in” for an event.
Use the smallest possible unit to achieve functionality. As noted in the discussion of
the postal code service, wrapping only the bare minimum reduces time to the server, total
processing, and the footprint of the client-server exchange, enhancing performance.
The UpdateProgress Control
UpdateProgress Control Reference
Markup-Enabled Properties:
Property Name
AssociatedUpdatePanelID
DisplayAfter
Type
String
DynamicLayout
bool
Int
Description
Specifies the ID of the UpdatePanel that this
UpdateProgress should report on.
Specifies the timeout in milliseconds before
this control is displayed after the
asynchronous request begins.
Specifies whether the progress is rendered
dynamically.
Markup Descendants:
Tag
<ProgressTemplate>
Description
Contains the control template set for the content that will be
displayed with this control.
The UpdateProgress control gives you a measure of feedback to keep your users’ interest while
doing the necessary work to transport to the server. This can help your users know that you’re
doing something even though it may not be apparent, especially since most users are used to the
page refreshing and seeing the status bar highlight.
As a note, UpdateProgress controls can appear anywhere on a page hierarchy. However, in cases
in which a partial postback is initiated from a child UpdatePanel (where an UpdatePanel is
nested within another UpdatePanel), postbacks that trigger the child UpdatePanel will cause
UpdateProgress templates to be displayed for the child UpdatePanel as well as the parent
UpdatePanel. But, if the trigger is a direct child of the parent UpdatePanel, then only the
UpdateProgress templates associated with the parent will be displayed.
Summary
The Microsoft ASP.NET AJAX extensions are sophisticated products designed to assist in
making web content more accessible and to provide a richer user experience to your web
applications. As part of the ASP.NET AJAX Extensions, the partial page rendering controls,
including the ScriptManager, the UpdatePanel, and UpdateProgress controls are some of the
most visible components of the toolkit.
The ScriptManager component integrates the provision of client JavaScript for the Extensions, as
well as enables the various server- and client-side components to work together with minimal
development investment.
The UpdatePanel control is the apparent magic box – markup within the UpdatePanel can have
server-side Codebehind and not trigger a page refresh. UpdatePanel controls can be nested, and
can be dependent on controls in other UpdatePanels. By default, UpdatePanels handle any
postbacks invoked by their descendant controls, although this functionality can be finely tuned,
either declaratively or programmatically.
When using the UpdatePanel control, developers should be aware of the performance impact that
could potentially arise. Potential alternatives include web services and page methods, though the
design of the application should be considered.
The UpdateProgress control allows the user to know that she or he is not being ignored, and that
the behind-the-scenes request is going on while the page is otherwise not doing anything to
respond to the user input. It also includes the ability to abort partial rendering results.
Together, these tools assist creating a rich and seamless user experience by making server work
less apparent to the user and interrupting workflow less.
Introduction
Microsoft’s ASP.NET technology brings an object-oriented and event-driven programming
model and unites it with the benefits of compiled code. However, its server-side processing
model has several drawbacks inherent in the technology, many of which can be addressed by the
new features included in the Microsoft ASP.NET 3.5 AJAX Extensions. These extensions enable
many new rich client features, including partial rendering of pages without requiring a full page
refresh, the ability to access Web Services via client script (including the ASP.NET profiling
API), and an extensive client-side API designed to mirror many of the control schemes seen in
the ASP.NET server-side control set.
This whitepaper examines the XML Triggers functionality of the ASP.NET AJAX UpdatePanel
component. XML Triggers give granular control over the components that can cause partial
rendering for specific UpdatePanel controls.
This whitepaper is based on the Beta 2 release of the .NET Framework 3.5 and Visual Studio
2008. The ASP.NET AJAX Extensions, previously an add-on assembly targeted at ASP.NET
2.0, are now integrated into the .NET Framework Base Class Library. This whitepaper also
assumes that you will be working with Visual Studio 2008, not Visual Web Developer Express,
and will provide walkthroughs according to the user interface of Visual Studio (although code
listings will be entirely compatible regardless of development environment).
Triggers
Triggers for a given UpdatePanel, by default, automatically include any child controls that
invoke a postback, including (for example) TextBox controls that have their AutoPostBack
property set to true. However, triggers can also be included declaratively using markup; this is
done within the <triggers> section of the UpdatePanel control declaration. Although triggers
can be accessed via the Triggers collection property, it is recommended that you register any
partial render triggers at run-time (for instance, if a control is not available at design time) by
using the RegisterAsyncPostBackControl(Control) method of the ScriptManager object for
your page, within the Page_Load event. Remember that Pages are stateless, and so you should
re-register these controls every time they are created.
Automatic child trigger inclusion can also be disabled (so that child controls that create
postbacks do not automatically trigger partial renders) by setting the ChildrenAsTriggers
property to false. This allows you the greatest flexibility in assigning which specific controls
may invoke a page render, and is recommended, so that a developer will “opt-in” to respond to
an event, rather than handling any events that may arise.
Note that when UpdatePanel controls are nested, when the UpdateMode is set to Conditional, if
the child UpdatePanel is triggered, but the parent is not, then only the child UpdatePanel will
refresh. However, if the parent UpdatePanel is refreshed, then the child UpdatePanel will also be
refreshed.
The <Triggers> Element
When working in the markup editor in Visual Studio, you may notice (from IntelliSense) that
there are two child elements of an UpdatePanel control. The most-frequently seen element is the
<ContentTemplate> element, which essentially encapsulates the content that will be held by the
update panel (the content for which we are enabling partial rendering). The other element is the
<Triggers> element, which specifies the controls on the page (or the user control, if you are
using one) that will trigger a partial render of the UpdatePanel control in which the <Triggers>
element resides.
The <Triggers> element can contain any number each of two child nodes:
<asp:AsyncPostBackTrigger> and <asp:PostBackTrigger>. They both accept two attributes,
ControlID and EventName, and can specify any Control within the current unit of encapsulation
(for instance, if your UpdatePanel control resides within a Web User Control, you should not
attempt to reference a Control on the Page on which the User Control will reside).
The <asp:AsyncPostBackTrigger> element is particularly useful in that it can target any event
from a Control that exists as a child of any UpdatePanel control in the unit of encapsulation, not
just the UpdatePanel under which this trigger is a child. Thus, any control can be made to trigger
a partial page update.
Similarly, the <asp:PostBackTrigger> element can be used to trigger a partial page render, but
one that requires a full round-trip to the server. This trigger element can also be used to force a
full page render when a control would otherwise normally trigger a partial page render (for
instance, when a Button control exists in the <ContentTemplate> element of an UpdatePanel
control). Again, the PostBackTrigger element can specify any control that is a child of any
UpdatePanel control in the current unit of encapsulation.
<Triggers> Element Reference
Markup Descendants:
Tag
Description
<asp:AsyncPostBackTrigger> Specifies a control and event that will cause a partial page update
for the UpdatePanel that contains this trigger reference.
<asp:PostBackTrigger>
Specifies a control and event that will cause a full page update (a
full page refresh). This tag can be used to force a full refresh
when a control would otherwise trigger partial rendering.
Walkthrough: Cross-UpdatePanel Triggers
1. Create a new ASP.NET page with a ScriptManager object set to enable partial rendering. Add
two UpdatePanels to this page – in the first, include a Label control (“Label1”) and two Button
controls (“Button1” and “Button2”). Button1 should say “Click to Update Both” and Button2
should say “Click to Update This,” or something along those lines. In the second UpdatePanel,
include only a Label control (“Label2”), but set its ForeColor property to something other than
the default to differentiate it.
2. Set the UpdateMode property of both UpdatePanel tags to Conditional.
Listing 1: Markup for default.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" >
<head
runat="server">
<title>Untitled
Page</title>
</head>
<body>
<form id="form1"
runat="server">
<asp:ScriptManager
EnablePartialRendering="true"
ID="ScriptManager1"
runat="server"></asp:ScriptManager>
<div>
<asp:UpdatePanel ID="UpdatePanel1"
runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<asp:Label
ID="Label1" runat="server" /><br />
<asp:Button
ID="Button1" runat="server"
Text="Update
Both Panels" OnClick="Button1_Click"
/>
<asp:Button ID="Button2"
runat="server"
Text="Update This Panel"
OnClick="Button2_Click"
/>
</ContentTemplate>
</asp:Upda
tePanel>
<asp:UpdatePanel ID="UpdatePanel2"
runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<asp:Label
ID="Label2" runat="server" ForeColor="red"
/>
</ContentTemplate>
<Trig
gers>
<asp:AsyncPostBackTrigger
ControlID="Button1" EventName="Click"
/>
</Triggers>
</asp:UpdatePanel
>
</div>
</form>
</body> </html>
3. In the Click event handler for Button1, set Label1.Text and Label2.Text to something timedependent (such as DateTime.Now.ToLongTimeString()). For the Click event handler for
Button2, set only Label1.Text to the time-dependent value.
Listing 2: Codebehind (trimmed) in default.aspx.cs:
public partial class _Default : System.Web.UI.Page {
protected void
Button1_Click(object sender, EventArgs e)
{
Label1.Text =
DateTime.Now.ToLongTimeString();
Label2.Text =
DateTime.Now.ToLongTimeString();
}
protected void
Button2_Click(object sender, EventArgs e)
{
Label1.Text =
DateTime.Now.ToLongTimeString();
} }
4. Press F5 to build and run the project. Note that, when you click “Update Both Panels”, both
labels change text; however, when you click “Update This Panel,” only Label1 updates.
(Click to view full-size image)
Under the Hood
Utilizing the example we just constructed, we can take a look at what ASP.NET AJAX is doing
and how our UpdatePanel cross-panel triggers work. To do so, we will work with the generated
page source HTML, as well as the Mozilla Firefox extension called FireBug – with it, we can
easily examine the AJAX postbacks. We will also use the .NET Reflector tool by Lutz Roeder.
Both of these tools are freely available online, and can be found with an internet search.
An examination of the page source code shows almost nothing out of the ordinary; the
UpdatePanel controls are rendered as <div> containers, and we can see the script resource
includes provided by the <asp:ScriptManager>. There are also some new AJAX-specific calls
to the PageRequestManager that are internal to the AJAX client script library. Finally, we see the
two UpdatePanel containers – one with the rendered <input> buttons with the two <asp:Label>
controls rendered as <span> containers. (If you inspect the DOM tree in FireBug, you will notice
that the labels are dimmed to indicate that they are not producing visible content).
Click the “Update This Panel” button, and notice the top UpdatePanel will be updated with the
current server time. In FireBug, choose the “Console” tab so that you can examine the request.
Examine the POST request parameters first:
(Click to view full-size image)
Note that the UpdatePanel has indicated to the server-side AJAX code precisely which control
tree was fired via the ScriptManager1 parameter: Button1 of the UpdatePanel1 control. Now,
click on the “Update Both Panels” button. Then, examining the response, we see a pipe-
delimited series of variables set in a string; specifically, we see the top UpdatePanel,
UpdatePanel1, has the entirety of its HTML sent to the browser. The AJAX client script library
substitutes the UpdatePanel’s original HTML content with the new content via the .innerHTML
property, and so the server sends the changed content from the server as HTML.
Now, click on the “Update Both Panels” button and examine the results from the server. The
results are very similar – both UpdatePanels receive new HTML from the server. As with the
previous callback, additional page state is sent.
As we can see, because no special code is utilized to perform an AJAX postback, the AJAX
client script library is able to intercept form postbacks without any additional code. Server
controls automatically utilize JavaScript so that they do not automatically submit the form –
ASP.NET automatically injects code for form validation and state already, primarily achieved by
automatic script resource inclusion, the PostBackOptions class, and the ClientScriptManager
class.
For instance, consider a CheckBox control; examine the class disassembly in .NET Reflector. To
do so, ensure that your System.Web assembly is open, and navigate to the
System.Web.UI.WebControls.CheckBox class, opening the RenderInputTag method. Look for
a conditional that checks the AutoPostBack property:
(Click to view full-size image)
When automatic postback is enabled on a CheckBox control (via the AutoPostBack property
being true), the resultant <input> tag is therefore rendered with an ASP.NET event handling
script in its onclick attribute. The interception of the form’s submission, then, allows ASP.NET
AJAX to be injected into the page nonintrusively, helping to avoid any potential breaking
changes that might occur by utilizing a possibly-imprecise string replacement. Furthermore, this
enables any custom ASP.NET control to utilize the power of ASP.NET AJAX without any
additional code to support its use within an UpdatePanel container.
The <triggers> functionality corresponds to the values initialized in the PageRequestManager
call to _updateControls (note that the ASP.NET AJAX client script library utilizes the
convention that methods, events, and field names that begin with an underscore are marked as
internal, and are not meant for use outside of the library itself). With it, we can observe which
controls are intended to cause AJAX postbacks.
For example, let’s add two additional controls to the page, leaving one control outside of the
UpdatePanels entirely, and leaving one within an UpdatePanel. We will add a CheckBox control
within the upper UpdatePanel, and drop a DropDownList with a number of colors defined within
the list. Here is the new markup:
Listing 3: New Markup
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" >
<head
id="Head1" runat="server">
<title>Untitled
Page</title>
</head>
<body>
<form id="form1"
runat="server">
<asp:ScriptManager
EnablePartialRendering="true"
ID="ScriptManager1"
runat="server"></asp:ScriptManager>
<div>
<asp:UpdatePanel ID="UpdatePanel1"
runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<asp:Label
ID="Label1" runat="server" /><br />
<asp:Button
ID="Button1" runat="server"
Text="Update
Both Panels" OnClick="Button1_Click"
/>
<asp:Button ID="Button2"
runat="server"
Text="Update This Panel"
OnClick="Button2_Click" />
<asp:CheckBox
ID="cbDate" runat="server"
Text="Include
Date"
AutoPostBack="false"
OnCheckedChanged="cbD
ate_CheckedChanged"
/>
</ContentTemplate>
</asp:Upda
tePanel>
<asp:UpdatePanel ID="UpdatePanel2"
runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<asp:Label
ID="Label2" runat="server"
ForeColor="red"
/>
</ContentTemplate>
<Trig
gers>
<asp:AsyncPostBackTrigger
ControlID="Button1"
EventName="Click"
/>
<asp:AsyncPostBackTrigger
ControlID="ddlColor"
EventName="SelectedIn
dexChanged"
/>
</Triggers>
</asp:UpdatePanel
>
<asp:DropDownList ID="ddlColor"
runat="server"
AutoPostBack="true"
/>
/>
/>
form>
OnSelectedIndexChanged="ddlColor_SelectedIndexChanged">
<asp:ListItem Selected="true" Value="Red"
<asp:ListItem Value="Blue"
<asp:ListItem Value="Green"
</asp:DropDownList>
</div>
</body> </html>
</
And here is the new code-behind:
Listing 4: Codebehind
public partial class _Default : System.Web.UI.Page {
protected void
Button1_Click(object sender, EventArgs e)
{
if
(cbDate.Checked)
{
Label1.Text =
DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss");
Label2.Text =
DateTime.Now.ToString("MM/dd/yyyy
hh:mm:ss");
}
else
{
Label1.Text
= DateTime.Now.ToLongTimeString();
Label2.Text =
DateTime.Now.ToLongTimeString();
}
}
protected void
Button2_Click(object sender, EventArgs e)
{
if
(cbDate.Checked)
{
Label1.Text =
DateTime.Now.ToString("MM/dd/yyyy
hh:mm:ss");
}
else
{
Label1.Text
= DateTime.Now.ToLongTimeString();
}
}
protected void
cbDate_CheckedChanged(object sender, EventArgs
e)
{
cbDate.Font.Bold = cbDate.Checked;
}
protected
void ddlColor_SelectedIndexChanged(object sender, EventArgs
e)
{
Color c =
Color.FromName(ddlColor.SelectedValue);
Label2.ForeColor =
c;
} }
The idea behind this page is that the drop-down list selects one of three colors to show the
second label, that the check box determines both whether it is bold, and whether the labels
display the date as well as the time. The check box should not cause an AJAX update, but the
drop-down list should, even though it is not housed within an UpdatePanel.
(Click to view full-size image)
As is apparent in the above screen shot, the most-recent button to be clicked was the right button
“Update This Panel,” which updated the top time independent of the bottom time. The date was
also switched off between clicks, as the date is visible in the bottom label. Finally of interest is
the bottom label’s color: it was updated more recently than the label’s text, which demonstrates
that control state is important, and users expect it to be preserved through AJAX postbacks.
However, the time was not updated. The time was automatically repopulated through the
persistence of the __VIEWSTATE field of the page being interpreted by the ASP.NET runtime
when the control was being re-rendered on the server. The ASP.NET AJAX server code does not
recognize in which methods the controls are changing state; it simply repopulates from view
state and then runs the events that are appropriate.
It should be pointed out, however, that had I initialized the time within the Page_Load event, the
time would have been incremented correctly. Consequently, developers should be wary that the
appropriate code is being run during the appropriate event handlers, and avoid use of Page_Load
when a control event handler would be appropriate.
Summary
Introduction
As part of the .NET Framework 3.5, Microsoft is delivering a sizable environment upgrade; not
only is a new development environment available, but the new Language-Integrated Query
(LINQ) features and other language enhancements are forthcoming. In addition, some familiar
features of other toolsets, notably the ASP.NET AJAX Extensions, are being included as firstclass members of the .NET Framework Base Class Library. These extensions enable many new
rich client features, including partial rendering of pages without requiring a full page refresh, the
ability to access Web Services via client script (including the ASP.NET profiling API), and an
extensive client-side API designed to mirror many of the control schemes seen in the ASP.NET
server-side control set.
This whitepaper looks at the implementation and use of the ASP.NET Profiling and Forms
Authentication services as they are exposed by the Microsoft ASP.NET AJAX ExtensionsThe
AJAX Extensions make Forms authentication incredibly easy to support, as it (as well as the
Profiling Service) is exposed through a Web Service proxy script. The AJAX Extensions also
support custom authentication through the AuthenticationServiceManager class.
This whitepaper is based on the Beta 2 release of the Visual Studio 2008 and the .NET
Framework 3.5. This whitepaper also assumes that you will be working with Visual Studio 2008
Beta 2, not Visual Web Developer Express, and will provide walkthroughs according to the user
interface of Visual Studio. Some code samples may utilize project templates unavailable in
Visual Web Developer Express.
Profiles and Authentication
The Microsoft ASP.NET Profiles and Authentication services are provided by the ASP.NET
Forms Authentication system, and are standard components of ASP.NET. The ASP.NET AJAX
Extensions provide script access to these services via script proxies, through a fairly
straightforward model under the Sys.Services namespace of the client AJAX library.
The Authentication service allows users to provide credentials in order to receive an
authentication cookie, and is the gateway service to allow custom user profiles provided by
ASP.NET. Use of the ASP.NET AJAX authentication service is compatible with standard
ASP.NET Forms authentication, so applications currently using Forms authentication (such as
with the Login control) will not be broken by upgrading to the AJAX authentication service.
The Profile service allows the automatic integration and storage of user data based on
membership as provided by the Authentication service. The stored data is specified by the
web.config file, and the various profiling service providers handle the data management. As with
the Authentication service, the AJAX Profile service is compatible with the standard ASP.NET
profile service, so that pages currently incorporating features of the ASP.NET Profile service
should not be broken by including AJAX support.
Incorporating the ASP.NET Authentication and Profiling services themselves into an application
is outside of the scope of this whitepaper. For more information on the topic, see the MSDN
Library reference article “Managing Users by Using Membership” at
http://msdn2.microsoft.com/en-us/library/tw292whz.aspx. ASP.NET also includes a utility to
automatically set up Membership with a SQL Server, which is the default authentication service
provider for ASP.NET Membership. For more information, see the article “ASP.NET SQL
Server Registration Tool (Aspnet_regsql.exe)” at http://msdn2.microsoft.com/enus/library/ms229862(vs.80).aspx.
Using the ASP.NET AJAX Authentication Service
The ASP.NET AJAX Authentication service must be enabled in the web.config file:
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true"
/>
</webServices>
</scripting> </system.web.extensions>
The Authentication service requires ASP.NET Forms authentication to be enabled and requires
cookies to be enabled on the client browser (a script cannot enable a cookieless session since
cookieless sessions require URL parameters).
Once the AJAX authentication service is enabled and configured, client script can immediately
take advantage of the Sys.Services.AuthenticationService object. Primarily, client script will
want to take advantage of the login method and isLoggedIn property. Several properties exist
to provide defaults for the login method, which can accept a large number of parameters.
Sys.Services.AuthenticationService members
login method:
The login() method begins a request to authenticate the user’s credentials. This method is
asynchronous and does not block execution.
Parameters:
Parameter Name
Meaning
userName
Required. The username to authenticate.
password
Optional (defaults to null). The user’s password.
isPersistent
Optional (defaults to false). Whether the user’s authentication cookie
should persist across sessions. If false, the user will log out when the
browser is closed or the session expires.
redirectUrl
Optional (defaults to null).The URL to redirect the browser to upon
successful authentication. If this parameter is null or an empty string,
no redirection occurs.
customInfo
Optional (defaults to null). This parameter is currently unused and is
reserved for future use.
loginCompletedCallback Optional (defaults to null).The function to call when the login has
successfully completed. If specified, this parameter overrides the
defaultLoginCompleted property.
failedCallback
Optional (defaults to null).The function to call when the login has
failed. If specified, this parameter overrides the defaultFailedCallback
property.
userContext
Optional (defaults to null). Custom user context data that should be
passed to the callback functions.
Return Value:
This function does not include a return value. However, a number of behaviors are included upon
completion of a call to this function:


The current page will either be refreshed or be changed if the redirectUrl parameter was
neither null nor an empty string.
However, if the parameter was null or an empty string, the loginCompletedCallback
parameter, or defaultLoginCompletedCallback property is called.

If the call to the web service fails, the failedCallback parameter of
defaultFailedCallback property is called.
logout method:
The logout() method removes the credentials cookie and logs out the current user from the web
application.
Parameters:
Parameter Name
Meaning
redirectUrl
Optional (defaults to null).The URL to redirect the browser to upon
successful authentication. If this parameter is null or an empty
string, no redirection occurs.
logoutCompletedCallback Optional (defaults to null).The function to call when the logout has
successfully completed. If specified, this parameter overrides the
defaultLogoutCompleted property.
failedCallback
Optional (defaults to null).The function to call when the login has
failed. If specified, this parameter overrides the
defaultFailedCallback property.
userContext
Optional (defaults to null). Custom user context data that should be
passed to the callback functions.
Return Value:
This function does not include a return value. However, a number of behaviors are included upon
completion of a call to this function:



The current page will either be refreshed or be changed if the redirectUrl parameter was
neither null nor an empty string.
However, if the parameter was null or an empty string, the logoutCompletedCallback
parameter, or defaultLogoutCompletedCallback property is called.
If the call to the web service fails, the failedCallback parameter of
defaultFailedCallback property is called.
defaultFailedCallback property (get, set):
This property specifies a function that should be called if a failure to communicate with the web
service occurs. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function AuthenticationFailureCallback(error, userContext, methodName);
Parameters:
Parameter Name
Meaning
error
Specifies the error information.
userContext
Specifies the user context information provided when the login or
logout function was called.
methodName
The name of the calling method.
defaultLoginCompletedCallback property (get, set):
This property specifies a function that should be called when the login web service call has
completed. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function AuthenticationLoginCompletedCallback(validCredentials, userContext,
methodName);
Parameters:
Parameter Name
Meaning
validCredentials
Specifies whether the user provided valid credentials. true if the
user successfully logged in; otherwise false.
userContext
Specifies the user context information provided when the login
function was called.
methodName
The name of the calling method.
defaultLogoutCompletedCallback property (get, set):
This property specifies a function that should be called when the logout web service call has
completed. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function AuthenticationLogoutCompletedCallback(result, userContext,
methodName);
Parameters:
Parameter Name
Meaning
result
This parameter will always be null; it is reserved for future use.
userContext
Specifies the user context information provided when the login
function was called.
methodName
The name of the calling method.
isLoggedIn property (get):
This property gets the current authentication state of the user; it is set by the ScriptManager
object during a page request.
This property returns true if the user is currently logged in; otherwise, it returns false.
path property (get, set):
This property programmatically determines the location of the authentication web service. It can
be used to override the default authentication provider, as well as one set declaratively in the
Path property of the ScriptManager control’s AuthenticationService child node (for more
information, see the “Using a Custom Authentication Service Provider” topic below).
Note that the location of the default authentication service does not change. However, ASP.NET
AJAX allows you to specify the location of a web service that provides the same class interface
as the ASP.NET AJAX authentication service proxy.
Note also that this property should not be set to a value that directs the script request off of the
current site. Because the current application would not receive the authentication credentials, it
would be useless; also, the technology underlying AJAX should not post cross-site requests, and
may generate a security exception in the client browser.
This property is a String object representing the path to the authentication web service.
timeout property (get, set):
This property determines the length of time to wait for the authentication service before
assuming the login request has failed. If the timeout expires while waiting for a call to complete,
the request-failed callback will be called, and the call will not complete.
This property is a Number object representing the number of milliseconds to wait for results from
the authentication service.
Code Sample: Logging into the Authentication Service
The following markup is an example ASP.NET page with a simple script call to the login and
logout methods of the AuthenticationService class.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html
xmlns="http://www.w3.org/1999/xhtml">
<head
runat="server">
<title>Login Example</title>
<script
type="text/javascript">
function
Login()
{
var userTextbox =
$get("txtUser");
var passTextbox =
$get("txtPassword");
Sys.Services.AuthenticationService.l
ogin(userTextbox.value,
passTextbox.value, false,
null, null,
LoginServiceCompleted,
LoginServiceFailed, "Context
Info");
}
function
Logout()
{
Sys.Services.AuthenticationServ
ice.logout(null,
LogoutServiceCompleted,
LoginServiceFailed, "Context
Info");
}
function LoginServiceFailed(error,
userContext, methodName)
{
alert('There
was an error with the authentication service:\n\n' +
error);
}
function
LoginServiceCompleted(validCredentials, userContext,
methodName)
{
if
(validCredentials)
{
alert('Grea
t! You successfully logged
in.');
}
else
{
alert('Oops! You gave us bad
credentials!');
}
}
functio
n LogoutServiceCompleted(result, userContext,
methodName)
{
alert('You have been logged
out from the web
site.');
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager
ID="ScriptManager1"
runat="server"
EnableScriptLocalization="true">
</asp:ScriptManager>
<div>
<asp:Text
Box ID="txtUser" runat="Server"></asp:TextBox>
<br
/>
<asp:TextBox ID="txtPassword" runat="Server"
TextMode="Password"/>
<br
/>
<asp:Button Text="Log in" ID="btnLogin"
runat="server"
OnClientClick="Login(); return
false;" />
</div>
</form>
</body> </html>
Accessing ASP.NET Profiling Data via AJAX
The ASP.NET profiling service is also exposed through the ASP.NET AJAX Extensions. Since
the ASP.NET profiling service provides a rich, granular API by which to store and retrieve user
data, this can be an excellent productivity tool.
The profile service must be enabled in web.config; it is not by default. To do so, ensure that the
profileService child element has enabled=”true” specified in web.config, and that you have
specified which properties can be read or written as follows:
<system.web.extensions>
<profileService
enabled="true"
undColor”
</webServices>
<scripting>
<webServices>
readAccessProperties=”Name,Address,Backgro
writeAccessProperties=”BackgroundColor”/>
</scripting> </system.web.extensions>
The profile service must also be configured. Although configuration of the profiling service is
outside of the scope of this whitepaper, it is worthwhile to note that groups as defined in profile
configuration settings will be accessible as sub-properties of the group name. For example, with
the following profile section specified:
<profile enabled="true">
<properties>
<add name="Name"
type="System.String"/>
<group name="Address">
<add
name="Line1" type="System.String"/>
<add name="Line2"
type="System.String"/>
<add name="City"
type="System.String"/>
<add name="State"
type="System.String"/>
<add name="Zip"
type="System.String"/>
</group>
<add
name="BackgroundColor" type="System.Drawing.Color"/>
</properties>
</profile>
Client script would be able to access Name, Address.Line1, Address.Line2, Address.City,
Address.State, Address.Zip, and BackgroundColor as properties of the properties field of the
ProfileService class.
Once the AJAX Profiling Service is configured, it will be immediately available in pages;
however, it will have to be loaded once before use.
Sys.Services.ProfileService members
properties field:
The properties field exposes all configured profile data as child properties that can be referenced
by the dot-operator-name convention. Properties that are children of property groups are referred
to as GroupName.PropertyName. In the example profile configuration presented above, to get
the state of the user, you could use the following identifier:
Sys.Services.ProfileService.properties.Address.State
load method:
Loads a selected list or all properties from the server.
Parameters:
Parameter Name
Meaning
propertyNames
Optional (defaults to null). The properties to be loaded from the
server.
loadCompletedCallback Optional (defaults to null). The function to call when loading has
completed.
failedCallback
Optional (defaults to null). The function to call if an error occurs.
userContext
Optional (defaults to null). Context information to be passed to the
callback function.
The load function does not have a return value. If the call completed successfully, it will call
either the loadCompletedCallback parameter or defaultLoadCompletedCallback property. If
the call failed, or the timeout expired, either the failedCallback parameter or
defaultFailedCallback property will be called.
If the propertyNames parameter is not supplied, all read-configured properties are retrieved
from the server.
save method:
The save() method saves the specified property list (or all properties) to the user’s ASP.NET
profile.
Parameters:
Parameter Name
Meaning
propertyNames
Optional (defaults to null). The properties to be saved to the server.
saveCompletedCallback Optional (defaults to null). The function to call when saving has
completed.
failedCallback
Optional (defaults to null). The function to call if an error occurs.
userContext
Optional (defaults to null). Context information to be passed to the
callback function.
The save function does not have a return value. If the call completes successfully, it will call
either the saveCompletedCallback parameter or defaultSaveCompletedCallback property. If
the call failed, or the timeout expired, either the failedCallback or defaultFailedCallback
property will be called.
If the propertyNames parameter is null, all profile properties will be sent to the server, and the
server will decide which properties can be saved and which cannot.
defaultFailedCallback property (get, set):
This property specifies a function that should be called if a failure to communicate with the web
service occurs. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function AuthenticationFailureCallback(error, userContext, methodName);
Parameters:
Parameter Name
Meaning
Error
Specifies the error information.
userContext
Specifies the user context information provided when the load or
save function was called.
methodName
The name of the calling method.
defaultSaveCompleted property (get, set):
This property specifies a function that should be called upon the completion of saving the user’s
profile data. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function ProfileSaveComplete(numPropsSaved, userContext, methodName);
Parameters:
Parameter Name
Meaning
numPropsSaved
Specifies the number of properties that were saved.
userContext
Specifies the user context information provided when the load or
save function was called.
methodName
The name of the calling method.
defaultLoadCompleted property (get, set):
This property specifies a function that should be called upon the completion of loading of the
user’s profile data. It should receive a delegate (or function reference).
The function reference specified by this property should have the following signature:
function ProfileLoadComplete(numPropsLoaded, userContext, methodName);
Parameters:
Parameter Name
Meaning
numPropsLoaded
Specifies the number of properties loaded.
userContext
Specifies the user context information provided when the load or
save function was called.
methodName
The name of the calling method.
path property (get, set):
This property programmatically determines the location of the profile web service. It can be used
to override the default profile service provider, as well as one set declaratively in the Path
property of the ScriptManager control’s ProfileService child node.
Note that the location of the default profile service does not change. However, ASP.NET AJAX
allows you to specify the location of a web service that provides the same class interface as the
ASP.NET AJAX authentication service proxy.
Note also that this property should not be set to a value that directs the script request off of the
current site. The technology underlying AJAX should not post cross-site requests, and may
generate a security exception in the client browser.
This property is a String object representing the path to the profile web service.
timeout property (get, set):
This property determines the length of time to wait for the profile service before assuming the
load or save request has failed. If the timeout expires while waiting for a call to complete, the
request-failed callback will be called, and the call will not complete.
This property is a Number object representing the number of milliseconds to wait for results from
the profile service.
Code sample: Loading profile data at page load
The following code will check to see whether a user is authenticated, and if so, will load the
user’s preferred background color as the page’s.
function Page_Load() {
if
(Sys.Services.AuthenticationService.get_isLoggedIn())
{
Sys.Se
rvices.ProfileService.load();
} } function ProfileLoaded(numPropsLoaded,
userContext, methodName)
{
document.documentElement.style.backgroundColor =
Sys.Services.ProfileService.properties.BackgroundColor; }
Using a Custom Authentication Service Provider
The ASP.NET AJAX Extensions allow you to create a custom script authentication service
provider by exposing your functionality through a custom web service. In order to be used, your
web service must expose two methods, Login and Logout; and these methods must be specified
with the same method signatures as the default ASP.NET AJAX Authentication web service.
Once you have created the custom web service, you will need to specify the path to it, either
declaratively on your page, programmatically in code, or via the client script.
To set the path declaratively:
To set the path declaratively, include the AuthenticationService child of the ScriptManager
object on your ASP.NET page:
<asp:ScriptManager ID="ScriptManager1"
runat="server">
<AuthenticationService Path="~/AuthService.asmx" />
</asp:ScriptManager>
To set the path in code:
To set the path programmatically, specify the path via the instance of your script manager:
protected void Page_Load(object sender, EventArgs e)
{
this.ScriptManager1.AuthenticationService.Path = "~/AuthService.asmx";
}
To set the path in script:
To set the path programmatically in script, utilize the path property of the AuthenticationService
class:
function Login() {
var userTextbox = $get("txtUser");
var
passTextbox =
$get("txtPassword");
Sys.Services.AuthenticationService.set_path("./Auth
Service.asmx");
Sys.Services.AuthenticationService.login(userTextbox.val
ue, passTextbox.value, false, null, null, LoginServiceCompleted,
LoginServiceFailed, "Context Info"); }
Sample Web Service for Custom Authentication
<%@ WebService Language="C#" Class="AuthService" %> using System; using
System.Web; using System.Web.Services; using System.Web.Services.Protocols;
using System.Web.Script.Services; [ScriptService] [WebService] public class
AuthService : WebService {
[WebMethod]
public bool Login(string
userName, string password, bool
createCookie)
{
Session["LoggedInUser"] =
userName;
return true;
}
[WebMethod]
public void
Logout()
{
Session.Abandon();
} }
Introduction
Microsoft’s ASP.NET technology brings an object-oriented and event-driven programming
model and unites it with the benefits of compiled code. However, its server-side processing
model has several drawbacks inherent in the technology, many of which can be addressed by the
new features included in the System.Web.Extensions namespace, which encapsulates the
Microsoft AJAX Services in the .NET Framework 3.5. These extensions enable many rich client
features, previously available as part of the ASP.NET 2.0 AJAX Extensions, but now part of the
Framework Base Class Library. Controls and features in this namespace include partial rendering
of pages without requiring a full page refresh, the ability to access Web Services via client script
(including the ASP.NET profiling API), and an extensive client-side API designed to mirror
many of the control schemes seen in the ASP.NET server-side control set.
This whitepaper examines the localization features present in the Microsoft AJAX Framework
and Microsoft AJAX Script Library, in the context of business need for localization support and
reviewing already-integrated support for localization in web applications provided by the .NET
Framework. The Microsoft AJAX Script Library utilizes the .resx file format already used by
.NET applications, which provides integrated IDE support and a shareable resource type.
This whitepaper is based on the Beta 2 release of Microsoft Visual Studio 2008. This whitepaper
also assumes that you will be working with Visual Studio 2008, not Visual Web Developer
Express, and will provide walkthroughs according to the user interface of Visual Studio. Some
code samples will utilize project templates that may be unavailable in Visual Web Developer
Express.
The Need for Localization
Particularly for enterprise application developers and component developers, the ability to create
tools that can be aware of the differences between cultures and languages has become
increasingly necessary. Designing components with the ability to adapt to the locale of the client
increases developer productivity and reduces the amount of work required for the adaptation of a
component to function globally.
Localization is the process of designing and integrating support for a specific language and
culture into an application or an application component. The Microsoft ASP.NET platform
provides extensive support for localization for standard ASP.NET applications by integrating the
standard .NET localization model; the Microsoft AJAX Framework utilize the integrated model
to support the diverse scenarios in which localization can be performed. With the Microsoft
AJAX Framework, scripts can either be localized by being deployed into satellite assemblies, or
by utilizing a static file system structure.
Embedding Scripts with Satellite Assemblies
Consistent with the standard .NET Framework localization strategy, resources can be included in
satellite assemblies. Satellite assemblies provide several advantages over traditional resource
inclusion in binaries – any given localization can be updated without updating the larger image,
additional localizations can be deployed simply by installing satellite assemblies into the project
folder, and satellite assemblies can be deployed without causing a reload of the main project
assembly. Particularly in ASP.NET projects, this is beneficial because it can significantly reduce
the amount of system resources used by incremental updates, and minimally disrupts production
website usage.
Scripts are embedded into assemblies by including them in managed .resx (or compiled
.resources) files, which are included into the assembly at compile-time. Their resources are then
made available to the script application through AJAX runtime-generated code, via assemblylevel attributes
Naming Conventions for Embedded Script Files
The Microsoft AJAX Framework script management supports a variety of options for use in
deployment and testing of scripts, and guidelines are provided to facilitate these options.
To facilitate debugging:
Release (production) scripts should not include the “.debug” qualifier in the filename. Scripts
designed for debugging should include “.debug” in the filename.
To facilitate localization:
Neutral-culture scripts should not include any culture identifier in the name of the file. For
scripts that contain localized resources, the ISO language code should be specified in the file
name. For example, es-CO stands for Spanish, Columbia.
The following table summarizes the file naming conventions with examples:
Filename
Meaning
Script.js
A release-version culture-neutral script.
Script.debug.js
A debug-version culture-neutral script.
Script.en-US.js
A release version English, United States script.
Script.debug.es-CO.js
A debug-version Spanish, Columbia script.
Walkthrough: Create an Localized, Embedded Script
Please note: this walkthrough requires the use of Visual Studio 2008 as Visual Web Developer
Express does not include a project template for class library projects.
1. Create a new Web Site project with ASP.NET AJAX Extensions integrated. Create another
project, a Class Library project, within the solution called “LocalizingResources.”
2. Add a Jscript file called “VerifyDeletion.js” to the LocalizingResources project, as well as .resx
resources files called “DeletionResources.resx” and “DeletionResources.es.resx.” The former will
contain culture-neutral resources; the latter will contain Spanish-language resources.
3. Add the following code to VerifyDeletion.js:
function VerifyDeletion(fileName) {
if
(confirm(Message.VerifyDelete.replace(/FILENAME/,
fileName)))
{
Delete(fileName);
return
true;
}
return false; } function Delete(fileName) {
(Message.Deleted.replace(/FILENAME/, fileName)); }
alert
For those unfamiliar with JavaScript Regex syntax, text within single forward slashes (in the
previous example, /FILENAME/ is an example) denotes a RegExp object. The MSDN Library
contains an extensive JavaScript reference, and resources on JavaScript native objects can be
found online.
4. Add the following resource strings to DeletionResources.resx:
VerifyDelete: Are you sure you want to delete FILENAME?
Deleted: FILENAME has been deleted.
5. Add the following resource strings to DeletionResources.es.resx:
VerifyDelete: ¿Está seguro que desee quitar FILENAME?
Deleted: FILENAME se ha quitado.
6. Add the following lines of code to the AssemblyInfo file:
[assembly:
System.Web.UI.WebResource("LocalizingResources.VerifyDeletion.js",
/javascript")] [assembly:
System.Web.UI.ScriptResource("LocalizingResources.VerifyDeletion.js",
ocalizingResources.DeletionResources", "Message")]
"text
"L
7. Add references to System.Web and System.Web.Extensions to the LocalizingResources project.
8. Add a reference to the LocalizingResources project from the Web Site project.
9. In default.aspx, under the Web Site project, update the ScriptManager control with the
following additional markup:
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnableScriptLocalization="true">
<Scripts>
<asp:ScriptReferenc
e Assembly="LocalizingResources"
Name="LocalizingResources.VerifyDeletion.js"/>
</Scripts>
</asp:ScriptManager>
10. In default.aspx, anywhere on the page, include this markup:
<asp:Button ID="btnDelete" runat="Server"
OnClientClick="VerifyDeletion('a.txt');" Text="Delete" />
11. Press F5. If prompted, enable debugging. When the page is loaded, press the Delete button.
Note that you are prompted in English (unless your computer is set to prefer Spanish-language
resources by default) for confirmation.
12. Close the browser window and return to default.aspx. In the @Page header directive, replace
“auto” for Culture and UICulture with “es-ES”. Press F5 again to launch the web application in
the browser again. This time, note that you are prompted to delete the file in Spanish:
(Click to view full-size image)
(Click to view full-size image)
Note that there are several variations for this walkthrough. For instance, scripts could be
registered with the ScriptManager control programmatically during page load.
Including a Static Script File Structure
When using static script files for deployment, you lose some of the benefits of using the inherent
.NET localization scheme. Primarily visible is that you lose the automatic type generated from
including script resource files; in the above walkthrough, for example, resources were exposed
by an automatically-generated type called “Message” from the ScriptManager control.
There are, however, some benefits to using a static script file structure. Updates can be
performed without recompiling and redeploying satellite assemblies, and the use of a static file
structure can also be done to override embedded script, to integrate a minor piece of functionality
that may not have been shipped with a component.
Microsoft recommends avoiding a version control issue by automatically generating your script
resources during project compilation. When maintaining an extensive script code base, it can
become increasingly difficult to ensure that code changes are reflected in each localized script.
As an alternative, you can simply maintain one logic script and multiple localization scripts,
merging the files while building the project.
Because there are not resources to declaratively include, static script files should be referenced
either by adding <asp:ScriptElement> elements as a child of the <Scripts> tag of the
ScriptManager control, or by programmatically adding ScriptReference objects to the
Scripts property of the ScriptManager control on the page at runtime.
The ScriptManager and its Role in Localization
The ScriptManager enables several automatic behaviors for localized applications:





It automatically locates script files based on settings and naming conventions; for instance, it
loads debug-enabled scripts when in debugging mode, and loads localized scripts based on the
browser’s user interface selection.
It enables the definition of cultures, including custom cultures.
It enables the compression of script files over HTTP.
It caches scripts to efficiently manage many requests.
It adds a layer of indirection to scripts by piping them through an encrypted URL.
Script references can be added to the ScriptManager control either programmatically or by
declarative markup. Declarative markup is particularly useful when working with scripts
embedded in assemblies other than the web site project itself, as the name of the script will likely
not change as revisions are pushed through.
Calling Web Services with ASP.NET AJAX
Dan Wahlin
Web Services are an integral part of the .NET framework that provide a cross-platform solution
for exchanging data between distributed systems. Although Web Services are normally used to
allow different operating systems, object models and programming languages to send and
receive data, they can also be used to dynamically inject data into an ASP.NET AJAX page or
send data from a page to a back-end system. All of this can be done without resorting to postback
operations.
While the ASP.NET AJAX UpdatePanel control provides a simple way to AJAX enable any
ASP.NET page, there may be times when you need to dynamically access data on the server
without using an UpdatePanel. In this article you'll see how to accomplish this by creating and
consuming Web Services within ASP.NET AJAX pages.
This article will focus on functionality available in the core ASP.NET AJAX Extensions as well
as a Web Service enabled control in the ASP.NET AJAX Toolkit called the
AutoCompleteExtender. Topics covered include defining AJAX-enabled Web Services, creating
client proxies and calling Web Services with JavaScript. You'll also see how Web Service calls
can be made directly to ASP.NET page methods.
Web Services Configuration
When a new Web Site project is created with Visual Studio 2008 Beta 2, the web.config file has
a number of new additions that may be unfamiliar to users of previous versions of Visual Studio.
Some of these modifications map the "asp" prefix to ASP.NET AJAX controls so they can be
used in pages while others define required HttpHandlers and HttpModules. Listing 1 shows
modifications made to the <httpHandlers> element in web.config that affects Web Service
calls. The default HttpHandler used to process .asmx calls is removed and replaced with a
ScriptHandlerFactory class located in the System.Web.Extensions.dll assembly.
System.Web.Extensions.dll contains all of the core functionality used by ASP.NET AJAX.
Listing 1. ASP.NET AJAX Web Service Handler Configuration
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*"
path="*.asmx"
validate="false"
type="System.Web.Script.Services.ScriptHandlerFact
ory,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35"/> </httpHandlers>
This HttpHandler replacement is made in order to allow JavaScript Object Notation (JSON) calls
to be made from ASP.NET AJAX pages to .NET Web Services using a JavaScript Web Service
proxy. ASP.NET AJAX sends JSON messages to Web Services as opposed to the standard
Simple Object Access Protocol (SOAP) calls typically associated with Web Services. This
results in smaller request and response messages overall. It also allows for more efficient clientside processing of data since the ASP.NET AJAX JavaScript library is optimized to work with
JSON objects. Listing 2 and Listing 3 show examples of Web Service request and response
messages serialized to JSON format. The request message shown in Listing 2 passes a country
parameter with a value of "Belgium" while the response message in Listing 3 passes an array of
Customer objects and their associated properties.
Listing 2. Web Service Request Message Serialized to JSON
{"country":"Belgium"}
Note: the operation name is defined as part of the URL to the web service; additionally, request
messages are not always submitted via JSON. Web Services can utilize the ScriptMethod
attribute with the UseHttpGet parameter set to true, which causes parameters to be passed via a
the query string parameters.
Listing 3. Web Service Response Message Serialized to JSON
[{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Maison
De
wey","CustomerID":"MAISD","ContactName":"Catherine
Dewey"},{"__type":"Mo
del.Customer","Country":"Belgium","CompanyName":"Suprêmes
délices","Cust
omerID":"SUPRD","ContactName":"Pascale
Cartrain"}]
In the next section you'll see how to create Web Services capable of handling JSON request
messages and responding with both simple and complex types.
Creating AJAX-Enabled Web Services
The ASP.NET AJAX framework provides several different ways to call Web Services. You can
use the AutoCompleteExtender control (available in the ASP.NET AJAX Toolkit) or JavaScript.
However, before calling a service you have to AJAX-enable it so that it can be called by clientscript code.
Whether or not you're new to ASP.NET Web Services, you'll find it straightforward to create and
AJAX-enable services. The .NET framework has supported the creation of ASP.NET Web
Services since its initial release in 2002 and the ASP.NET AJAX Extensions provide additional
AJAX functionality that builds upon the .NET framework's default set of features. Visual Studio
.NET 2008 Beta 2 has built-in support for creating .asmx Web Service files and automatically
derives associated code beside classes from the System.Web.Services.WebService class. As you
add methods into the class you must apply the WebMethod attribute in order for them to be
called by Web Service consumers.
Listing 4 shows an example of applying the WebMethod attribute to a method named
GetCustomersByCountry().
Listing 4. Using the WebMethod Attribute in a Web Service
[WebMethod] public Customer[] GetCustomersByCountry(string country)
{
return Biz.BAL.GetCustomersByCountry(country); }
The GetCustomersByCountry() method accepts a country parameter and returns a Customer
object array. The country value passed into the method is forwarded to a business layer class
which in turn calls a data layer class to retrieve the data from the database, fill the Customer
object properties with data and return the array.
Using the ScriptService Attribute
While adding the WebMethod attribute allows the GetCustomersByCountry() method to be
called by clients that send standard SOAP messages to the Web Service, it doesn't allow JSON
calls to be made from ASP.NET AJAX applications out of the box. To allow JSON calls to be
made you have to apply the ASP.NET AJAX Extension's ScriptService attribute to the Web
Service class. This enables a Web Service to send response messages formatted using JSON and
allows client-side script to call a service by sending JSON messages.
Listing 5 shows an example of applying the ScriptService attribute to a Web Service class named
CustomersService.
Listing 5. Using the ScriptService Attribute to AJAX-enable a Web Service
[System.Web.Script.Services.ScriptService] [WebService(Namespace =
"http://xmlforasp.net")] [WebServiceBinding(ConformsTo =
WsiProfiles.BasicProfile1_1)] public class CustomersService :
System.Web.Services.WebService {
[WebMethod]
GetCustomersByCountry(string country)
{
Biz.BAL.GetCustomersByCountry(country);
} }
public Customer[]
return
The ScriptService attribute acts as a marker that indicates it can be called from AJAX script
code. It doesn't actually handle any of the JSON serialization or deserialization tasks that occur
behind the scenes. The ScriptHandlerFactory (configured in web.config) and other related
classes do the bulk of JSON processing.
Using the ScriptMethod Attribute
The ScriptService attribute is the only ASP.NET AJAX attribute that has to be defined in a .NET
Web Service in order for it to be used by ASP.NET AJAX pages. However, another attribute
named ScriptMethod can also be applied directly to Web Methods in a service. ScriptMethod
defines three properties including UseHttpGet, ResponseFormat and XmlSerializeString.
Changing the values of these properties can be useful in cases where the type of request accepted
by a Web Method needs to be changed to GET, when a Web Method needs to return raw XML
data in the form of an XmlDocument or XmlElement object or when data returned from a service
should always be serialized as XML instead of JSON.
The UseHttpGet property can be used when a Web Method should accept GET requests as
opposed to POST requests. Requests are sent using a URL with Web Method input parameters
converted to QueryString parameters. The UseHttpGet property defaults to false and should only
be set to true when operations are known to be safe and when sensitive data is not passed to a
Web Service. Listing 6 shows an example of using the ScriptMethod attribute with the
UseHttpGet property.
Listing 6. Using the ScriptMethod attribute with the UseHttpGet property.
[WebMethod] [ScriptMethod(UseHttpGet = true)] public string
HttpGetEcho(string input) {
return input; }
An example of the headers sent when the HttpGetEcho Web Method shown in Listing 6 is called
are shown next:
GET /CustomerViewer/DemoService.asmx/HttpGetEcho?input=%22Input Value%22
HTTP/1.1
In addition to allowing Web Methods to accept HTTP GET requests, the ScriptMethod attribute
can also be used when XML responses need to be returned from a service rather than JSON. For
example, a Web Service may retrieve an RSS feed from a remote site and return it as an
XmlDocument or XmlElement object. Processing of the XML data can then occur on the client.
Listing 7 shows an example of using the ResponseFormat property to specify that XML data
should be returned from a Web Method.
Listing 7. Using the ScriptMethod attribute with the ResponseFormat property.
[WebMethod] [ScriptMethod(ResponseFormat = ResponseFormat.Xml)] public
XmlElement GetRssFeed(string url) {
XmlDocument doc = new
XmlDocument();
doc.Load(url);
return doc.DocumentElement; }
The ResponseFormat property can also be used along with the XmlSerializeString property. The
XmlSerializeString property has a default value of false which means that all return types except
strings returned from a Web Method are serialized as XML when the ResponseFormat property
is set to ResponseFormat.Xml. When XmlSerializeString is set to true, all types returned
from a Web Method are serialized as XML including string types. If the ResponseFormat
property has a value of ResponseFormat.Json the XmlSerializeString property is ignored.
Listing 8 shows an example of using the XmlSerializeString property to force strings to be
serialized as XML.
Listing 8. Using the ScriptMethod attribute with the XmlSerializeString property
[WebMethod] [ScriptMethod(ResponseFormat =
ResponseFormat.Xml,XmlSerializeString=true)] public string
GetXmlString(string input) {
return input; }
The value returned from calling the GetXmlString Web Method shown in Listing 8 is shown
next:
<?xml version="1.0"?>
<string>Test</string>
Although the default JSON format minimizes the overall size of request and response messages
and is more readily consumed by ASP.NET AJAX clients in a cross-browser manner, the
ResponseFormat and XmlSerializeString properties can be utilized when client applications such
as Internet Explorer 5 or higher expect XML data to be returned from a Web Method.
Working with Complex Types
Listing 5 showed an example of returning a complex type named Customer from a Web Service.
The Customer class defines several different simple types internally as properties such as
FirstName and LastName. Complex types used as an input parameter or return type on an AJAXenabled Web Method are automatically serialized into JSON before being sent to the client-side.
However, nested complex types (those defined internally within another type) are not made
available to the client as standalone objects by default.
In cases where a nested complex type used by a Web Service must also be used in a client page,
the ASP.NET AJAX GenerateScriptType attribute can be added to the Web Service. For
example, the CustomerDetails class shown in Listing 9 contains Address and Gender properties
which represent nested complex types.
Listing 9. The CustomerDetails class shown here contains two nested complex types.
public class CustomerDetails : Customer {
public
CustomerDetails()
{
}
Address _Address;
Gender.Unknown;
public Address Address
{
_Address; }
set { _Address = value; }
}
Gender
{
get { return _Gender; }
value; }
} }
Gender _Gender =
get { return
public Gender
set { _Gender =
The Address and Gender objects defined within the CustomerDetails class shown in Listing 9
won't automatically be made available for use on the client-side via JavaScript since they are
nested types (Address is a class and Gender is an enumeration). In situations where a nested type
used within a Web Service must be available on the client-side, the GenerateScriptType attribute
mentioned earlier can be used (see Listing 10). This attribute can be added multiple times in
cases where different nested complex types are returned from a service. It can be applied directly
to the Web Service class or above specific Web Methods.
Listing 10. Using the GenerateScriptService attribute to define nested types that should be
available to the client.
[System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.GenerateScriptType(typeof(Address))]
[System.Web.Script.Services.GenerateScriptType(typeof(Gender))]
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo
= WsiProfiles.BasicProfile1_1)] public class NestedComplexTypeService :
System.Web.Services.WebService {
//Web Methods }
By applying the GenerateScriptType attribute to the Web Service, the Address and Gender
types will automatically be made available for use by client-side ASP.NET AJAX JavaScript
code. An example of the JavaScript that is automatically generated and sent to the client by
adding the GenerateScriptType attribute on a Web Service is shown in Listing 11. You’ll see
how to use nested complex types later in the article.
Listing 11. Nested complex types made available to an ASP.NET AJAX page.
if (typeof(Model.Address) === 'undefined')
{
Model.Address=gtc("Model.Address");
Model.Address.registerClass('
Model.Address'); } Model.Gender = function() { throw
Error.invalidOperation(); } Model.Gender.prototype = {Unknown: 0,Male:
1,Female: 2} Model.Gender.registerEnum('Model.Gender', true);
Now that you've seen how to create Web Services and make them accessible to ASP.NET AJAX
pages, let's take a look at how to create and use JavaScript proxies so that data can be retrieved or
sent to Web Services.
Creating JavaScript Proxies
Calling a standard Web Service (.NET or another platform) typically involves creating a proxy
object that shields you from the complexities of sending SOAP request and response messages.
With ASP.NET AJAX Web Service calls, JavaScript proxies can be created and used to easily
call services without worrying about serializing and deserializing JSON messages. JavaScript
proxies can be automatically generated by using the ASP.NET AJAX ScriptManager control.
Creating a JavaScript proxy that can call Web Services is accomplished by using the
ScriptManager’s Services property. This property allows you to define one or more services that
an ASP.NET AJAX page can call asynchronously to send or receive data without requiring
postback operations. You define a service by using the ASP.NET AJAX ServiceReference
control and assigning the Web Service URL to the control’s Path property. Listing 12 shows an
example of referencing a service named CustomersService.asmx.
<asp:ScriptManager ID="ScriptManager1"
runat="server">
<Services>
<asp:ServiceReference
Path="~/CustomersService.asmx" />
</Services> </asp:ScriptManager>
Listing 12. Defining a Web Service used in an ASP.NET AJAX page.
Adding a reference to the CustomersService.asmx through the ScriptManager control causes a
JavaScript proxy to be dynamically generated and referenced by the page. The proxy is
embedded by using the <script> tag and dynamically loaded by calling the
CustomersService.asmx file and appending /js to the end of it. The following example shows
how the JavaScript proxy is embedded in the page when debugging is disabled in web.config:
<script src="CustomersService.asmx/js" type="text/javascript"></script>
Note: If you’d like to see the actual JavaScript proxy code that is generated you can type the
URL to the desired .NET Web Service into Internet Explorer’s address box and append /js to the
end of it.
If debugging is enabled in web.config a debug version of the JavaScript proxy will be embedded
in the page as shown next:
<script src="CustomersService.asmx/jsdebug" type="text/javascript"></script>
The JavaScript proxy created by the ScriptManager can also be embedded directly into the page
rather than referenced using the <script> tag’s src attribute. This can be done by setting the
ServiceReference control’s InlineScript property to true (the default is false). This can be useful
when a proxy isn’t shared across multiple pages and when you’d like to reduce the number of
network calls made to the server. When InlineScript is set to true the proxy script won’t be
cached by the browser so the default value of false is recommended in cases where the proxy is
used by multiple pages in an ASP.NET AJAX application. An example of using the InlineScript
property is shown next:
<asp:ServiceReference InlineScript="true" Path="~/CustomersService.asmx"/>
Using JavaScript Proxies
Once a Web Service is referenced by an ASP.NET AJAX page using the ScriptManager control,
a call can be made to the Web Service and the returned data can be handled using callback
functions. A Web Service is called by referencing its namespace (if one exists), class name and
Web Method name. Any parameters passed to the Web Service can be defined along with a
callback function that handles the returned data.
An example of using a JavaScript proxy to call a Web Method named GetCustomersByCountry()
is shown in Listing 13. The GetCustomersByCountry() function is called when an end user clicks
a button on the page.
Listing 13. Calling a Web Service with a JavaScript proxy.
function GetCustomerByCountry() {
var country =
$get("txtCountry").value;
InterfaceTraining.CustomersService.GetCustomer
sByCountry(country, OnWSRequestComplete); } function
OnWSRequestComplete(results) {
if (results !=
null)
{
CreateCustomersTable(results);
GetMap(result
s);
} }
This call references the InterfaceTraining namespace, CustomersService class and
GetCustomersByCountry Web Method defined in the service. It passes a country value obtained
from a textbox as well as a callback function named OnWSRequestComplete that should be
invoked when the asynchronous Web Service call returns. OnWSRequestComplete handles the
array of Customer objects returned from the service and converts them into a table that is
displayed in the page. The output generated from the call is shown in Figure 1.
Figure 1: Binding data obtained by making an asynchronous AJAX call to a Web Service. (Click
to view full-size image)
JavaScript proxies can also make one-way calls to Web Services in cases where a Web Method
should be called but the proxy shouldn’t wait for a response. For example, you may want to call
a Web Service to start a process such as a work-flow but not wait for a return value from the
service. In cases where a one-way call needs to be made to a service, the callback function
shown in Listing 13 can simply be omitted. Since no callback function is defined the proxy
object will not wait for the Web Service to return data.
Handling Errors
Asynchronous callbacks to Web Services can encounter different types of errors such as the
network being down, the Web Service being unavailable or an exception being returned.
Fortunately, JavaScript proxy objects generated by the ScriptManager allow multiple callbacks
to be defined to handle errors and failures in addition to the success callback shown earlier. An
error callback function can be defined immediately after the standard callback function in the call
to the Web Method as shown in Listing 14.
Listing 14. Defining an error callback function and displaying errors.
function GetCustomersByCountry() {
var country =
$get("txtCountry").value;
InterfaceTraining.CustomersService.GetCustomer
sByCountry(country,
OnWSRequestComplete, OnWSRequestFailed); }
function OnWSRequestFailed(error) {
alert("Stack Trace: " +
error.get_stackTrace() + "/r/n" +
"Error: " + error.get_message() +
"/r/n" +
"Status Code: " + error.get_statusCode() + "/r/n"
+
"Exception Type: " + error.get_exceptionType() + "/r/n"
+
"Timed Out: " + error.get_timedOut()); }
Any errors that occur when the Web Service is called will trigger the OnWSRequestFailed()
callback function to be called which accepts an object representing the error as a parameter. The
error object exposes several different functions to determine the cause of the error as well as
whether or not the call timed out. Listing 14 shows an example of using the different error
functions and Figure 2 shows an example of the output generated by the functions.
Figure 2: Output generated by calling ASP.NET AJAX error functions. (Click to view full-size
image)
Handling XML Data Returned from a Web Service
Earlier you saw how a Web Method could return raw XML data by using the ScriptMethod
attribute along with its ResponseFormat property. When ResponseFormat is set to
ResponseFormat.Xml, data returned from the Web Service is serialized as XML rather than
JSON. This can be useful when XML data needs to be passed directly to the client for processing
using JavaScript or XSLT. At the current time, Internet Explorer 5 or higher provides the best
client-side object model for parsing and filtering XML data due to its built-in support for
MSXML.
Retrieving XML data from a Web Service is no different than retrieving other data types. Start
by invoking the JavaScript proxy to call the appropriate function and define a callback function.
Once the call returns you can then process the data in the callback function.
Listing 15 shows an example of calling a Web Method named GetRssFeed() that returns an
XmlElement object. GetRssFeed() accepts a single parameter representing the URL for the RSS
feed to retrieve.
Listing 15. Working with XML data returned from a Web Service.
function GetRss()
{
InterfaceTraining.DemoService.GetRssFeed(
"http://blogs.inte
rfacett.com/dan-wahlins-blog/rss.xml",
OnWSRequestComplete); }
function OnWSRequestComplete(result) {
if (document.all) //Filter for IE
DOM since other browsers are limited
{
var items =
result.selectNodes("//item");
for (var
i=0;i<items.length;i++)
{
var title =
items[i].selectSingleNode("title").text;
var href =
items[i].selectSingleNode("link").text;
$get("divOutput").inne
rHTML +=
"<a href='" + href + "'>" + title +
"</a><br/>";
}
}
else
{
$get("divOutput").
innerHTML = "RSS only available in IE5+";
} }
This example passes a URL to an RSS feed and processes the returned XML data in the
OnWSRequestComplete() function. OnWSRequestComplete() first checks to see if the browser
is Internet Explorer to know whether or not the MSXML parser is available. If it is, an XPath
statement is used to locate all <item> tags within the RSS feed. Each item is then iterated
through and the associated <title> and <link> tags are located and processed to display each
item’s data. Figure 3 shows an example of the output generated from making an ASP.NET
AJAX call through a JavaScript proxy to the GetRssFeed() Web Method.
Handling Complex Types
Complex types accepted or returned by a Web Service are automatically exposed through a
JavaScript proxy. However, nested complex types are not directly accessible on the client-side
unless the GenerateScriptType attribute is applied to the service as discussed earlier. Why would
you want to use a nested complex type on the client-side?
To answer this question, assume that an ASP.NET AJAX page displays customer data and
allows end users to update a customer’s address. If the Web Service specifies that the Address
type (a complex type defined within a CustomerDetails class) can be sent to the client then the
update process can be divided into separate functions for better code re-use.
Figure 3: Output creating from calling a Web Service that returns RSS data. (Click to view fullsize image)
Listing 16 shows an example of client-side code that invokes an Address object defined in a
Model namespace, fills it with updated data and assigns it to a CustomerDetails object’s Address
property. The CustomerDetails object is then passed to the Web Service for processing.
Listing 16. Using nested complex types
function UpdateAddress() {
var cust = new
Model.CustomerDetails();
cust.CustomerID =
$get("hidCustomerID").value;
cust.Address =
CreateAddress();
InterfaceTraining.DemoService.UpdateAddress(cust,OnWSUp
dateComplete); } function CreateAddress() {
var addr = new
Model.Address();
addr.Street = $get("txtStreet").value;
addr.City =
$get("txtCity").value;
addr.State = $get("txtState").value;
return
addr; } function OnWSUpdateComplete(result) {
alert("Update " +
((result)?"succeeded":"failed")+ "!"); }
Creating and Using Page Methods
Web Services provide an excellent way to expose re-useable services to a variety of clients
including ASP.NET AJAX pages. However, there may be cases where a page needs to retrieve
data that won’t ever be used or shared by other pages. In this case, making an .asmx file to allow
the page to access the data may seem like overkill since the service is only used by a single page.
ASP.NET AJAX provides another mechanism for making Web Service-like calls without
creating standalone .asmx files. This is done by using a technique referred to as “page methods”.
Page methods are static (shared in VB.NET) methods embedded directly in a page or codebeside file that have the WebMethod attribute applied to them. By applying the WebMethod
attribute they can be called using a special JavaScript object named PageMethods that gets
dynamically created at runtime. The PageMethods object acts as a proxy that shields you from
the JSON serialization/deserialization process. Note that in order to use the PageMethods object
you must set the ScriptManager's EnablePageMethods property to true.
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePageMethods="true"> </asp:ScriptManager>
Listing 17 shows an example of defining two page methods in an ASP.NET code-beside class.
These methods retrieve data from a business layer class located in the App_Code folder of the
Website.
Listing 17. Defining page methods.
[WebMethod] public static Customer[] GetCustomersByCountry(string country)
{
return Biz.BAL.GetCustomersByCountry(country); } [WebMethod] public
static Customer[] GetCustomersByID(string id) {
return
Biz.BAL.GetCustomersByID(id); }
When ScriptManager detects the presence of Web Methods in the page it generates a dynamic
reference to the PageMethods object mentioned earlier. Calling a Web Method is accomplished
by referencing the PageMethods class followed by the name of the method and any necessary
parameter data that should be passed. Listing 18 shows examples of calling the two page
methods shown earlier.
Listing 18. Calling page methods with the PageMethods JavaScript object.
function GetCustomerByCountry() {
var country =
$get("txtCountry").value;
PageMethods.GetCustomersByCountry(country,
OnWSRequestComplete); } function GetCustomerByID() {
var custID =
$get("txtCustomerID").value;
PageMethods.GetCustomersByID(custID,
OnWSRequestComplete); } function OnWSRequestComplete(results) {
var
searchResults =
$get("searchResults");
searchResults.control.set_data(results);
if
(results != null) GetMap(results[0].Country,results); }
Using the PageMethods object is very similar to using a JavaScript proxy object. You first
specify all of the parameter data that should be passed to the page method and then define the
callback function that should be called when the asynchronous call returns. A failure callback
can also be specified (refer to Listing 14 for an example of handling failures).
The AutoCompleteExtender and the ASP.NET AJAX
Toolkit
The ASP.NET AJAX Toolkit (available from http://ajax.asp.net) offers several controls that can
be used to access Web Services. Specifically, the toolkit contains a useful control named
AutoCompleteExtender that can be used to call Web Services and show data in pages without
writing any JavaScript code at all.
The AutoCompleteExtender control can be used to extend existing functionality of a textbox and
help users more easily locate data they’re looking for. As they type into a textbox the control can
be used to query a Web Service and shows results below the textbox dynamically. Figure 4
shows an example of using the AutoCompleteExtender control to display customer ids for a
support application. As the user types different characters into the textbox, different items will be
shown below it based upon their input. Users can then select the desired customer id.
Using the AutoCompleteExtender within an ASP.NET AJAX page requires that the
AjaxControlToolkit.dll assembly be added to the Website’s bin folder. Once the toolkit assembly
has been added, you’ll want to reference it in web.config so that the controls it contains are
available to all pages in an application. This can be done by adding the following tag within
web.config’s <controls> tag:
<add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit"
tagPrefix="ajaxToolkit"/>
In cases where you only need to use the control in a specific page you can reference it by adding
the Reference directive to the top of a page as shown next rather than updating web.config:
<%@ Register Assembly="AjaxControlToolkit"
Namespace="AjaxControlToolkit"
TagPrefix="ajaxToolkit" %>
Figure 4: Using the AutoCompleteExtender control. (Click to view full-size image)
Once the Website has been configured to use the ASP.NET AJAX Toolkit, an
AutoCompleteExtender control can be added into the page much like you’d add a regular
ASP.NET server control. Listing 19 shows an example of using the control to call a Web
Service.
Listing 19. Using the ASP.NET AJAX Toolkit AutoCompleteExtender control.
<ajaxToolkit:AutoCompleteExtender ID="extTxtCustomerID"
runat="server"
MinimumPrefixLength="1"
ServiceMethod="GetCustomerIDs"
ServicePath="~/CustomersService.asmx"
TargetControlID="txtCustomerID" />
The AutoCompleteExtender has several different properties including the standard ID and runat
properties found on server controls. In addition to these, it allows you to define how many
characters an end user types before the Web Service is queried for data. The
MinimumPrefixLength property shown in Listing 19 causes the service to be called each time a
character is typed into the textbox. You'll want to be careful setting this value since each time the
user types a character the Web Service will be called to search for values that match the
characters in the textbox. The Web Service to call as well as the target Web Method are defined
using the ServicePath and ServiceMethod properties respectively. Finally, the TargetControlID
property identifies which textbox to hook the AutoCompleteExtender control to.
The Web Service being called must have the ScriptService attribute applied as discussed earlier
and the target Web Method must accept two parameters named prefixText and count. The
prefixText parameter represents the characters typed by the end user and the count parameter
represents how many items to return (the default is 10). Listing 20 shows an example of the
GetCustomerIDs Web Method called by the AutoCompleteExtender control shown earlier in
Listing 19. The Web Method calls a business layer method that in turn calls a data layer method
that handles filtering the data and returning the matching results. The code for the data layer
method is shown in Listing 21.
Listing 20. Filtering data sent from the AutoCompleteExtender control.
[WebMethod] public string[] GetCustomerIDs(string prefixText, int count)
{
return Biz.BAL.GetCustomerIDs(prefixText, count); }
Listing 21. Filtering results based upon end user input.
public static string[] GetCustomerIDs(string prefixText, int count)
{
//Customer IDs cached in _CustomerIDs field to improve
performance
if (_CustomerIDs == null)
{
List<string> ids
= new List<string>();
//SQL text used for simplicity...recommend
using sprocs
string sql = "SELECT CustomerID FROM
Customers";
DbConnection conn =
GetDBConnection();
conn.Open();
DbCommand cmd =
conn.CreateCommand();
cmd.CommandText = sql;
DbDataReader
reader = cmd.ExecuteReader();
while
(reader.Read())
{
ids.Add(reader["CustomerID"].ToStr
ing());
}
reader.Close();
conn.Close();
_CustomerIDs = ids.ToArray();
}
int index =
Array.BinarySearch(_CustomerIDs, prefixText, new
CaseInsensitiveComparer());
//~ is bitwise complement (reverse each
bit)
if (index < 0) index = ~index;
int matchingCount;
for
(matchingCount = 0; matchingCount < count && index + matchingCount <
_CustomerIDs.Length; matchingCount++)
{
if
(!_CustomerIDs[index + matchingCount].StartsWith(prefixText,
StringComparison.CurrentCultureIgnoreCase))
{
break;
}
}
String[] returnValue = new
string[matchingCount];
if (matchingCount >
0)
{
Array.Copy(_CustomerIDs, index, returnValue, 0,
matchingCount);
}
return returnValue; }