ASP.NET AJAX-Enabled Web Sites


Now that you have seen the component parts of ASP.NET AJAX, it is time to start looking at how to use them to enhance your Web sites. Before continuing with this section, you will need to install the ASP.NET AJAX framework if you intend to experiment for yourself. You will find the three downloads for this at http://ajax.asp.net. You should install the ASP.NET AJAX 1.0 package first, which comes in the form of an .msi installer. Next, install the Futures CTP (also an .msi installer). Finally, download and unzip the full (with source) version of the AJAX Control Toolkit, and install the wizards for creating your own extender classes by using the AjaxControlExtender.vsi installer.

In this section of the chapter, you will see how ASP.NET AJAX-enabled Web applications work, and how to use the various aspects of functionality that the package includes. You will start by examining and dissecting a simple application, and then add additional functionality in subsequent sections.

AJAX-Enabled Web Site Example

In this section, to get up and running, you’ll see how to use the new Web site template that is installed with ASP.NET AJAX. This template appears in the New Web Site dialog, as shown in Figure 34-2.

image from book
Figure 34-2

The ASP.NET AJAX-Enabled Web Site template includes all the ASP.NET AJAX core functionality. You can also use the ASP.NET AJAX CTP-Enabled Web Site template to include the futures functionality or the AJAX Control Toolkit Web Site template to include controls from the AJAX Control Toolkit. For the purposes of this example, you can create a new Web site that uses the first of these Web sites in the C:\ProCSharp\Chapter34, called PCSAjaxWebApp1.

Modify the code in Default.aspx as follows:

 <%@ 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>Pro C# ASP.NET AJAX Sample</title> </head> <body>   <form  runat="server">     <asp:ScriptManager  runat="server" />     <div>       <h1>Pro C# ASP.NET AJAX Sample</h1>       This sample obtains a list of primes up to a maximum value.       <br />       Maximum:       <asp:TextBox runat="server"  Text="2500" />       <br />       Result:       <asp:UpdatePanel runat="server" >         <ContentTemplate>           <asp:Button runat="server"  Text="Calculate " />           <br />           <asp:Label runat="server"  />           <br />           <small>Panel render time: <% =DateTime.Now.ToLongTimeString() %></small>         </ContentTemplate>       </asp:UpdatePanel>       <asp:UpdateProgress runat="server" >         <ProgressTemplate>          <div style="position: absolute; left: 100px; top: 200px;              padding: 40px 60px 40px 60px; background-color: lightyellow;              border: black 1px solid; font-weight: bold; font-size: larger;              filter: alpha(opacity=80);">Updating...</div>         </ProgressTemplate>       </asp:UpdateProgress>       <small>Page render time: <% =DateTime.Now.ToLongTimeString() %></small>     </div>   </form> </body> </html>

Switch to design view (note that the ASP.NET AJAX controls such as UpdatePanel and UpdateProgress have visual designer components) and double-click the Go button to add an event handler. Modify the code as follows:

 protected void GoButton_Click(object sender, EventArgs e) {    int maxValue = 0;    StringBuilder resultText = new StringBuilder();    if (int.TryParse(MaxValue.Text, out maxValue))    {       for (int trial = 2; trial <= maxValue; trial++)       {          bool isPrime = true;          for (int divisor = 2; divisor <= Math.Sqrt(trial); divisor++)          {             if (trial % divisor == 0)             {                isPrime = false;                break;             }          }          if (isPrime)          {             resultText.AppendFormat("{0} ", trial);          }       }    }    else    {       resultText.Append("Unable to parse maximum value.");    }    ResultLabel.Text = resultText.ToString(); }

Save your modifications and press F5 to run the project. If prompted, enable debugging in Web.config.

When the Web page appears as shown in Figure 34-3, note that the two render times shown are the same.

image from book
Figure 34-3

Click the Calculate button to display prime numbers less than or equal to 2500. Unless you are running on a slow machine, this should be almost instantaneous. Note that the render times are now different - only the one in the UpdatePanel has changed. This is shown in Figure 34-4.

image from book
Figure 34-4

Finally, add some zeroes to the maximum value to introduce a processing delay (about three more should be enough on a fast PC) and click the Calculate button again. This time, before the result is displayed, note that the UpdateProgress control displays a partially transparent feedback message, as shown in Figure 34-5.

image from book
Figure 34-5

While the application updates, the page remains responsive. You can, for example, scroll through the page.

Tip 

Note that when the update completes, the scroll position of the browser is set to the point it was at before you clicked Calculate. In most cases, when partial-page updates are quick to execute, this is great for usability.

Close the browser to return to Visual Studio.

ASP.NET AJAX-Enabled Web Site Configuration

Now that you have looked at a simple ASP.NET AJAX-enabled Web application, you can examine it to see how it works. The first thing to look at is the Web.config file for the application, in particular the following two blocks of code in the <system.web> configuration section of <configuration>:

 <?xml version="1.0"?> <configuration>   ...   <system.web>     <pages>       <controls>         <add tagPrefix="asp" namespace="System.Web.UI"           assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,                     PublicKeyToken=31bf3856ad364e35"/>       </controls>     </pages>     ...     <compilation debug="true">       <assemblies>         <add assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,                        PublicKeyToken=31bf3856ad364e35"/>       </assemblies>     </compilation>     ...   </system.web>   ... </configuration>

The code in the <assemblies> configuration section in <compilation> ensures that the ASP.NET AJAX System.Web.Extensions.dll assembly is loaded from the GAC. The code in the <controls> configuration element in <pages> references this assembly and associates the controls it contains with the tag prefix asp. These two sections are essential for all ASP.NET AJAX-enabled Web applications.

The next two sections, <httpHandlers> and <httpModules>, are also required for ASP.NET AJAX functionality. The <httpHandlers> section defines three things. First, the handler for .asmx Web services is replaced with a new class from the System.Web.Extensions namespace. This new class is capable of handling requests from client-side calls from the AJAX Library, including JSON serialization and deserialization. Second, a handler is added to enable the use of ASP.NET application services. Third, a new handler is added for the ScriptResource.axd resource. This resource serves the AJAX Library JavaScript files from the ASP.NET AJAX assembly, so that these files do not need to be included directly in your applications.

 <system.web>   ...   <httpHandlers>     <remove verb="*" path="*.asmx"/>     <add verb="*" path="*.asmx" validate="false"       type="System.Web.Script.Services.ScriptHandlerFactory,             System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,             PublicKeyToken=31bf3856ad364e35"/>     <add verb="*" path="*_AppService.axd" validate="false"       type="System.Web.Script.Services.ScriptHandlerFactory,             System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,             PublicKeyToken=31bf3856ad364e35"/>     <add verb="GET,HEAD" path="ScriptResource.axd"       type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions,             Version=1.0.61025.0, Culture=neutral,             PublicKeyToken=31bf3856ad364e35" validate="false"/>   </httpHandlers>   ... </system.web>

The <httpModules> section adds a new HTTP module that adds additional processing for HTTP requests in the Web application. This enables partial-page postbacks.

 <system.web>   ...   <httpModules>     <add name="ScriptModule"       type="System.Web.Handlers.ScriptModule, System.Web.Extensions,             Version=1.0.61025.0, Culture=neutral,             PublicKeyToken=31bf3856ad364e35"/>   </httpModules> </system.web>

The remaining configuration settings are configured by the <configSections> settings, which are included as the first child element of <configuration>. This section, which is not listed here, must be included so that you can use the <system.web.extensions> and <system.webServer> sections.

The <system.web.extensions> section contains settings that provide additional configuration for ASP.NET AJAX, all of which is optional and is commented out when you use the Web application template. Most of this configuration concerns Web services and is contained in the <webServices> element. First, this section contains an optional section that you can use to control the JSON serialization for Web services:

  <system.web.extensions>   <scripting>     <webServices>       <!--       <jsonSerialization maxJsonLength="500">         <converters>           <add name="ConvertMe" type="Acme.SubAcme.ConvertMeTypeConverter"/>         </converters>       </jsonSerialization>       --> 

Next, there is a section to enable access to the ASP.NET authentication service through a Web service (you can choose to enforce SSL here if you wish):

  <!-- <authenticationService enabled="true" requireSSL = "true|false"/> --> 

The last Web service-related setting is for enabling and configuring access to ASP.NET personalization functionality through the profile Web service:

    <!--   <profileService enabled="true"     readAccessProperties="propertyname1,propertyname2"     writeAccessProperties="propertyname1,propertyname2" />   --> </webServices> 

Finally, the <system.web.extensions> section contains an element that enables you to configure compression and caching for asynchronous communications:

      <!--     <scriptResourceHandler enableCompression="true" enableCaching="true" />     -->   </scripting> </system.web.extensions> 

The final section, <system.webServer>, contains settings that relate to the IIS 7 Web server; this section is not required if you are using an earlier version of IIS. This section is not listed here.

Additional Configuration for CTP-Enabled Web Sites

If you use the ASP.NET AJAX CTP-Enabled Web Site template, the Web.config file contains additional settings. First, additional controls are associated with the asp tag prefix:

 <controls>   ...   <add tagPrefix="asp" namespace="Microsoft.Web.Preview.UI"     assembly="Microsoft.Web.Preview"/>   <add tagPrefix="asp" namespace="Microsoft.Web.Preview.UI.Controls"     assembly="Microsoft.Web.Preview"/> </controls>

These controls are taken from the Microsoft.Web.Preview.dll assembly, which should be in the /bin directory for CTP-enabled Web applications.

You can add this configuration manually to add these controls to an existing application.

The Web.config file also includes settings for bridging to remote Web services by using the .asbx file type. This functionality is not covered in this chapter, but if you use it then you will need to include the following configuration information:

 <compilation debug="false">   ...   <buildProviders>     <add extension="*.asbx"       type="Microsoft.Web.Preview.Services.BridgeBuildProvider"/>   </buildProviders> </compilation> <httpHandlers>   ...   <add verb="GET,HEAD,POST" path="*.asbx"     type="System.Web.Script.Services.ScriptHandlerFactory,           System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,           PublicKeyToken=31bf3856ad364e35" validate="false"/> </httpHandlers>

There is also an additional handler listed in the IIS 7 configuration section.0

Additional Configuration for the AJAX Control Toolkit

To use the controls in the AJAX Control Toolkit, you can use the AJAX Control Toolkit Web Site template, which includes the following configuration:

 <controls>   ...   <add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit"     tagPrefix="ajaxToolkit"/> </controls>

This maps the toolkit controls to the ajaxToolkit tag prefix. These controls are contained in the AjaxControlToolkit.dll assembly, which should be in the /bin directory for the Web application.

You can add this configuration manually to add these controls to an existing application.

Adding ASP.NET AJAX Functionality

Once you have configured a Web site to use ASP.NET AJAX, either by using the Web site templates or by manually configuring a new or existing ASP.NET Web site, you can proceed to add Ajax functionality. The first step is to add a ScriptManager control to your Web pages or to configure the ScriptManager control that is supplied by the templates. Next, you will add server controls such as UpdatePanel controls to enable partial-page rendering and dynamic controls such as those supplied in the Futures CTP and AJAX Control Toolkit to add usability and glitz to your application. You may also add client-side code, and can use the AJAX Library to assist you in further customizing and enhancing the functionality of your application.

In this section, you’ll look at the functionality you can add using server controls. Later in the chapter you’ll look at client-side techniques.

The ScriptManager Control

As mentioned earlier in the chapter, the ScriptManager control must be included on all pages that use partial-page postbacks and several other aspects of ASP.NET AJAX functionality.

Tip 

A great way to ensure that all the pages in your Web application contain the ScriptManager control is to add this control to the master page (or master pages) that your application uses.

As well as enabling ASP.NET AJAX functionality, you can also use properties to configure this control. The simplest of these properties is EnablePartialRendering, which is true by default. If you set this property to false, then you will disable all asynchronous postback processing, such as that provided by UpdatePanel controls. This can be useful, for example, if you want to compare your AJAX-enabled Web site with a traditional Web site, perhaps if you are giving a demonstration to a manager.

You can use the ScriptManager control for several reasons, such as in the following common situations:

  • To determine whether server-side code is being called as a result of a partial-page postback

  • To add references to additional client-side JavaScript files

  • To reference Web services

  • To return error messages to the client

These configuration options are covered in the following sections.

Detect Partial-Page Postbacks

The ScriptManager control includes a Boolean property called IsInAsyncPostBack. You can use this property in server-side code to detect whether a partial-page postback is in progress. Note that the ScriptManager for a page may actually be on a master page. Rather than accessing this control through the master page, you can obtain a reference to the current ScriptManager instance by using the static

  GetCurrent() method, for example: ScriptManager scriptManager = ScriptManager.GetCurrent(this); if (scriptManager != null && scriptManager.IsInAsyncPostBack) {    // Code to execute for partial-page postbacks. } 

You must pass a reference to a Page control to the GetCurrent() method. For example, if you use this method in a Page_Load() event handler for an ASP.NET Web page, then you can use this as your Page reference. Also, remember to check for a null reference to avoid exceptions.

Client-Side JavaScript References

Rather than adding code to the HTML page header, or in <script> elements on the page, you can use the Scripts property of the ScriptManager class. This centralizes your script references and makes it easier to maintain them. You can do this declaratively by adding a child <Scripts> element to the <UpdatePanel> control element, and then adding <asp:ScriptReference> child control elements to <Scripts>. You use the Path property of a ScriptReference control to reference a custom script.

One common use of the Scripts property is to add references to the Futures CTP script files, which contain the Futures CTP additions to client-side functionality. There are several of these script files, including two main ones, which are called Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js and Microsoft.Web.Resources.ScriptLibrary.PreviewGlitz.js. You can get references to these script files from the Microsoft.Web.Preview.dll assembly by setting the Name and Assembly properties of a ScriptReference control.

The following sample shows how to add references to the two main Futures CTP script files and a custom script file called MyScript.js in the root folder of the Web application:

 <asp:ScriptManager runat="server" >   <Scripts>     <asp:ScriptReference Path="~/MyScript.js" />     <asp:ScriptReference       Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js"       Assembly="Microsoft.Web.Preview" />     <asp:ScriptReference       Name="Microsoft.Web.Resources.ScriptLibrary.PreviewGlitz.js"       Assembly="Microsoft.Web.Preview" />   </Scripts> </asp:ScriptManager>

Web Service References

In order to access Web services from client-side JavaScript code, ASP.NET AJAX must generate a proxy class. To control this behavior, you use the Services property of the ScriptManager class. As with Scripts, you can specify this property declaratively, this time with a <Services> element.

You add <asp:ServiceReference> controls to this element. For each ServiceReference object in the Services property, you specify the path to the Web service by using the Path property.

The ServiceReference class also has a property called InlineScript, which defaults to false. When this property is false, client-side code obtains a proxy class to call the Web service by requesting it from the server. To enhance performance (particularly if you use a lot of Web services on a page), you can set InlineScript to true. This causes the proxy class to be defined in the client-script for the page.

ASP.NET Web services, which are covered in Chapter 36, use a file extension of .asmx. Without wanting to get into too much detail in this chapter, to add a reference to a Web service called MyService.asmx in the root folder of a Web application, you would use code as follows:

 <asp:ScriptManager runat="server" >   <Services>     <asp:ServiceReference Path="~/MyService.asmx" />   </Services> </asp:ScriptManager>

You can only add references to local Web services (that is, Web services in the same Web application as the calling code) in this way. To reference remote Web services, you can either use the Futures CTP bridging functionality or call remote Web service indirectly via local Web methods.

Later in this chapter you will see how to make asynchronous Web method calls from client-side JavaScript code that uses proxy classes generated in this way.

Client-Side Error Messages

If an exception is thrown as part of a partial-page postback then the default behavior is to place the error message contained in the exception into a client-side JavaScript alert message box. You can customize the message that is displayed by handling the AsyncPostBackError event of the ScriptManager instance. In the event handler, you can use the AsyncPostBackErrorEventArgs.Exception property to access the exception that is thrown and the ScriptManager.AsyncPostBackErrorMessage property to set the message that is displayed to the client. You might do this to hide the exception details from users.

If you want to override the default behavior and display a message in a different way, you must handle the endRequest event of the client-side PageRequestManager object by using JavaScript. This is described later in this chapter.

Using UpdatePanel Controls

The UpdatePanel control is perhaps the control that you will use most often when you write ASP.NET AJAX-enabled Web applications. This control, as you have seen in the simple example earlier in the chapter, enables you to wrap a portion of a Web page so that it is capable of participating in a partial-page postback operation. To do this, you add an UpdatePanel control to the page and fill its child <ContentTemplate> element with the controls that you want it to contain.

 <asp:UpdatePanel runat="Server" >   <ContentTemplate>     ...   </ContentTemplate> </asp:UpdatePanel>

The contents of the <ContentTemplate> template are rendered in either a <div> or <span> element according to the value of the RenderMode property of the UpdatePanel. The default value of this property is Block, which will result in a <div> element. To use a <span> element, set RenderMode to Inline.

Multiple UpdatePanel Controls on a Single Web Page

You can include any number of UpdatePanel controls on a page. If a postback is caused by a control that is contained in the <ContentTemplate> of any UpdatePanel on the page, a partial-page postback will occur instead of a full-page postback. This will cause all the UpdatePanel controls to update according to the value of their UpdateMode property. The default value of this property is Always, which means that the UpdatePanel will update for a partial-page postback operation on the page, even if this operation occurs in a different UpdatePanel control. If you set this property to Conditional, the UpdatePanel updates only when a control that it contains causes a partial-page postback or when a trigger that you have defined occurs. Triggers are covered shortly.

If you have set UpdateMode to Conditional, you can also set the ChildrenAsTriggers property to false to prevent controls that are contained by the UpdatePanel from triggering an update of the panel. Note, though, that in this case these controls still trigger a partial-page update, which may result in other UpdatePanel controls on the page being updated. For example, this will update controls that have an UpdateMode property value of Always. This is illustrated in the following code:

  <asp:UpdatePanel runat="Server"  UpdateMode="Conditional"   ChildrenAsTriggers="false">   <ContentTemplate>     <asp:Button runat="Server"  Text="Click Me" />     <small>Panel 1 render time: <% =DateTime.Now.ToLongTimeString() %></small>   </ContentTemplate> </asp:UpdatePanel> <asp:UpdatePanel runat="Server" >   <ContentTemplate>     <small>Panel 2 render time: <% =DateTime.Now.ToLongTimeString() %></small>   </ContentTemplate> </asp:UpdatePanel> <small>Page render time: <% =DateTime.Now.ToLongTimeString() %></small> 

In this code, the UpdatePanel2 control has an UpdateMode property of Always; the default value. When the button is clicked, it will cause a partial-page postback, but only UpdatePanel2 will be updated. Visually, you will notice that only the “Panel 2 render time” label is updated.

Server-Side UpdatePanel Updates

Sometimes when you have multiple UpdatePanel controls on a page, you might decide not to update one of them unless certain conditions are met. In this case, you would configure the UpdateMode property of the panel to Conditional as shown in the previous section and possibly also set the ChildrenAsTriggers property to false. Then, in your server-side event-handler code for one of the controls on the page that causes a partial-page update, you would (conditionally) call the Update() method of the UpdatePanel. For example:

  protected void Button1_Click(object sender, EventArgs e) {    if (TestSomeCondition())    {       UpdatePanel1.Update();    } } 

UpdatePanel Triggers

You can cause an UpdatePanel control to be updated by a control elsewhere on the Web page by adding triggers to the Triggers property of the control. A trigger is an association between an event of a control elsewhere on the page and the UpdatePanel control. All controls have default events (for example, the default event of a Button control is Click), so specifying the name of an event is optional. There are two types of triggers that you can add, represented by the following two classes:

  • AsyncPostBackTrigger - This class causes the UpdatePanel to update when the specified event of the specified control is triggered.

  • PostBackTrigger - This class causes a full-page update to be triggered when the specified event of the specified control is triggered.

You will mostly use AsyncPostBackTrigger, but PostBackTrigger can be useful if you want a control inside an UpdatePanel to trigger a full-page postback.

Both of these trigger classes have two properties: ControlID, which specifies the control that causes the trigger by its identifier, and EventName, which specifies the name of the event for the control that is linked to the trigger.

To extend an earlier example, consider the following code:

 <asp:UpdatePanel runat="Server"  UpdateMode="Conditional"   ChildrenAsTriggers="false">   <Triggers>     <asp:AsyncPostBackTrigger Control />   </Triggers>   <ContentTemplate>     <asp:Button runat="Server"  Text="Click Me" />     <small>Panel 1 render time: <% =DateTime.Now.ToLongTimeString() %></small>   </ContentTemplate> </asp:UpdatePanel> <asp:UpdatePanel runat="Server" >   <ContentTemplate>     <asp:Button runat="Server"  Text="Click Me" />     <small>Panel 2 render time: <% =DateTime.Now.ToLongTimeString() %></small>   </ContentTemplate> </asp:UpdatePanel> <small>Page render time: <% =DateTime.Now.ToLongTimeString() %></small>

The new Button control, Button2, is specified as a trigger in the UpdatePanel1. When this button is clicked, both UpdatePanel1 and UpdatePanel2 will be updated.

Using UpdateProgress

The UpdateProgress control, as you saw in the earlier example, enables you to display a progress message to the user while a partial-page postback is in operation. You use the ProgressTemplate property to supply an ITemplate for the progress display. You will typically use the <ProgressTemplate> child element of the control to do this.

You can place multiple UpdateProgress controls on a page by using the AssociatedUpdatePanelID property to associate the control with a specific UpdatePanel. If this is not set (the default) then the UpdateProgress template will be displayed for any partial-page postback, regardless of which UpdatePanel causes it.

When a partial-page postback occurs, there is a delay before the UpdateProgress template is displayed. This delay is configurable through the DisplayAfter property, which is an int property that specifies the delay in milliseconds. The default is 500 milliseconds.

Finally, you can use the Boolean DynamicLayout property to specify whether space is allocated for the template before it is displayed. For the default value of true for this property, space on the page is dynamically allocated, which may result in other controls being moved out of the way for an inline progress template display. If you set this property to false then space will be allocated for the template before it is displayed, so the layout of other controls on the page will not change. You will set this property according to the effect you want to achieve when displaying progress. For a progress template that is positioned by using absolute coordinates, as in the earlier example, you should leave this property set to the default value.

Using Extender Controls

The core ASP.NET AJAX download includes a class called ExtenderControl. The purpose of this control is to enable you to extend (that is, add functionality to) other ASP.NET server controls. This is used extensively in the AJAX Control Toolkit to great effect, and you can use the template in the AJAX Control Toolkit to create your own extended controls. ExtenderControl controls all work in a similar way - you place them on a page, associate them with target controls, and add further configuration. The extender then emits client-side code to add functionality.

To see this in action in a simple example, create a new Web site called PCSExtenderDemo that uses the AJAX Control Toolkit Web Site template in the C:\ProCSharp\Chapter34 directory, and add the following code to Default.aspx:

 <%@ 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>Color Selector</title> </head> <body>   <form  runat="server">     <asp:ScriptManager  runat="server" />     <div>       <asp:UpdatePanel runat="server" >         <ContentTemplate>           <span style="display: inline-block; padding: 2px;">             My favorite color is:           </span>           <asp:Label runat="server"  Text="green"             style="color: #00dd00; display: inline-block; padding: 2px;                    width: 70px; font-weight: bold;" />           <ajaxToolkit:DropDownExtender runat="server"              TargetControl DropDownControl />           <asp:Panel  runat="server"             Style="display: none; visibility: hidden; width: 60px; padding: 8px;                    border: double 4px black; background-color: #ffffdd;                    font-weight: bold;">             <asp:LinkButton runat="server"  Text="red"               OnClick="OnSelect" style="color: #ff0000;" /><br />             <asp:LinkButton runat="server"  Text="orange"               OnClick="OnSelect" style="color: #dd7700;" /><br />             <asp:LinkButton runat="server"  Text="yellow"               OnClick="OnSelect" style="color: #dddd00;" /><br />             <asp:LinkButton runat="server"  Text="green"               OnClick="OnSelect" style="color: #00dd00;" /><br />             <asp:LinkButton runat="server"  Text="blue"               OnClick="OnSelect" style="color: #0000dd;" /><br />             <asp:LinkButton runat="server"  Text="purple"               OnClick="OnSelect" style="color: #dd00ff;" />           </asp:Panel>         </ContentTemplate>       </asp:UpdatePanel>     </div>   </form> </body> </html>

You also need to add the following event handler to the code behind this file:

  protected void OnSelect(object sender, EventArgs e) {    favoriteColorLabel.Text = ((LinkButton)sender).Text;    favoriteColorLabel.Style["color"] = ((LinkButton)sender).Style["color"]; } 

In the browser, not very much is visible at first, and the extender seems to have no effect. This is shown in Figure 34-6.

image from book
Figure 34-6

However, when you hover over the text that reads “green,” a drop-down dynamically appears. If you click this drop-down, a list appears, as shown in Figure 34-7.

image from book
Figure 34-7

When you click one of the links in the drop-down list, the text changes accordingly (after a partial-page postback operation).

There are two important points to note about this simple example. First, it was extremely easy to associate the extender with target controls. Second, the drop-down list was styled using custom code - meaning that you can place whatever content you like in the list. This simple extender is a great way to add functionality to your Web applications, and it’s very simple to use.

The extenders that are contained in the AJAX Control Toolkit are continually being added to and updated, so check http://ajax.asp.net/ajaxtoolkit regularly. This Web page includes live demonstrations of all the current extenders so that you can see them in action.

In addition to the extender controls that are supplied by the AJAX Control Toolkit, you can create your own. To make this process as simple as possible, the AJAX Control Toolkit includes a project template, called the ASP.NET AJAX Control Project. This project includes all the basic functionality that you require for an extender, including the server-side class for the extender and the client-side JavaScript behavior file that the extender uses. The project files also include plenty of comments to help you build an extender. To create an effective extender, you must use the AJAX Library.

Using the AJAX Library

There is a great deal of functionality available in the AJAX Library that you can use to further enhance your Web applications. However, in order to do this you require at least a working knowledge of JavaScript. In this section, you’ll see some of the functionality on offer, although this is not an exhaustive tutorial.

The basic principles behind the use of the AJAX Library are much the same as for adding any type of client-side script to a Web application. You will still use the core JavaScript language, and you will still interact with the DOM. However, there are many areas where the AJAX Library makes things easier for you. In this section, you will learn about many of these and be provided with a foundation that you can build on with further experimentation and reference to the online AJAX Library documentation.

The techniques you cover in this section are illustrated in the PCSLibraryDemo project, which is referred to throughout the rest of this chapter.

Adding JavaScript to a Web Page

The first thing you need to know is how to add client-side JavaScript to a Web Page. You have three options here:

  • Add JavaScript inline in ASP.NET Web pages, by using the <script> element.

  • Add JavaScript to separate JavaScript files with the extension .js and reference these files from <script> elements or (preferably) by using the <Scripts> child element of the ScriptManager control.

  • Generate JavaScript from server-side code, such as code behind or custom extender controls.

Each of these techniques has its own benefits. For prototyping code, there is no substitute for inline code because it is so quick and easy to use. You will also find it easy to associate client-side event handlers of HTML elements and server controls with client-side functions, as everything is in the same file.

Having separate files is good for reusability, as you may create your own library of classes much like the existing AJAX Library JavaScript files.

Generating code from code behind can be tricky to implement, as you will not usually have access to IntelliSense for JavaScript programming when you use C# code. However, you will be able to generate code dynamically in response to application state, and sometimes this is the only way to do things.

The extenders that you can create with the AJAX Control Toolkit include a separate JavaScript file that you use to define behaviors, which gets round some of the problems of exposing client-side code from the server.

In this chapter, you’ll use the inline code technique, as it is simplest and allows you to concentrate on the JavaScript functionality.

Global Utility Functions

One of the features supplied by the AJAX Library that you will use most often is the set of global functions that wrap other functionality. These include the following:

  • $get() - This function enables you to get a reference to a DOM element by supplying its client-side id value as a parameter, with an optional second parameter to specify the parent ele-ment to search in.

  • $create() - This function enables you to create objects of a specific JavaScript type and perform initialization at the same time. You can supply between one and five parameters to this function. The first function is the type you want to instantiate, which will typically be a type defined by the AJAX Library. The other parameters enable you to specify initial property values, event handlers, references to other components, and the DOM element that the object is attached to, respectively.

  • $addHandler() - This function provides a shorthand for adding an event handler to an object.

There are more global functions, but these are the ones you will use most option. $create() in particular is a very useful way to reduce the amount of code required to create initialize an object.

Using the AJAX Library JavaScript OOP Extensions

The AJAX Library includes an enhanced framework for defining types that uses an OOP-based system that maps closely to .NET Framework techniques. You can create namespaces, add types to namespaces, add constructors, methods, properties, and events to types, and even use inheritance and interfaces in type definitions.

In this section, you’ll see how to implement the basics of this functionality, but you won’t look at events and interfaces here. These constructs are beyond the scope of this chapter.

Defining Namespaces

To define a namespace, you use the Type.refisterNamespace() function, for example:

  Type.registerNamespace("ProCSharp"); 

Once you have registered a namespace you can add types to it.

Defining Classes

Defining a class is a three-stage process. First, you define the constructor. Next, you add properties and methods. Finally, you register the class.

To define a constructor, you define a function using a namespace and class name, for example:

  ProCSharp.Shape = function(color, scaleFactor) {    this._color = color;    this._scaleFactor = scaleFactor; } 

This constructor takes two parameters and uses them to set local fields (note that you do not have to explicitly define these fields, you just have to set their values).

To add properties and methods, you assign them to the prototype property of the class as follows:

  ProCSharp.Shape.prototype = {   getColor : function() {     return this._color;   },   setColor : function(color) {     this._color = color;   },   getScaleFactor : function() {     return this._scaleFactor;   },      setScaleFactor : function(scaleFactor) {     this._scaleFactor = scaleFactor;   } } 

This code defines two properties by their get and set accessors.

To resister class you call its registerClass() function:

  ProCSharp.Shape.registerClass('ProCSharp.Shape'); 

Inheritance

You derive a class in the much same way as creating a class but with some slight differences. You use the initializeBase() function to initialize the base class in the constructor, passing parameters in the form of an array:

  ProCSharp.Shape.registerClass('ProCSharp.Shape'); ProCSharp.Circle = function(color, scaleFactor, diameter) {    ProCSharp.Circle.initializeBase(this, [color, scaleFactor]);    this._diameter = diameter; } 

You define properties and methods in the same way as before:

  ProCSharp.Circle.prototype = {   getDiameter : function() {     return this._diameter;   },   setDiameter : function(diameter) {     this._diameter = diameter;   },   getArea : function() {     return Math.PI * Math.pow((this._diameter * this._scaleFactor) / 2, 2);   },   describe : function() {     var description = "This is a " + this._color + " circle with an area of "       + this.getArea();     alert(description);   } } 

When you register the class, you provide the base class type as a second parameter:

  ProCSharp.Circle.registerClass('ProCSharp.Circle', ProCSharp.Shape); 

You can implement interfaces by passing them as additional parameters, although you won’t see details of that here to keep things simple.

Using User-Defined Types

Once you have defined classes in this way, you can instantiate and use them with simple syntax. For example:

  var myCircle = new ProCSharp.Circle('red', 1.0, 4.4); myCircle.describe(); 

This code would result in a JavaScript alert box, as shown in Figure 34-8.

image from book
Figure 34-8

If you want to test this, run the PCSLibraryDemo project and click the Test OOP Functionality button.

The PageRequestManager and Application Objects

Among the most useful classes that the AJAX Library provides are the PageRequestManager and Application classes. You will find PageRequestManager in the Sys.WebForms namespace and Application in the Sys namespace. The important thing about these classes is that they expose several events that you can attach JavaScript event handlers to. These events occur at particularly interesting points in the life cycle of a page (for Application) or partial-page postback (for PageRequestManager) and enable you to perform operations at these critical times.

The AJAX Library defines event handlers in a similar way to event handlers in the .NET Framework. Every event handler has a similar signature, with two parameters. The first parameter is a reference to the object that generated the event. The second parameter is an instance of the Sys.EventArgs class or an instance of a class that derives from this class. Many of the events exposed by PageRequestManager and Application include specialized event argument classes that you can use to determine more information about the event. The following table lists these events in the order they will occur in a page that is loaded, triggers a partial-page postback, and is then closed.

Open table as spreadsheet

Event

Description

Application .init

This event is the first to occur in the life cycle of a page. It is raised after all the JavaScript files have been loaded but before any objects in the application have been created.

Application .load

This event fires after the objects in the application have loaded and been initialized. You will often use an event handler attached to this event to perform actions when the page is first loaded. You can also provide an implementation for a function called pageLoad() on a page, which is automatically defined as an event handler for this event. Sends event arguments by using a Sys.ApplicationLoadEventArgs object, which includes the isPartialLoad property that you can use to determine if a partial-page postback has occurred. Access this property with the get_isPartialLoad() accessor.

PageRequestManager.initializeRequest

Occurs before a partial-page postback, before the request object is created. You can use the Sys.WebForms.InitializeRequestEventArgs event argument properties to access the element that triggered the postback (postBackElement) and the underlying request object (request).

PageRequestManager.beginRequest

Occurs before a partial-page postback, after the request object is created. You can use the Sys.WebForms.BeginRequestEventArgs event argument properties to access the element that triggered the postback (postBackElement) and the underlying request object (request).

PageRequestManager.pageLoading

This event is raised after a partial-page postback, before any subsequent processing occurs. This processing can include <div> elements that will be deleted or updated, which you can reference through the Sys.WebForms.PageLoadingEventArgs object by using the panelsDeleting and panelsUpdating properties.

PageRequestManager.pageLoaded

This event is raised after a partial-page postback, after UpdatePanel controls have been processed. This processing can include <div> elements that have been created or updated, which you can reference through the Sys.WebForms.PageLoadedEventArgs object by using the panelsCreated and panelsUpdated properties.

PageRequestManager.endRequest

Occurs after processing of a partial-page postback has completed. The Sys.WebForms.EndRequestEventArgs object passed to the event handler enables you to detect and process server-side errors (by using the error and errorHandled properties) as well as to access the response object though response.

Application .unload

This event is raised just before the objects in the application are disposed, which gives you a chance to perform final actions or cleanup if necessary.

You can add an event handler to an event of the Application object by using the static add_xxx() functions, for example:

  Sys.Application.add_load(LoadHandler); function LoadHandler(sender, args) {    // Event handler code. } 

The process is similar for PageRequestManager, but you must use the get_instance() function to obtain an instance of the current object, for example:

  Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(   BeginRequestHandler); function BeginRequestHandler(sender, args) {    // Event handler code. } 

In the PCSLibraryDemo application, an event handler is added to the PageRequestManager.endRequest event. This event handler responds to server-side processing errors and displays an error message in a <span> element with an id of errorDisplay. To test this method, click the Test Client-Side Error Display button, as shown in Figure 34-9.

image from book
Figure 34-9

The code that achieves this is:

  Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler); function EndRequestHandler(sender, args) {    if (args.get_error() != undefined)    {       var errorMessage = args.get_error().message;       args.set_errorHandled(true);       $get('errorDisplay').innerHTML = errorMessage;    } } 

Note that the errorHandled property of the EndRequestEventArgs object is set to true. This prevents the default behavior, which is to display the error message in a dialog box by using the JavaScript alert() function.

The error itself is generated by throwing an exception on the server as follows:

  protected void testErrorDisplay_Click(object sender, EventArgs e) {    throw new ApplicationException(       "This is the message set in the exception on the server."); } 

There are many other situations when you will want to use event handling techniques to act on the Application and PageRequestManager events.

JavaScript Debugging

JavaScript is notorious for being difficult to debug. Often you will not be able to tell when an error occurs; you will just find that your code doesn’t work like it’s supposed to. Unlike C# code, you cannot step through JavaScript code. Instead, you will mostly add debug and trace code to report information as code is executed and sometime to show information in dialog boxes by using the JavaScript alert() function.

There are third-party tools that you can use to add a client-side UI for debugging. These include:

  • Fiddler - This tool, which you can obtain from www.fiddlertool.com, enables you to log all HTTP traffic between your computer and a Web application - including partial-page postbacks. There are also additional tools that you can use to look at what occurs during the processing of Web pages in more detail.

  • Nikhil’s Web Development Helper - This tool, available at http://projects.nikhilk.net/Projects/WebDevHelper.aspx, can also log HTTP traffic. In addition, this tool contains a number of utilities specifically aimed at ASP.NET and ASP.NET AJAX development, for example, the ability to examine view state and to execute immediate JavaScript code. This latter feature is particularly useful to test objects that you may have created on the client. The Web Development Helper also displays extended error information when JavaScript errors occur, which makes it easier to track down bugs in JavaScript code.

As well as these, the next version of Visual Studio, currently codenamed “Orcas,” is expected to include more extensive JavaScript support and client-side debugging tools.

The AJAX Library also provides the Sys.Debug class, which you can use to add some extra debugging features to your application. One of the most useful features of this class is the Sys.Debug.traceDump() function, which enables you to analyze objects. One way to use this function is to place a textarea control on your Web page with an id attribute of TraceConsole. Then, all output from Debug will be sent to this control. For example, you can use the traceDump() method to output information about the Application object to the console:

 Sys.Application.add_load(LoadHandler); function LoadHandler(sender, args) {   Sys.Debug.traceDump(sender); }

This results in the output along the lines of the following:

  traceDump {Sys._Application}     _updating: false     _id: null     _disposing: false     _creatingComponents: false     _disposableObjects {Array}     _components {Object}     _createdComponents {Array}     _secondPassComponents {Array}     _loadHandlerDelegate: null     _events {Sys.EventHandlerList}         _list {Object}             load {Array}                 [0] {Function}     _initialized: true     _initializing: true 

You can see all the properties of this object in this output. This technique can be extremely useful for ASP.NET AJAX development.

Making Asynchronous Web Method Calls

One of the most powerful features of ASP.NET AJAX is the ability to call Web methods from client-side script. This gives you access to data access, server-side processing, and all manner of other functionality.

You will not be looking at Web methods until Chapter 36 of this book, so this is not the place to explore the details of this. However, you can learn about the basics. Put simply, a Web method is a method that you can expose from a Web service that enables you to access remote resources over the internet. In ASP.NET AJAX, you can also expose Web methods as static methods of server-side Web page code-behind code. You can use parameters and return values in Web methods just as you do in other method types.

In ASP.NET AJAX, Web methods are called asynchronously. You pass parameters to a Web method and define a callback function, which is called when the Web method call completes. You use this callback function to process the Web method response. You can also provide an alternative callback function to call in the event of a call failure.

In the PCSLibraryDemo application, you can see a Web method call being performed by clicking the Call Web Method button, as shown in Figure 34-10.

image from book
Figure 34-10

Before you can use a Web method from client-side script, you must generate a client-side proxy class to perform the communication. The easiest way to do this is simply to reference the URL of the Web service that contains the Web method in the ScriptManager control:

  <asp:ScriptManager  runat="server">   <Services>     <asp:ServiceReference Path="~/SimpleService.asmx" />   </Services> </asp:ScriptManager> 

ASP.NET Web services use the extension .asmx, as shown in this code. In order to use a client-side proxy to access a Web method in a Web service, you must apply the System.Web.Script.Services.ScriptService attribute to the Web service.

For Web methods in the code behind for the Web page, you do not need this attribute, or this reference in ScriptManager, but you must use static methods and apply the System.Web.Services.WebMethod attribute to the methods.

Once you have generated a client-side stub, you can access the Web method by its name, which is defined as a function of a class with the same name as the Web service. In PCSLibraryDemo, the SimpleService.asmx Web service has a Web method called Multiply(), which multiplies two double parameters. When you call this method from client side code, you pass the two parameters required by the method (obtained from HTML <input> elements in the example) and can pass one or two callback function references. If you pass one reference, this is the callback function that is used when the call returns a success result. If you pass two references, the second one is the callback function that is used for Web method failure.

In PCSLibraryDemo, a single callback function is used, which takes the result of the Web method call and assigns it to the <span> with the id of webMethodResult:

  function callWebMethod() {   SimpleService.Multiply(parseFloat($get('xParam').value),     parseFloat($get('yParam').value), multiplyCallBack); } function multiplyCallBack(result) {   $get('webMethodResult').innerHTML = result; } 

This method is a very simple one but illustrates the ease with which you can call Web services asynchronously from client-side code.

ASP.NET Application Services

ASP.NET AJAX includes two specialized Web services that you can use to access ASP.NET application services. These services are accessed through the following client-side classes:

  • Sys.Services.AuthenticationService - This service includes methods to log in or log out a user or determine whether a user is logged in.

  • Sys.Services.ProfileService - This service enables you to get and set profile properties for the currently logged-on user. The profile properties are configured in the Web.config file for the application.

Used properly, these classes enable you to implement extremely responsive user interfaces that include authorization and profile functionality. You can also use membership functionality quite easily by creating a custom Web method that exposes calls to HttpContext.Current.User.IsInRole() to determine whether the user is a member of a particular role.

These services are beyond the scope of this chapter, but you should be aware of them, and they are well worth investigating.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net