Chapter 33. Using Client-Side ASP.NET AJAX

<feature>

IN THIS CHAPTER

</feature>

This chapter is about the future of building web applications. In this chapter, you learn how to build “pure” Ajax applications that execute on the browser instead of the server.

In the first part of this chapter, you learn how Microsoft has extended JavaScript so that the JavaScript language more closely resembles .NET languages such as C# and VB.NET. You also learn how Microsoft has added useful debugging and tracing support to JavaScript.

Next, we get to the heart of client-side AJAX. You learn how to perform Ajax calls from the browser to the server. You learn how to call both web methods exposed by a web service and web methods exposed by a page. We also discuss how you can call three built-in services included with the AJAX Framework. You learn how to work with the Authentication, Role, and Profile services.

Finally, you learn how to create client-side AJAX controls and behaviors. You learn how to add client-side controls and behaviors to an AJAX page both programmatically and declaratively.

Making JavaScript Look Like C#

Let me start by saying that there is nothing wrong with JavaScript the language. It is not a toy language. It is not a limited language. JavaScript simply has it roots in a different programming language family than other languages you are familiar with, such as C# and VB.NET.

Note

For a great, quick introduction to JavaScript the language, I recommend that you read “A re-introduction to JavaScript” at http://developer.mozilla.org/en/docs/A_re-introduction_to_JavaScript.

JavaScript is an object-oriented programming language. In fact, one could argue that it is more object-oriented than languages such as C# and VB.NET. In a language such as C#, you make a distinction between classes (Aristotelian forms) and objects (Aristotelian matter). An object is an instance of a class, but a class does not exist in its own right.

In JavaScript, classes do not exist. The only thing that exists are objects (everything is matter). Objects are not related to one another by being instances of the same class. Instead, one object can be related to another object when one object is the prototype for another object.

Another major difference between JavaScript and C# is that JavaScript is a dynamic language. The type of a variable can change at any moment during runtime. When JavaScript code is executed, a String might transform into an Integer and back again. The C# language, on the other hand, is statically typed. Once declared, a String is a String and it can never be anything else.

In the past, I believe that there were two reasons that JavaScript was not taken very seriously as a programming language. First, it seemed to be a dead language. If it was evolving, no one was paying attention. This situation changed when Mozilla Firefox was introduced into the world. New versions of JavaScript have been popping up in new releases of Firefox. It is no coincidence that the inventor of JavaScript, Brendan Eich, is now the CTO of Mozilla.

Second, for a number of years, JavaScript was primarily used to create cheesy special effects on web pages or obnoxious advertisements that floated across web pages. All the excitement over Ajax, however, has caused developers to reexamine JavaScript as a language. Developers have learned that JavaScript is a much more flexible and powerful language than originally thought.

For example, Douglas Crockford writes the following in “A Survey of the JavaScript Programming Language” (http://javascript.crockford.com/survey.html):

When JavaScript was first introduced, I dismissed it as being not worth my attention. Much later, I took another look at it and discovered that hidden in the browser was an excellent programming language. My initial attitudes were based on the initial positioning of JavaScript by Sun and Netscape. They made many misstatements about JavaScript in order to avoid positioning JavaScript as a competitor to Java. Those misstatements continue to echo in the scores of badly written JavaScript books aimed at the dummies and amateurs market.

Note

Several good articles on the evolution of the perception of JavaScript as a toy language to a real language can be found at the Ajaxian website (www.ajaxian.com).

So, it is with mixed feelings that I describe Microsoft’s attempts to make JavaScript work more like C#. On the one hand, I really don’t think the language needs improving. On the other hand, one of the best features of JavaScript is that you can easily extend it. So, if Microsoft can make JavaScript work more like C#, and be more palatable to .NET developers, who can blame them?

Using the Microsoft AJAX Library

The supporting code for the client-side Microsoft AJAX Framework is contained in a single JavaScript file named MicrosoftAjax.js. This file is included in a page automatically when you add an ASP.NET ScriptManager control to a page. If you add an AJAX Web Form to a website within Visual Web Developer, the page contains the ScriptManager control automatically (see Figure 33.1).

Adding an AJAX Web Form with Visual Web Developer.

Figure 33.1. Adding an AJAX Web Form with Visual Web Developer.

Note

By default, the ScriptManager adds references to both the MicrosoftAjax.js and MicrosoftAjaxWebForms.js files. The MicrosoftAjaxWebForms.js file contains the JavaScript code for supporting the UpdatePanel control. If you are not using the UpdatePanel control, you can assign the value false to the ScriptManager control’s EnablePartialRendering property and the MicrosoftAjaxWebForms.js file will no longer be included.

If you want to look at the contents of the MicrosoftAjax.debug.js file, you can open it from the Microsoft folder on the CD that accompanies this book.

The MicrosoftAjax.js file has a debug version and a release version. The release version is minimized and has debugging code removed so that it can be downloaded faster to a web browser. The size of the release version of the Microsoft AJAX Library is a trim 83KB. The size of the debug version is 325KB.

Note

For good reason, people worry about the size of AJAX libraries. The Microsoft AJAX Library is extremely tiny. For the sake of comparison, the size of the release version of the Microsoft AJAX Library is only about double the size of a typical picture that is displayed on the home page of The New York Times website (www.nytimes.com). Furthermore, it is important to remember that a browser caches a JavaScript file across multiple pages and multiple visits to the same website.

All the functionality we discuss in the following sections is contained in the JavaScript code in the MicrosoftAjax.js file.

Creating an AJAX Client Library

Before we do anything else, we need to discuss how you create an external JavaScript file and reference it in an AJAX Web Form page. You create a JavaScript file by selecting the menu option Website, Add New Item and selecting the AJAX Client Library option (see Figure 33.2).

Creating an AJAX Client Library with Visual Web Developer.

Figure 33.2. Creating an AJAX Client Library with Visual Web Developer.

For example, the file in Listing 33.1 contains a single JavaScript function called sayMessage() that displays a JavaScript alert with a message.

Example 33.1. myLibrary.js

/// <reference name="MicrosoftAjax.js"/>

function sayMessage()
{
    alert("Hello World!");
}
if(typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

You should notice two special things about this file. First, at the top of the file is a comment. The significance of this comment will be explained in the next section.

Second, at the bottom of the file is a conditional that checks whether a namespace named Sys exists. If it does exist, the Application.notifyScriptLoaded() method is called. You should add this conditional to the bottom of every external JavaScript file that you use with the ASP.NET AJAX Framework. The notifyScriptLoaded() method is used to tell the Framework that the external JavaScript file has been successfully loaded.

The check for the Sys namespace enables you to use the JavaScript file in applications that do not use the ASP.NET AJAX Framework. If the Sys namespace doesn’t exist, the application is not an ASP.NET AJAX application and there is no point in calling the notifyScriptLoaded() method.

After you create an external JavaScript file, you can use it in an ASP.NET AJAX–enabled page by creating a script reference. The page in Listing 33.2 illustrates how you add a script reference to the myLibrary.js library.

Example 33.2. ShowMyLibrary.aspx

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show myLibrary</title>
    <script type="text/javascript">

      function pageLoad()
      {
        sayMessage();
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/myLibrary.js" />
        </Scripts>
        </asp:ScriptManager>
    </div>
    </form>
</body>
</html>

The page in Listing 33.2 contains a ScriptManager control that contains a <Scripts> sub-element. A reference to the myLibrary.js JavaScript library is contained in this sub-element.

The page also contains a client-side pageLoad event handler. This event handler calls the sayMessage() function we defined in the external JavaScript file.

Taking Advantage of JavaScript Intellisense

One huge advantage of using Visual Web Developer to write client-side AJAX applications is its support for Intellisense for JavaScript. The Intellisense appears both for built-in JavaScript objects and methods and for JavaScript libraries you write.

Visual Web Developer will attempt to infer the data type of a JavaScript variable. This is quite an accomplishment considering that JavaScript is a dynamic language and a variable might change its data type at any time at runtime.

For example, suppose you assign a string to a variable and declare it like this:

var message = "hello world";

Visual Web Developer will pop up with a list of all the string methods and properties when you type the variable name followed by a period (see Figure 33.3).

Viewing Visual Web Developer JavaScript Intellisense.

Figure 33.3. Viewing Visual Web Developer JavaScript Intellisense.

Visual Web Developer can even detect whether you are attempting to work with a DOM element and will show the appropriate properties and methods.

Note

The exact list of properties and methods displayed by Intellisense for a DOM element depends on your Validation settings (select the menu option Tools, Options, Text Editor, HTML, Validation). By default, Visual Web Developer is configured to validate against the XHTML 1.0 Transitional standard. Because the innerHTML property is not supported by that standard, you won’t get the innerHTML property in Intellisense. If, however, you target Internet Explorer 6.0 for validation, the innerHTML property appears.

When you create a new AJAX Client Library file, as we did in the previous section, the top of the file includes the following comment:

/// <reference name="MicrosoftAjax.js"/>

This strange-looking comment has a very important purpose. It enables Visual Web Developer to provide Intellisense for the Microsoft AJAX Library. For example, if you type Sys.Browser, you’ll see all the properties of the Browser object contained in the Sys namespace (see Figure 33.4). The Browser object is defined in the MicrosoftAjax.js file.

Displaying Intellisense for an external library.

Figure 33.4. Displaying Intellisense for an external library.

You can provide Intellisense for a client library that you create. To add Intellisense to your libraries, you simply need to add XML comments to your JavaScript code (just like you would for C# or VB.NET code). For example, the client library in Listing 33.3 contains a method named addNumbers() that adds two numbers together. Notice that XML comments are used to describe the function and the two parameters.

Example 33.3. mathLib.js

/// <reference name="MicrosoftAjax.js"/>


function addNumbers(firstNumber, secondNumber)
{
/// <summary>Adds 2 numbers</summary>
/// <param name="firstNumber" type="Number">The first number</param>
/// <param name="secondNumber" type="Number">The second number</param>
/// <returns type="Number">The sum of numbers</returns>

    return firstNumber + secondNumber;
}

if(typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

There is one important difference between how you add XML comments to JavaScript and how you add comments to C# or VB.NET. Notice that the comment appears within the function and not above the function. This is a requirement.

After you add XML comments to a client library, the comments appear when you use the library. The comments appear in a page when you use the library in a page, and they appear in a separate client library when you add a reference the original library. For example, the file in Listing 33.4 contains a reference (at the top of the file) to the MathLib.js file. If you start typing the name of the addNumbers() function, Intellisense will appear (see Figure 33.5).

Example 33.4. referMathLib.js

/// <reference name="MicrosoftAjax.js"/>
/// <reference path="mathLib.js"/>
var num = addNumbers(1, 2);

if(typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();
Generating Intellisense with XML comments.

Figure 33.5. Generating Intellisense with XML comments.

Notice that the reference to the MicrosoftAjax.js library uses the name attribute and that the reference to the mathLib.js library uses the path attribute. The MicrosoftAjax.js library is compiled as an embedded resource into the System.Web.Extensions.dll assembly. When a library is embedded in an assembly, you use the library name instead of its path.

Working with Classes

As discussed previously, the JavaScript language does not have classes. In the JavaScript universe, everything is an object.

In order to make JavaScript look more like C#, Microsoft has extended JavaScript in such a way that you can pretend that the JavaScript language has classes. That way, you can use familiar C# language constructs such as interfaces and class inheritance.

The file in Listing 33.5 demonstrates how you create a class by using the Microsoft AJAX Library.

Example 33.5. MyClass.js

/// <reference name="MicrosoftAjax.js"/>

var MyClass = function()
{
    this._message = 'Hello World';
    this._animal = function() {alert("wow")};
};

MyClass.prototype =
{
    get_message: function()
    {
        return this._message;
    },

    set_message: function(value)
    {
       this._message = value;
    },

    sayMessage: function()
    {
        alert(this._message);
    },

    yellMessage: function()
    {
        alert(this._message + '!'),
    }
};

MyClass.registerClass('MyClass'),

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

In JavaScript, you create a particular type of object by creating a constructor function. Just like in C# or VB.NET, a constructor function is typically used to initialize the fields of the object being created. The following code declares the constructor function for MyClass:

var MyClass = function()
{
    this._message = 'Hello World';
};

This constructor initializes a single private field named _message. The underscore is used to mark the field as a private field.

Note

By convention, when building an ASP.NET AJAX application, you name private members of an object with a leading underscore. Any field, property, or method of an object that has a name starting with an underscore does not appear in Intellisense.

Strictly speaking, these object members are not truly private. From the point of view of the JavaScript language, there is no difference between an object member named _message and an object member named message. Calling alert(obj._message) will show the value of the private _message field.

The JavaScript language does support truly private fields if you are willing to do some work (see http://www.crockford.com/javascript/private.html).

Next, the public methods and properties of the class are defined by specifying a prototype object for the class. The following code causes all MyClass objects to include a property named message and two methods named sayMessage() and yellMessage():

MyClass.prototype =
{
    get_message: function()
    {
        return this._message;
    },

    set_message: function(value)
    {
       this._message = value;
    },

    sayMessage: function()
    {
        alert(this._message);
    },

    yellMessage: function()
    {
        alert(this._message + '!'),
    }
};

Notice how you define a property. The message property is defined by creating a set_message() property setter and a get_message() property getter. The version of JavaScript included with Internet Explorer does not support true properties. So, properties are simulated by creating setter and getter methods.

Note

Getters and setters were added to the JavaScript language in version 1.5. Because Internet Explorer does not support this feature, the getters and setters must be simulated with methods.

The two methods are declared in a straightforward manner. Both the sayMessage() and yellMessage() methods access the private _message field. The sayMessage() method displays the message in a JavaScript alert. The yellMessage() method tacks an exclamation mark at the end of the message before displaying it.

Finally, the class is registered with the ASP.NET AJAX Framework with the following call to the registerClass() method:

MyClass.registerClass('MyClass'),

This syntax here is a little odd. Notice that the registerClass() method is called on the class itself and that the name of the class is passed to the method. It all seems very circular. How can MyClass have a registerClass() method when it hasn’t even been registered yet?

It all makes sense when you realize that MyClass is actually a function. Remember, MyClass is the constructor function for creating MyClass objects. The AJAX Library extends the JavaScript Function object so that it includes a registerClass() method. Therefore, the registerClass() statement isn’t really circular because the registerClass() method of the Function object is being called.

Note

The registerClass() method is a method of the Type object. However, the Type object is simply an alias for the built-in JavaScript Function object. In the AJAX Library, this is accomplished with the following lines of code:

// Alias Function as Type
window.Type = Function;

After you define a class, you can use it in pages and other external JavaScript files (client libraries). The page in Listing 33.6 creates an instance of the MyClass class in the pageLoad handler, assigns a value to the message property, and calls the yellMessage() method.

Example 33.6. ShowMyClass.aspx

<%@ Page Language="C#" %>
<!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>Show MyClass</title>
    <script type="text/javascript">
      function pageLoad()
      {
        var obj = new MyClass();
        obj.set_message("Good Day");
        obj.yellMessage();
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/MyClass.js" />
        </Scripts>
        </asp:ScriptManager>
    </div>
    </form>
</body>
</html>

When using the MyClass object, you get full Intellisense. The private _message field is hidden and the public methods are displayed (see Figure 33.6).

Viewing MyClass members.

Figure 33.6. Viewing MyClass members.

Working with Inheritance

If you use the registerClass() method to simulate creating .NET classes on the client, you can take advantage of something that resembles class inheritance. You can create a derived class that inherits from a base class. The derived class can override and extend the properties and methods of the base class.

For example, the client library in Listing 33.7 includes a Product class and a Laptop class. The Laptop class inherits from the Product class and overrides two of the base class properties.

Example 33.7. Product.js

/// <reference name="MicrosoftAjax.js"/>

// Define Computer
var Computer = function()
{
    this._price = 500.00;
};

Computer.prototype =
{
    get_name: function()
    {
        return "Computer";
    },

    get_price: function()
    {
        return this._price;
    }
};

Computer.registerClass("Computer");

// Define Laptop
var Laptop = function()
{
    Laptop.initializeBase(this);
};

Laptop.prototype =
{
    // Overrides base method
    get_name: function()
    {
        return "Laptop";
    },

    // Override and extend base method
    get_price: function()
    {
        return Laptop.callBaseMethod(this, "get_price") * 2;
    }
};
Laptop.registerClass("Laptop", Computer);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded()

The Computer class (the base class) is registered with the following statement:

Computer.registerClass("Computer");

The Laptop class (the derived class) is registered with the following statement:

Laptop.registerClass("Laptop", Computer);

The second parameter passed to the registerClass() method represents a base class. This register statement causes the Laptop class to inherit from the Product class.

Notice that the Laptop class includes a call to initializeBase() in its constructor function. This call is necessary to initialize the fields in the base class constructor function. In this case, if you neglect to call the initializeBase() method, the _price field won’t be initialized.

The Laptop class contains two properties that override properties of the base Product class. The first property simply overrides the get_name() property. The second property, the get_price() property, also overrides the base property. However, this property calls the base property with the callBaseMethod() method so that the base price can be doubled (laptop computers always cost double the price of a normal computer).

The page in Listing 33.8 illustrates how you can use the Laptop class.

Example 33.8. ShowProduct.aspx

<%@ Page Language="C#" %>
<!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>Show Product</title>
    <script type="text/javascript">

      function pageLoad()
      {
        var myLapTop = new Laptop();
        var message = String.format("The {0} costs {1}",
            myLapTop.get_name(), myLapTop.get_price().localeFormat("c"));
        alert( message );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Product.js" />
        </Scripts>
        </asp:ScriptManager>
    </div>
    </form>
</body>
</html>

In the pageLoad() method, an instance of the Laptop class is created. Next, the static String.Format() method is used to build a string from the name and price properties of Laptop. Finally, the string is displayed in a JavaScript alert.

Note

The Microsoft AJAX Library also supports creating interfaces and enumerations. To learn more, see the Microsoft .NET Framework SDK documentation.

Working with Namespaces

In the .NET Framework, namespaces are used to group related classes. For example, all the classes related to working with the file system are located in the System.IO namespace. This is done for the purposes of documentation and to prevent naming collisions. If a class appears in the System.IO namespace, you know that is has something to do with file access. Different namespaces can have classes with the very same name.

JavaScript does not explicitly support namespaces. The AJAX Library extends the JavaScript language so that you can simulate namespaces. For example, the page in Listing 33.9 registers a new namespaces named MyNamespace and adds a class named MyClass to the MyNamespace.

Example 33.9. Namespaces.js

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("MyNamespace");

MyNamespace.MyClass = function()
{
  this._message = "Fantastic!";
};
MyNamespace.MyClass.prototype =
{
    sayMessage: function()
    {
        alert(this._message);
    }
};

MyNamespace.MyClass.registerClass("MyNamespace.MyClass");

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

The new namespace is created by calling the static Type.registerNamespace() method. Notice that when the class is registered with the registerClass() method, the fully qualified name of the class is used (MyNamespace.MyClass instead of MyClass).

The page in Listing 33.10 demonstrates how you can use the MyNamespace namespace when working with a class.

Example 33.10. ShowNamespaces.aspx

<%@ Page Language="C#" %>
<!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>Show Namespaces</title>
    <script type="text/javascript">

      function pageLoad()
      {
        var myClass = new MyNamespace.MyClass();
        myClass.sayMessage();
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Namespaces.js" />
        </Scripts>
        </asp:ScriptManager>
    </div>
    </form>
</body>
</html>

In Listing 33.10, an instance of the MyClass class is created in the pageLoad() method. The instance of the class is created by using the namespace-qualified name of the class: MyNamespace.MyClass().

Retrieving DOM Elements

One of the most common operations you perform when building Ajax applications is retrieving DOM elements. For example, you might need to grab a span element from a page and modify its innerHTML. In a typical JavaScript application, you would use the document.getElementById() method to grab the DOM element, like this:

var span = document.getElementById("mySpan");
span.innerHTML = "Hello World!";

The Microsoft AJAX Library introduces a shortcut method you can use instead of the document.getElementById() method. You can use the $get() method, like this:

var span = $get("mySpan");
span.innerHTML = "Hello World!";

Alternatively, if you want to write really condensed code, you can use the $get() method, like this:

$get("mySpan").innerHTML = "Hello World!";

When calling $get(), you can pass a second parameter that represents the DOM element to search. For example, the following statement returns the mySpan element contained in the myDiv element:

var myDiv = $get("myDiv");
$get("mySpan", myDiv).innerHTML = "Hello World!";

Be careful when calling either $get() or document.getElementById() because they are expensive operations in terms of performance. It is better to use $get() to grab a reference to a DOM element and assign it to a variable once than to use $get() multiple times to work with the same DOM element.

Note

The Prototype framework (a popular, non-Microsoft AJAX framework) first introduced the $() function as an alias for document.getElementById(). Originally, Microsoft used $() instead of $get() as well. However, Microsoft wanted developers to be able to mix Prototype applications with Microsoft AJAX applications so it changed the name of the function to $get().

Handling DOM Events

One of the biggest pains associated with writing client-side applications has to do with handling DOM events (for example, handling a client-side Button click event). The problem is that Microsoft Internet Explorer does not follow W3C standards. Therefore, you always end up writing a lot of extra code to make sure your application works with Internet Explorer and standards-compliant browsers such as Firefox and Opera.

The Microsoft AJAX Library provides an abstraction layer that smoothes over the difference between browsers. You handle a DOM event either with the $addHandler() shortcut or the $addHandlers() shortcut.

For example, the page in Listing 33.11 contains an <input type="button"> element. In the pageLoad() method, the doSomething method is wired to the Button click event with the help of the $addHandler() shortcut. The doSomething() method simply displays an alert box with the message “Hello World!”.

Example 33.11. ShowAddHandler.aspx

<%@ Page Language="C#" %>
<!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>Show AddHandler</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler( $get("btn"), "click", doSomething);

        Sys.Application.add_disposing(appDispose);
      }

      function doSomething()
      {
        alert("Hello World!");
      }

      function appDispose()
      {
        $removeHandler($get("btn"), "click", doSomething);
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <input id="btn" type="button" value="Do Something" />

    </form>
</body>
</html>

The $addHandler() shortcut accepts three parameters: the DOM element, the name of the event to handle, and a reference to the method to execute.

If you need to wire up multiple event handlers to the same element, you can use the $addHandlers() method. The $addHandlers() method accepts a list of event handlers for its second parameter. For example, the page in Listing 33.12 handles a <div> element’s mouseover and mouseout events. The background color of the <div> element changes from white to yellow (see Figure 33.7).

Handling the mouseover and mouseout events.

Figure 33.7. Handling the mouseover and mouseout events.

Example 33.12. ShowAddHandlers.aspx

<%@ Page Language="C#" %>
<!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>Show AddHandlers</title>
    <style type="text/css">
        .glow
        {
            background-color:yellow;
        }
    </style>
    <script type="text/javascript">

      var divHover;

      function pageLoad()
      {
        divHover = $get("divHover");
        $addHandlers( divHover, {mouseover:addGlow, mouseout:removeGlow} );

        Sys.Application.add_disposing(appDispose);
      }

      function addGlow()
      {
        Sys.UI.DomElement.addCssClass(divHover, "glow");
      }

      function removeGlow()
      {
        Sys.UI.DomElement.removeCssClass(divHover, "glow");
      }

      function appDispose()
      {
        $clearHandlers( divHover );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <div id="divHover">

        <h1>Hover Here!</h1>

    </div>
    </form>
</body>
</html>

When you are wiring up DOM event handlers, it is important to unwire the event handlers when you are done using them. Unfortunately, Internet Explorer exhibits bad memory leaks when you create references between JavaScript objects and DOM objects. You can avoid these memory leaks by calling either the $removeHandler or $clearHandlers() shortcut method.

In Listing 33.11, the $removeHandler() method is used to remove the doSomething() handler from the Button click event. In Listing 33.12, the $clearHandlers() method is used to remove all the handlers from divHover’s <div> element. In both cases, the method is called within an application-disposing event handler.

Note

The application-disposing event is raised whenever the user moves from the current page. For example, disposing is raised when the user refreshes the browser, navigates to a new page, or closes the browser window.

Retrieving DOM Event Information

When you are writing a normal JavaScript application, retrieving information about an event is just as difficult as wiring up a handler for a client-side event. Again, Internet Explorer represents event information in a completely different way than Firefox or Opera. The Microsoft AJAX Library, once again, enables you to smooth over the differences between the different browsers.

If you create an event handler with the Microsoft AJAX Library, event information is passed to the event handler as a parameter. In particular, an instance of the Sys.UI.DomEvent class is passed to the event handler.

The Sys.UI.DomEvent class has a number of useful properties:

  • altKeyReturns true when the Alt key is pressed.

  • buttonReturns a value from the Sys.UI.MouseButton enumeration: leftButton, middleButton, or rightButton.

  • charCodeReturns the code for the key pressed that raised the event. Use the Sys.UI.Key enumeration to compare the charCode against the particular types of keys, such as the Backspace, Tab, and Enter.

  • clientXReturns the horizontal position of the mouse relative to the client area of the browser window, excluding the scroll bars.

  • clientYReturns the vertical position of the mouse relative to the client area of the browser window, excluding the scroll bars.

  • ctrlKeyReturns true when the Ctrl key is pressed.

  • offsetXReturns the horizontal position of the mouse relative to the element that raised the event.

  • offsetYReturns the vertical position of the mouse relative to the element that raised the event.

  • screenXReturns the horizontal position of the mouse relative to the entire screen.

  • screenYReturns the vertical position of the mouse relative to the entire screen.

  • shiftKeyReturns true when the Shift key is pressed.

  • targetReturns the original element that raised the event (as distinct from the element associated with the event handler).

  • typeReturns the name of the event (for example, click).

The Sys.UI.DomEvent class also has two useful methods:

  • preventDefaultStops the default action associated with the event.

  • stopPropagationStops the event from bubbling up to its parent element.

The preventDefault method is especially useful. If you want to prevent the default event associated with performing an action, then you can call this method to cancel it. For example, if you add a <button> tag to a page, and you don’t want a postback to happen when you click the button, then call the preventDefault() method.

The page in Listing 33.13 illustrates how you can use the DomEvent class to determine the target of an event. The page contains a simple menu of food items. When you click an item, the selected item appears in a <span> tag (see Figure 33.8).

Selecting a menu item.

Figure 33.8. Selecting a menu item.

Each menu item is contained in a <span> tag. The <span> tags are contained in a <div> tag. A single event handler is wired up to the <div> tag’s click event. When you click a menu item, the selected menu item is detected with the help of the DomEvent class’s target property.

Example 33.13. ShowDOMEvent.aspx

<%@ Page Language="C#" %>
<!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>Show DOM Event</title>
    <style type="text/css">
        #divMenu
        {
            padding: 5px;
        }
        #divMenu span
        {
            margin: 4px;
            padding: 3px;
            border:solid 1px black;
            background-color: #eeeeee;
        }
    </style>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler( $get("divMenu"), "click", selectMenuItem );
        Sys.Application.add_disposing(appDispose);
      }

      function selectMenuItem(e)
      {
        if (e.target.tagName === "SPAN")
            $get("spanSelection").innerHTML = e.target.innerHTML;
      }

      function appDispose()
      {
        $clearHandlers( $get("divMenu") );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <div id="divMenu">

        <span>Cheeseburger</span>

        <span>Milkshake</span>

        <span>French fries</span>

    </div>

    <br /> You selected:
    <span id="spanSelection"></span>

    </form>
</body>
</html>

Creating Callbacks and Delegates

In the previous sections, you learned how to handle DOM events and retrieve event information by using the $addHandler() method. This method of wiring up an event handler, however, has a serious limitation: It doesn’t enable you to pass additional information to the event handler.

The ASP.NET AJAX Library includes two methods you can use to pass additional information to event handlers:

  • Function.createCallback(method, context)

  • Function.createDelegate(instance, method)

Calling the createCallback() method creates a method that passes an additional context parameter to an event handler. You can pass anything you want as the context parameter. For example, the context parameter could be a simple string or it could be a reference to a component.

Calling the createDelegate() method does not pass any additional parameters to the event handler. However, it changes the meaning of this in the handler. Normally, if you use this in an event handler, it refers to the DOM element that raised the event. The createDelegate() method enables you to change this to refer to anything you like.

The difference between createCallback() and createDelegate() is illustrated by the page in Listing 33.14 (see Figure 33.9). The page contains four buttons, named btnHandler, btnCallback, btnDelegate, and btnHandlers. The page also contains four event handlers, named doSomething1(), doSomething2(), doSomething3(), and doSomething4(). Different parameters are passed to the different event handlers, depending on how the handlers are wired up to the buttons.

Executing a callback handler.

Figure 33.9. Executing a callback handler.

Example 33.14. ShowDelegates.aspx

<%@ Page Language="C#" %>
<!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>Show Delegates and Callbacks</title>
    <script type="text/javascript">

      function pageLoad()
      {
        // Use $addHandler
        $addHandler($get("btnHandler"), "click", doSomething);

        // Use createCallback
        var callback = Function.createCallback(doSomething2, "apple");
        $addHandler($get("btnCallback"), "click", callback);

        // Use createDelegate
        var delegate = Function.createDelegate("apple", doSomething3);
        $addHandler($get("btnDelegate"), "click", delegate);

        // Use $addHandlers
        $addHandlers($get("btnHandlers"), {click: doSomething4}, "apple");
      }

      function doSomething(event)
      {
        alert( [this.id, event.type] );
      }
      function doSomething2(event, context)
      {
        alert( [this.id, event.type, context] );
      }

      function doSomething3(event)
      {
        alert( [this, event.type] );
      }

      function doSomething4(event)
      {
        alert( [this, event.type] );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <input id="btnHandler" type="button" value="Handler" />
    <input id="btnCallback" type="button" value="Callback" />
    <input id="btnDelegate" type="button" value="Delegate" />
    <input id="btnHandlers" type="button" value="Handlers" />

    </form>
</body>
</html>

If you click either of the first two buttons, then this refers to the button clicked. If you click either of the second two buttons, then this refers to the string “apple” since “apple” was passed to the createDelegate() method and the $addHandlers() shortcut.

Notice that the $addHandlers() shortcut, when you pass a final argument that represents the context, does the same thing as calling the createDelegate() method. The advantage of $addHandlers() is that you can use this shortcut to wire up multiple methods to events at a time.

In real-world scenarios, you won’t change this to refer to a string; you’ll change this to refer to the client-side control or behavior associated with the event handler. Within an event handler, you’ll want to refer to the properties and methods of the client-side control or behavior associated with the event.

Debug and Release AJAX Libraries

Two versions of the MicrosoftAjax.js file contain the AJAX Library: the release version and the debug version. The release version of the library is minimized to its smallest possible size. All inessential code and comments have been stripped.

The debug version, on the other hand, is quite readable. It contains comments on each of the methods. Furthermore, the debug version contains additional code intended to inform you when you are misusing a method. When you call a method using the release version of the library, all the parameters passed to the method are validated (the type and number of parameters are validated).

You will want to use the release version of the AJAX Library for a production application and the debug version only while actively developing an application. The easiest way to switch between the release and debug versions of the script is to switch between debug and release mode in the web configuration file. The same setting you use to control whether server-side code is compiled in debug or release mode controls whether the debug or release version of the AJAX Library is served.

To switch to debug mode, find or add the compilation element in the system.web section of the web.config file and assign the value true to its debug attribute, like this:

<compilation debug="true">

Alternatively, you can control whether the debug or release version of the AJAX Library is used by modifying the ScriptMode property of the ScriptManager control. This property has the following four possible values:

  • AutoThis is the default value. Use the compilation setting from the Web.config file.

  • DebugUse debug scripts.

  • InheritSame as Auto.

  • ReleaseUse release scripts.

Declaring a ScriptManager control in a page like this forces the debug version of the AJAX Library to be used:

<asp:ScriptManager ID="ScriptManager1" ScriptMode="Debug" runat="server" />

Debugging Microsoft AJAX Applications

The last topic we need to examine in this section is debugging. There are two ways that you can debug a client-side ASP.NET AJAX application: You can display trace messages, and you can use the Visual Web Developer debugger.

The Microsoft AJAX Library includes a Sys.Debug class. You can use this class to output trace messages and break into the Visual Web Developer debugger. The Sys.Debug class supports the following methods:

  • assertEnables you to evaluate a JavaScript expression. If the expression returns false, a message is displayed in the debugger and the trace console and you are prompted to break into the debugger.

  • clearTraceEnables you to clear all messages from the trace console.

  • failEnables you to display a message in the debugger and the trace console and break into the debugger.

  • traceEnables you to output a message to the debugger and trace console.

  • traceDumpEnables you to dump all properties of an object to the debugger and trace console.

These methods output trace messages to two different consoles. The debugger console is the Visual Web Developer debugger console that appears when you execute an application in debug mode. Right-click a page in the Solution Explorer window and select the menu option Set as Start Page. Select the menu option Debug, Start Debugging (F5), and the messages will appear in the debugger console Output window (see Figure 33.10).

Viewing trace messages in the console’s Output window.

Figure 33.10. Viewing trace messages in the console’s Output window.

The trace console works in different ways, depending on whether you are using Microsoft Internet Explorer or Mozilla Firefox. If you are using Firefox and you have the Firebug extension installed, trace messages sent to the trace console appear in the Firebug console (see Figure 33.11).

Viewing trace output in the Firebug console.

Figure 33.11. Viewing trace output in the Firebug console.

If you are using Internet Explorer and you want to view messages sent to the trace console, you need to add a <textarea> element to your page with the ID TraceConsole. All trace messages will be sent to this text area.

The page in Listing 33.15 demonstrates how to use the trace() and traceDump() methods.

Example 33.15. ShowDebug.aspx

<%@ Page Language="C#" %>
<!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>
    <style type="text/css">
        #TraceConsole
        {
            display:none;
        }
    </style>
    <!—[if IE]>
    <link rel="Stylesheet" type="text/css" href="TraceConsole.css" />
    <![endif]—>

    <script type="text/javascript">

      function pageLoad()
      {
        Sys.Debug.trace("Starting trace");
        Sys.Debug.traceDump( Sys.UI.DomEvent );
      }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <textarea id="TraceConsole"></textarea>
    </div>
    </form>
</body>
</html>

The page in Listing 33.15 hides the TraceConsole <textarea> by default. However, it includes an Internet Explorer conditional comment. If you are using Internet Explorer, an external style sheet named TraceConsole is loaded. This style sheet displays the <textarea> and gives the console a fixed position at the bottom of the page (so that the Internet Explorer trace console will kind of look like the Firebug console, as shown in Figure 33.12).

Viewing trace messages in a fake Internet Explorer console.

Figure 33.12. Viewing trace messages in a fake Internet Explorer console.

You can also use the Visual Web Developer debugger when debugging ASP.NET AJAX applications. You can use the debugger to set breakpoints and step through your JavaScript code, line by line, just like you do for your C# code. You can set breakpoints both on a page and in external JavaScript files.

In order for debugging to work, you need to set two properties of Internet Explorer. Select the menu option Tools, Internet Options and then select the Advanced Tab. Make sure the option Disable Script Debugging (Internet Explorer) and the option Disable Script Debugging (Other) are both unchecked.

You can use either the Sys.Debug.assert() method or the Sys.Debug.fail() method to break into the debugger while running your JavaScript code. Alternatively, you can set breakpoints by double-clicking in the left-hand gutter of the main window in Source view (see Figure 33.13).

Setting breakpoints in Visual Web Developer.

Figure 33.13. Setting breakpoints in Visual Web Developer.

Calling Web Services from the Client

The heart of Ajax is the ability to send and retrieve information from the web server without needing to post a page back to the web server. Ajax is all about performing “sneaky” postbacks.

The vision behind a pure Ajax application is that it should consist of a single page. All updates to the single page after it has been loaded should be performed by calling web services. You should never need to perform a postback because any postback results in a bad user experience (the page jumps and the universe freezes).

The ASP.NET AJAX Library provides support for calling web services directly from the client (the web browser). In this section, you learn two methods of exposing a web method to an AJAX page. You learn how to call a web method from a separate web service, and you learn how to call a web method exposed by the page itself. Finally, we examine three specialized web services exposed by the ASP.NET AJAX Framework: the Authentication service, the Role service, and the Profile service.

Calling an External Web Service

Let’s start simple. We’ll create a Quotation web service that randomly returns a quotation from a list of quotations. Next, we’ll create an AJAX page that contains a button. When you click the button, a random quotation will be displayed in a <span> tag (see Figure 33.14).

Retrieving a random quotation from the server.

Figure 33.14. Retrieving a random quotation from the server.

The first step is to create the web service. The web service is contained in Listing 33.16.

Example 33.16. QuotationService.asmx

<%@ WebService Language="C#" Class="QuotationService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Collections.Generic;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class QuotationService : System.Web.Services.WebService
{

    [WebMethod]
    public string GetQuote()
    {
        List<string> quotes = new List<string>();
        quotes.Add("The fool who is silent passes for wise.");
        quotes.Add("The early bird catches the worm.");
        quotes.Add("If wishes were true, shepherds would be kings.");
        Random rnd = new Random();
        return quotes[rnd.Next(quotes.Count)];
    }
}

You create the file in Listing 33.16 by selecting the menu option Website, Add New Item and choosing the Web Service item.

The web service contains a single web method named GetQuote(). This method returns a single quotation from a list of quotations as a string.

There is only one thing special about this web service. Notice that a ScriptService attribute is applied to the web service class (the ScriptService attribute lives in the System.Web.Script.Services namespace). You must add this attribute in order to call the web service from an AJAX page.

Now that we have created the web service, we can call it from an AJAX page. The page in Listing 33.17 calls the web service in order to display a random quotation.

Example 33.17. ShowWebServiceMethod.aspx

<%@ Page Language="C#" %>
<!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>Show Web Service Method</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler( $get("btnGet"), "click", getQuote );
      }

      function getQuote()
      {
        QuotationService.GetQuote(getQuoteSuccess, getQuoteFail);
      }

      function getQuoteSuccess(result)
      {
        $get("spanQuote").innerHTML = result;
      }

      function getQuoteFail(error)
      {
        alert(error.get_message());
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Services>
            <asp:ServiceReference
                InlineScript="true"
                Path="~/Services/QuotationService.asmx" />
        </Services>
        </asp:ScriptManager>

        <input id="btnGet" type="button" value="Get Quote" />
        <br /><br />
        <span id="spanQuote"></span>

    </div>
    </form>
</body>
</html>

You should note several things in this page. To begin, notice that the ScriptManager control contains a <Services> element that includes a reference to the QuotationService web service. Adding this reference causes a proxy class for the QuotationService web service to be generated automatically.

The ScriptReference control has two properties: Path and InlineScript. You use the Path property to provide the path to the web service. The InlineScript property accepts the value true or false. When the value is true, the web service proxy is added inline to the page. When the value is false, a separate request must be made to grab the proxy class from the server (the browser can cache the proxy and use it for multiple pages). If you plan to use the web service in only one page, you should assign the value true to InlineScript. If you plan to use the web service in multiple pages, you should assign the value false.

Warning

You can only call web services located in the same domain as the AJAX page. You can’t call a web service located in another domain.

In the pageLoad() method, a button is wired up to the getQuote() method. When you click the button, the getQuote() method executes and calls the remote web service. The web service is called with the following line of code:

QuotationService.GetQuote(getQuoteSuccess, getQuoteFail);

The QuotationService class is the proxy class that is generated automatically by the ServiceReference included the ScriptManager control. The proxy class includes a method that corresponds to each of the remote web methods exposed by the web service. In this case, the getQuote() method is called.

When you call a web method, you can pass a reference to both a success and a fail method. If the web method call is successful, the success method is called. Otherwise, the fail method is called.

In Listing 33.17, if the getQuote() method is successful, the quotation is displayed in a <span> tag in the body of the page. This is accomplished with the getQuoteSuccess() method:

function getQuoteSuccess(result)
{
  $get("spanQuote").innerHTML = result;
}

The parameter passed to getQuoteSuccess() method represents whatever was returned by the web service. In this case, the result is a string that represents the quotation.

If the call to the web service fails, the following getQuoteFailure() method is called:

function getQuoteFail(error)
{
  alert(error.get_message());
}

This method simply displays an alert with the error message. The error parameter is an instance of the Sys.Net.WebServiceError class. This class supports the following properties:

  • exceptionTypeReturns a string representation of the exception type.

  • messageReturns the error message from the server.

  • stackTraceReturns the stack trace from the server.

  • statusCodeReturns an HTTP status code (for example, 500 for an Internal Server Error).

  • timedOutReturns true if the call to the web service timed out.

Calling a Static Page Method

If you are not planning to call a web method from multiple pages, there is no reason to perform all the work of creating a separate web service. Instead, you can expose a static method from the same AJAX page that is calling the web method.

For example, the page in Listing 33.18 includes a server method named GetQuote(). The server GetQuote() method is called by a client method named GetQuote().

Example 33.18. ShowPageMethod.aspx

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    [System.Web.Services.WebMethod]
    public static string GetQuote()
    {
        List<string> quotes = new List<string>();
        quotes.Add("The fool who is silent passes for wise.");
        quotes.Add("The early bird catches the worm.");
        quotes.Add("If wishes were true, shepherds would be kings.");
        Random rnd = new Random();
        return quotes[rnd.Next(quotes.Count)];
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Page Method</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler( $get("btnGet"), "click", getQuote );
      }

      function getQuote()
      {
       PageMethods.GetQuote(getQuoteSuccess, getQuoteFail);
      }

      function getQuoteSuccess(result)
      {
        $get("spanQuote").innerHTML = result;
      }

      function getQuoteFail(error)
      {
        alert(error.get_message());
      }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager
        ID="ScriptManager1"
        EnablePageMethods="true"
        runat="server" />

    <input id="btnGet" type="button" value="Get Quote" />
    <br /><br />
    <span id="spanQuote"></span>
    </form>
</body>
</html>

You must do one special thing before you can expose web methods from a page. You must assign the value true to the ScriptManager control’s EnablePageMethods property.

In Listing 33.18, the server GetQuote() method is called with the following line of code:

PageMethods.GetQuote(getQuoteSuccess, getQuoteFail);

Just like in the previous section, when you call a page method, you can supply both a success and failure handler.

You might be wondering where the PageMethods class comes from. This class is generated in the page automatically when you expose a page method. The class will always be called PageMethods. The class will contain a proxy client method that corresponds to each server page method.

Editing Movies with AJAX

In this section, I want to present you with a more complicated (and realistic) example of calling server web methods from the client. In this section, we create a page that can be used to edit the Movie database table. The page enables you to add new movies to the database and display all the existing movies (see Figure 33.15). Both operations are performed through AJAX calls so that a postback is never necessary.

Displaying and inserting database records with AJAX.

Figure 33.15. Displaying and inserting database records with AJAX.

The EditMovies.aspx page is contained in Listing 33.19.

Example 33.19. EditMovies.aspx

<%@ Page Language="C#" %>
<!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>Edit Movies</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler($get("btnAdd"), "click", addMovie);
        bindMovies();
      }

      function bindMovies()
      {
        MovieService.SelectAll(selectAllSuccess);
      }

      function addMovie()
      {
        var movieToAdd =
            {
                Title: $get("txtTitle").value,
                Director: $get("txtDirector").value
            };

        MovieService.Insert(movieToAdd, addMovieSuccess);
      }

      function addMovieSuccess()
      {
        bindMovies();
      }

      function selectAllSuccess(results)
      {
        var sb = new Sys.StringBuilder()
        var movie;
        var row;
        for (var i=0;i < results.length; i++)
        {
            movie = results[i];
            row = String.format("{0} directed by {1}<br />",
                    movie.Title, movie.Director);
            sb.appendLine(row);
        }
        $get("divMovies").innerHTML = sb.toString();
      }
    </script>
</head>
<body>
<form runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
    <asp:ServiceReference
      InlineScript="true"
      Path="~/Services/MovieService.asmx" />
</Services>
</asp:ScriptManager>

<fieldset>
<legend>Add Movie</legend>

<label for="txtTitle">Title:</label>
<input id="txtTitle" />
<br /><br />

<label for="txtTitle">Director:</label>
<input id="txtDirector" />

<br /><br />

<input id="btnAdd" type="button" value="Add Movie" />

</fieldset>

<div id="divMovies"></div>

</form>
</body>
</html>

The page in Listing 33.19 calls an external web service named MovieService. The two web methods from MovieService are SelectAll() and Insert(). The SelectAll() method is called to get the current list of movies. This method is called when the page first loads and it is called after a new movie is inserted. The list of movies is displayed in a <div> element with the following code:

function selectAllSuccess(results)
{
  var sb = new Sys.StringBuilder()
  var movie;
  var row;
  for (var i=0;i < results.length; i++)
  {
      movie = results[i];
      row = String.format("{0} directed by {1}<br />",
         movie.Title, movie.Director);
      sb.appendLine(row);
  }
  $get("divMovies").innerHTML = sb.toString();
}

This code iterates through the list of Movie objects returned by the web service. A single string that represents all the movies is built with a StringBuilder object. Finally, the contents of the StringBuilder are displayed in a <div> tag.

The Insert() web method is called to add a new movie to the database. The body of the page contains a simple form for gathering the movie title and director. When you click the Add Movie button, the following method is called:

function addMovie()
{
  var movieToAdd =
      {
          Title: $get("txtTitle").value,
          Director: $get("txtDirector").value
      };

  MovieService.Insert(movieToAdd, addMovieSuccess);
}

The addMovie() method creates a new Movie object named movieToAdd. The movieToAdd object represents the values that the user entered into the <input> elements of txtTitle and txtDirector. Finally, the movieToAdd object is passed to the Insert() method exposed by the MovieService web service proxy.

The MovieService web service used by the page is contained in Listing 33.20.

Example 33.20. MovieService.asmx

<%@ WebService Language="C#" Class="MovieService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Web.Script.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class MovieService : System.Web.Services.WebService
{
    [WebMethod]
    public List<Movie> SelectAll()
    {
        MyDatabaseDataContext db = new MyDatabaseDataContext();
        return db.Movies.ToList();
    }

    [WebMethod]
    public int Insert(Movie movieToAdd)
    {
        MyDatabaseDataContext db = new MyDatabaseDataContext();
        db.Movies.InsertOnSubmit(movieToAdd);
        db.SubmitChanges();
        return movieToAdd.Id;
    }
}

Notice that you can pass objects back and forth between an AJAX page and a web service. The AJAX page passes a Movie object to the web service’s Insert() method. The web service’s SelectAll() method returns a collection of Movie objects. These objects are seamlessly passed back and forth between the client world and the server world.

Using the Authentication Service

The Microsoft AJAX Library includes three built-in web services. In this section, we examine the first of these built-in web services: the Authentication service.

The Authentication service works with ASP.NET Form authentication. You can use it with the ASP.NET membership framework to authenticate users using one of the ASP.NET membership providers. The two providers included with the ASP.NET framework are SqlMembershipProvider (authenticates users against a database of usernames and passwords) and ActiveDirectoryMembershipProvider (authenticates users against the Active Directory).

Before you can use the Authentication service, you need to make two configuration changes to your web configuration file. First, you need to enable Forms authentication (the default form of authentication is Windows). Find the <authentication> element in the <system.web> section and modify it to look like this:

<authentication mode="Forms"/>

Second, you need to enable the Authentication service because it is disabled by default. If your web configuration file does not already contain a <system.web.extensions> element, you will need to add one. Add it outside of the <system.web> section. The <system.web.extensions> element must contain an <authenticationService> element that looks like this:

<system.web.extensions>
    <scripting>
        <webServices>
            <authenticationService enabled="true"/>
        </webServices>
    </scripting>
</system.web.extensions>

Note

The <authenticationService> element includes a requireSSL attribute in case you want to require an encrypted connection when logging in to the server.

Finally, if you want to create one or more users, you can use the Website Administration Tool. Select the menu option Website, ASP.NET Configuration and then select the Security tab. Click the Create User link to create a new user.

Note

To learn more about the ASP.NET membership providers, see Chapter 23, “Using ASP.NET Membership.”

The page in Listing 33.21 demonstrates how you can call the Authentication service from an AJAX page. The page contains a login form that you can use to authenticate against the SqlMembershipProvider. If you log in successfully, you get to see the secret message (see Figure 33.16).

Authenticating and seeing the secret message.

Figure 33.16. Authenticating and seeing the secret message.

Note

If you want to try the ShowLogin.aspx page on the CD, a valid username and password are Steve and secret#, respectively.

Example 33.21. ShowLogin.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    [System.Web.Services.WebMethod]
    public static string GetSecretMessage()
    {
        return "Time is a fish";
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Login</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $addHandler( $get("btnLogin"), "click", login);
      }

      function login()
      {
        Sys.Services.AuthenticationService.login
            (
                $get("txtUserName").value,
                $get("txtPassword").value,
                false,
                null,
                null,
                loginSuccess,
                loginFail
            );
      }

      function loginSuccess(isAuthenticated)
      {
        if (isAuthenticated)
            PageMethods.GetSecretMessage(getSecretMessageSuccess);
        else
            alert( "Log in failed" );
      }

      function loginFail()
      {
            alert( "Log in failed" );
      }
      function getSecretMessageSuccess(message)
      {
        $get("spanMessage").innerHTML = message;
      }


    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager
        ID="ScriptManager1"
        EnablePageMethods="true"
        runat="server" />

    <fieldset>
    <legend>Login</legend>

        <label for="txtUserName">User Name:</label>
        <input id="txtUserName" />

        <br /><br />

        <label for="txtUserName">Password:</label>
        <input id="txtPassword" type="password" />

        <br /><br />
        <input id="btnLogin" type="button" value="Login" />

    </fieldset>

    The secret message is:
    <span id="spanMessage"></span>

    </form>
</body>
</html>

The page in Listing 33.21 contains a simple Login form. When you click the Login button, the login() method executes and calls the following method:

Sys.Services.AuthenticationService.login
    (
        $get("txtUserName").value,
        $get("txtPassword").value,
        false,
        null,
        null,
        loginSuccess,
        loginFail
    );

The AuthenticationService.login() method accepts the following parameters:

  • userNameThe username to authenticate.

  • passwordThe password to authenticate.

  • isPersistentDetermines whether a session or persistent cookie is created after successful authentication.

  • customInfoNot used.

  • redirectUrlThe page to which the user is redirected after successful authentication.

  • loginCompletedCallbackThe method to call when the web service call completes.

  • failedCallbackThe method to call when the web service call fails.

  • userContextAdditional information to pass to the loginCompletedCallback or failedCallback method.

If the web service call to the Authentication service is successful, the following method is called:

function loginSuccess(isAuthenticated)
{
  if (isAuthenticated)
      PageMethods.GetSecretMessage(getSecretMessageSuccess);
  else
      alert( "Log in failed" );
}

It is important to understand that this method is called both when the user is successfully authenticated and when the user is not successfully authenticated. This method is called when the Authentication web service call is successful.

The first parameter passed to the method represents whether or not the user is successfully authenticated. If the user is authenticated, the secret message is grabbed from the server and displayed in a <span> element in the body of the page. Otherwise, a JavaScript alert is displayed that informs the user that the login was a failure.

Warning

Don’t ever put secret information in your JavaScript code. Anyone can always view all your JavaScript code. If you have secret information that you only want authenticated users to view, don’t retrieve the information from the server until the user is authenticated successfully.

Notice that we were not required to create the Authentication web service. The Authentication web service is built in to the AJAX framework.

Using the Role Service

The second of the built-in application services included with ASP.NET AJAX is the Role service. This service enables you to retrieve the list of roles associated with the current user.

For example, you might want to display different content and provide different functionality to different users depending on their roles. A member of the Administrators role can edit a record, but a member of the Public role can only view a record.

To use the Role service, you need to make two configuration changes to your web configuration file. First, you need to enable the Role service. You can enable the Role service by adding the following <roleService> element to the <system.web.extensions> section of your web.config file:

<system.web.extensions>
    <scripting>
        <webServices>
            <authenticationService enabled="true"/>
            <roleService enabled="true"/>
        </webServices>
    </scripting>
</system.web.extensions>

Second, you need to enable ASP.NET roles. You enable roles by adding the following element to the <system.web> section of your web configuration file:

<roleManager enabled="true" />

After you make these configuration changes, roles are enabled for both the server and the client. You can create new roles, as well as associate the roles with users, by using the Website Administration Tool. Select the menu option Website, ASP.NET Configuration and then select the Security tab. Click the Create or Manage Roles link to create a new role and associate it with a user.

Now that we have everything configured, we can create an AJAX page that takes advantage of the Role service. The page in Listing 33.22 displays whether or not a user is a member of the Painters and Plumbers roles.

Example 33.22. ShowRoles.aspx

<%@ Page Language="C#" %>
<!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>Show Roles</title>
    <script type="text/javascript">

      function pageLoad()
      {
        Sys.Services.AuthenticationService.login
            (
                "Steve",
                "secret#",
                false,
                null,
                null,
                loginSuccess
            );
      }

      function loginSuccess(isAuthenticated)
      {
        if (isAuthenticated)
            loadRoles();
        else
           alert("Log in failed!");
      }

      function loadRoles()
      {
        Sys.Services.RoleService.load(loadRolesSuccess, loadRolesFail);
      }

      function loadRolesSuccess()
      {
        var isPlumber = Sys.Services.RoleService.isUserInRole("Plumber");
        $get("spanPlumber").innerHTML = isPlumber;

        var isPainter = Sys.Services.RoleService.isUserInRole("Painter");
        $get("spanPainter").innerHTML = isPainter;
      }

      function loadRolesFail(error)
      {
        alert("Could not load roles!");
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        Is Plumber: <span id="spanPlumber"></span>

        <br /><br />

        Is Painter: <span id="spanPainter"></span>

    </div>
    </form>
</body>
</html>

In the pageLoad() method in Listing 33.22, the Authentication service is called to authenticate a user named Steve. When the Authentication web service call completes, and the user is authenticated successfully, the following loadRoles() method is called:

function loadRoles()
{
  Sys.Services.RoleService.load(loadRolesSuccess, loadRolesFail);
}

The RoleService.load() method loads all the roles for the current user. The method accepts two parameters: the method to call if the web service call is successful, and the method to call if the web service call fails.

If the RoleService.load() method completes successfully, the following method is called:

function loadRolesSuccess()
{
  var isPlumber = Sys.Services.RoleService.isUserInRole("Plumber");
  $get("spanPlumber").innerHTML = isPlumber;

  var isPainter = Sys.Services.RoleService.isUserInRole("Painter");
  $get("spanPainter").innerHTML = isPainter;
}

This method uses the RoleService.isUserInRole() method to detect whether the current user is a member of the Plumber and Painter roles. This information is displayed in two <span> tags contained in the body of the page (see Figure 33.17).

Displaying a user’s roles.

Figure 33.17. Displaying a user’s roles.

The RoleService class also includes a roles property. You can call Sys.Services.RoleService.get_roles() to get a list of all roles associated with the current user.

Using the Profile Service

The final built-in application service we need to discuss is the Profile service. The Profile service enables you to store information associated with a user across multiple visits to a web application. You can use the Profile service to store any type of information you need. For example, you can use the Profile service to store a user shopping cart.

Note

We discussed the ASP.NET Profile object in Chapter 24, “Maintaining Application State.”

Before you use the Profile service, you must enable it for both the server and the client. On the server side, you need to enable and define the profile. The following <profile> element, which you should add to the <system.web> section of the web.config file, enables the Profile object and defines two properties named pageViews and backgroundColor:

<anonymousIdentification enabled="true"/>
<profile enabled="true">
<properties>
       <add
             name="pageViews"
             type="Int32"
             defaultValue="0"
             allowAnonymous="true" />
       <add
             name="backgroundColor"
             defaultValue="yellow"
             allowAnonymous="true" />
</properties>
</profile>

Notice the <anonymousIdentification> element. This element causes anonymous users to be tracked by a cookie. If you don’t include the <anonymousIdentification> element, only authenticated users can modify profile properties. Both the pageViews and backgroundColor properties include an allowAnonymous="true" property so that anonymous users can modify these properties.

If you want to restrict the profile to authenticated users, remove the <anonymousIdentification> element. In that case, you will need to use the Authentication service to authenticate a user before you modify the user’s profile properties.

You must perform an additional configuration step on the server to enable an AJAX page to access a user profile. You must add a <profileService> element to the <system.web.extensions> section of the web configuration file, like this:

<system.web.extensions>
    <scripting>
        <webServices>
            <profileService
                enabled="true"
                readAccessProperties="pageViews,backgroundColor"
                writeAccessProperties="pageViews" />
        </webServices>
    </scripting>
</system.web.extensions>

You must enable the Profile service before you can use it. It is disabled by default.

Notice that the <profileService> element includes both a readAccessProperties and a writeAccessProperties attribute. These attributes are used to expose a comma-separated list of Profile properties that can be read and written from the client. You can read the backgroundColor profile property, but you cannot modify it. The pageViews property, on the other hand, can be read and modified.

Now that we have the profile configured on the server, we can use it in an AJAX page. The page in Listing 33.23 keeps count of the number of times a particular user has requested the page. Each time a user requests the page, the count increments by one and the total count is displayed in the body of the page (see Figure 33.18).

Updating page views with the Profile service.

Figure 33.18. Updating page views with the Profile service.

Furthermore, the page in Listing 33.23 is displayed using the background color defined in the profile. If you modify the profile background color, the page background color is also modified.

Example 33.23. ShowProfile.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html id="html1" xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Profile</title>
    <script type="text/javascript">

      function pageLoad()
      {
        // Increment page views
        Sys.Services.ProfileService.properties.pageViews ++;

        // Save profile
        Sys.Services.ProfileService.save(["pageViews"], saveSuccess);

        // Show page views
        $get("spanPageViews").innerHTML =
           Sys.Services.ProfileService.properties.pageViews;

        // Change background color
        var backgroundColor = Sys.Services.ProfileService.properties ["backgroundColor"];
        $get("html1").style.backgroundColor = backgroundColor;
      }

      function saveSuccess(countOfPropertiesSaved)
      {
        Sys.Debug.trace("Profile properties saved: " + countOfPropertiesSaved);
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <ProfileService
                LoadProperties="pageViews,backgroundColor" />
        </asp:ScriptManager>

        Your total page views:
        <span id="spanPageViews"></span>
    </div>
    </form>
</body>
</html>

The page in Listing 33.23 loads the current user’s profile properties when the page first loads. Notice that the ScriptManager control includes a <ProfileService> sub-element. The <ProfileService> sub-element includes a LoadProperties attribute that contains a comma-separated list of profile properties to load when the page loads.

Note

If you want to load profile properties after a page loads, you can use the Sys.Services.ProfileService.load() method to load them through a web service call.

The pageLoad method increments the total page views with the following line of code:

Sys.Services.ProfileService.properties.pageViews ++;

Notice that you can access profile properties on the client just like you can on the server. Profile properties are automatically exposed through the Sys.Services.ProfileService.properties property. You even get Intellisense for the client-side profile properties.

After the pageViews profile property is modified, it is saved back to the server with the following line of code:

Sys.Services.ProfileService.save(["pageViews"], saveSuccess);

The first parameter to the ProfileService.save() method represents a JavaScript array of profile property names to save back to the server. The second parameter is a method that gets called when the web service call completes.

Note

As an alternative to supplying a list of property names to the ProfileService.save() method, you can pass the value null. When you pass the value null, all profile properties are saved.

The saveSuccess() method gets called when the profile properties are saved to the server. This method looks like this:

function saveSuccess(countOfPropertiesSaved)
{
  Sys.Debug.trace("Profile properties saved: " + countOfPropertiesSaved);
}

A count of the profile properties that got saved on the server successfully is passed to this method. The saveSuccess() method simply writes the count to the debug and trace console.

After updating the total page views, the pageLoad method displays the page views in a <span> element in the body of the page. Finally, the pageLoad() method changes the background color of the page to the value of the profile backgroundColor property.

Creating Custom AJAX Controls and Behaviors

In this final part of this chapter, you learn how to create custom AJAX controls and behaviors. AJAX controls and behaviors are some of the most exciting features of the Microsoft AJAX Framework. Unfortunately, they are also some of the features that are least developed.

In the following sections, you learn how to create custom AJAX controls and behaviors. You also learn how to launch AJAX controls and behaviors from server-side controls.

Creating AJAX Controls

In the Ajax world, an AJAX control is the equivalent of an ASP.NET control. However, whereas an ASP.NET control executes on the server and renders content to the browser, an AJAX control executes entirely in the browser. An AJAX control is built entirely from JavaScript and DOM elements.

Warning

You can build an AJAX control that works with all major modern browsers, including Microsoft Internet Explorer, Mozilla Firefox, Opera, and Safari. However, browser compatibility is entirely up to you. The version of JavaScript supported by Firefox is different from the version of JavaScript supported by Internet Explorer. There are substantial differences in how the DOM is implemented in different browsers.

When using Visual Web Developer, you create an AJAX control by selecting the menu option Website, Add New Item and selecting the AJAX Client Control template. When you create a new control, you start with the control skeleton in Listing 33.24.

Example 33.24. ClientControl.js

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("myControls");

myControls.ClientControl = function(element)
{
    myControls.ClientControl.initializeBase(this, [element]);
}

myControls.ClientControl.prototype =
{
    initialize: function()
    {
        myControls.ClientControl.callBaseMethod(this, 'initialize'),
        // Add custom initialization here
    },

    dispose: function()
    {
        //Add custom dispose actions here
        myControls.ClientControl.callBaseMethod(this, 'dispose'),
    }
}
myControls.ClientControl.registerClass('myControls.ClientControl', Sys.UI.Control);

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Like the JavaScript classes we created earlier in this chapter, an AJAX control consists of a constructor function and a set of methods defined in the control’s prototype.

If you look at the second-to-last line in the skeleton, you will notice that an AJAX control inherits from the base Sys.UI.Control class. For this reason, in the constructor function, it is important to call initializeBase() so that the base Sys.UI.Control class get initiated.

Notice that a parameter is passed to the constructor function (and to the initializeBase() method). This parameter represents the DOM element with which the AJAX control is associated. You can use the base Sys.UI.Control class’s get_element() method to retrieve the DOM element within any of the control methods.

The control has two methods: initialize() and dispose(). You use the initialize() method to build up the control’s DOM elements and wire up the control’s event handlers. You use the dispose() method to unwire the event handlers to prevent memory leaks.

Let’s go ahead and create a really simple AJAX control: a Glow control. When you hover your mouse over the control, its background color changes to yellow. The Glow control is contained in Listing 33.25.

Example 33.25. Glow.js

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("myControls");

myControls.Glow = function(element)
{
    myControls.Glow.initializeBase(this, [element]);

    // initialize fields
    this._text = "Glow Control";
    this._backgroundColor = "yellow";
}

myControls.Glow.prototype =
{
    initialize: function()
    {
        myControls.Glow.callBaseMethod(this, 'initialize'),

        // Wire-up delegates to element events
        $addHandlers
            (
                this.get_element(),
                {
                    mouseover: this._onMouseOver,
                    mouseout: this._onMouseOut
                },
                this
            );
    },

    dispose: function()
    {
        // Unwire delegates
        $clearHandlers(this.get_element());
        myControls.Glow.callBaseMethod(this, 'dispose'),
    },

    _onMouseOver: function()
    {
        this.get_element().style.backgroundColor = this._backgroundColor;
    },

    _onMouseOut: function()
    {
        this.get_element().style.backgroundColor = "";
    },

    get_text: function()
    {
        return this._text;
    },

    set_text: function(value)
    {
        this._text = value;
        this.get_element().innerHTML = value;
    },

    get_backgroundColor: function()
    {
        return this._backgroundColor;
    },

    set_backgroundColor: function(value)
    {
        this._backgroundColor = value;
    }
}
myControls.Glow.registerClass('myControls.Glow', Sys.UI.Control);

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

A single parameter is passed to the Glow control’s constructor function. This parameter represents the DOM element to which the Glow control will be attached.

In the Glow control’s constructor function, two private fields named _text and _backgroundColor are initialized with default values. If you don’t modify the control’s properties, the control contains the text “Glow Control” and changes its background color to yellow when you hover over it.

The control’s prototype object contains an initialize() method that is used to set up the mouseover and mouseout event handlers for the control. The delegates are created and wired up to the mouseover and mouseout events with the following lines of code:

// Wire-up delegates to element events
$addHandlers
    (
        this.get_element(),
        {
            mouseover: this._onMouseOver,
            mouseout: this._onMouseOut
        },
        this
     );

If you hover your mouse over the DOM element associated with the control, the _onMouseOver() method is called. If you move your mouse away from the control, the _onMouseOut() method is called.

Notice that a final context argument, this, is passed to the $addHandlers() shortcut. This final argument changes the meaning of this within the _onMouseOver() and _onMouseOut() methods. Normally, within these event handlers, this would refer to the DOM element that raised the mouseout or mouseover event. Passing a final context argument to the $addhandlers() shortcut changes the meaning of this to refer to the client control instead of the DOM element.

The _onMouseOver() event handler contains the following code:

_onMouseOver: function()
{
  this.get_element().style.backgroundColor = this._backgroundColor;
}

The get_element() method returns the DOM element associated with the control. The backgroundColor of the DOM element is changed to the value of the control’s private _backgroundColor field.

The page in Listing 33.26 illustrates one way you can use the Glow control in an AJAX page.

Example 33.26. ShowGlowControl.aspx

<%@ Page Language="C#" %>
<!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>Show Glow Control</title>
    <script type="text/javascript">
      function pageLoad()
      {
        $create
            (
                myControls.Glow,
                {text: "Hello", backgroundColor: "orange"},
                null,
                null,
                $get("div1")
            );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Glow.js" />
        </Scripts>
        </asp:ScriptManager>

        <div id="div1"></div>

    </div>
    </form>
</body>
</html>

In Listing 33.26, the Glow control is created in the pageLoad() method with the following $create() shortcut:

$create
(
      myControls.Glow,
     {text: "Hello", backgroundColor: "orange"},
     null,
     null,
     $get("div1")
);

The $create() shortcut accepts the following parameters:

  • typeThe type of control or behavior to create.

  • propertiesA set of property values used to initialize the control.

  • eventsA set of event handlers used to initialize the control.

  • referencesA set of references to other components used to initialize the control.

  • elementThe DOM element associated with the control.

In Listing 33.26, the Glow control is associated with a <div> element named div1. The $create() shortcut transforms the <div> element into an AJAX client control. If you hover your mouse over the <div> element, the background color of the <div> element changes to orange.

Calling the $create() shortcut to create an instance of an AJAX control might seem like a crazy amount of work to instantiate a control in a page. What if you have dozens of controls in a page? In that case, would you need to call the $create() method dozens of times? (I feel exhausted just thinking about it.)

You can create server-side controls simply by declaring the control in a page. It seems like you should be able to do the very same thing when creating client-side AJAX controls. I want to declare an AJAX control, not create it programmatically. We address this issue in the next section.

Launching a Client-Side Control from the Server

Two chapters ago, I wrote that ASP.NET is a dead technology. The server side is the past; the client side is the future. If we want responsive applications, they need to be client-side applications.

However, in the brave new world of Ajax, there is still a place for ASP.NET server-side controls. An ASP.NET server-side control can be used as a launch device (like the booster rockets on the space shuttle) to lift JavaScript code from the server to the client.

Once we have reached escape velocity, it is important that an application continue to execute on the client. Just as it is incredibly expensive and time consuming to move back and forth from earth to orbit and orbit to earth, an AJAX page should not keep posting back to the server. An ASP.NET server control has done all the work it should ever do after it renders its contents once.

In this section, you learn how to create a server-side control that launches the AJAX client-side control we created in the previous section. Actually, we create two different server-side controls that do the same thing. First, we create an AJAX user control and then we create an AJAX custom control.

Creating a Server-Side AJAX User Control

One way to create a server-side control that launches a client-side AJAX control is to create a server-side user control. To make this work, you must implement the System.Web.UI.IScriptControl interface when you create the user control.

Note

We discussed user controls in Chapter 7, “Creating Custom Controls with User Controls.”

The user control in Listing 33.27 launches the client-side Glow control.

Example 33.27. Glow.ascx

<%@ Control Language="C#" ClassName="Glow" %>
<%@ Implements Interface="System.Web.UI.IScriptControl" %>
<%@ Import Namespace="System.Collections.Generic" %>
<script runat="server">

    private string _Text = "Glow Control";
    private string _BackgroundColor = "orange";

    public string Text
    {
        get { return _Text; }
        set { _Text = value; }
    }

    public string BackgroundColor
    {
        get { return _BackgroundColor; }
        set { _BackgroundColor = value; }
    }

    public IEnumerable<ScriptReference> GetScriptReferences()
    {
        ScriptReference sref = new ScriptReference("Glow.js");
        List<ScriptReference> colSRefs = new List<ScriptReference>();
        colSRefs.Add( sref );
        return colSRefs;
    }

    public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        ScriptControlDescriptor des = new ScriptControlDescriptor ("myControls.Glow", this.ClientID);
        des.AddProperty("text", _Text);
        des.AddProperty("backgroundColor", _BackgroundColor);
        List<ScriptDescriptor> colDes = new List<ScriptDescriptor>();
        colDes.Add( des );
        return colDes;
    }

    protected override void OnPreRender(EventArgs e)
    {
        ScriptManager.GetCurrent(Page).RegisterScriptControl(this);
        base.OnPreRender(e);
    }


    protected override void Render(HtmlTextWriter writer)
    {
        ScriptManager.GetCurrent(Page).RegisterScriptDescriptors(this);
        base.Render(writer);
    }

</script>

<div id='<%= this.ClientID %>'></div>

The IScriptControl interface has two methods that you must implement:

  • GetScriptReferences()Returns a list of JavaScript files required by the client-side control.

  • GetScriptDescriptors()Returns a list of $create() shortcuts for instantiating the client-side control.

The GetScriptReferences() method is implemented like this:

public IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference sref = new ScriptReference("Glow.js");
    List<ScriptReference> colSRefs = new List<ScriptReference>();
    colSRefs.Add( sref );
    return colSRefs;
}

This method creates a single ScriptReference that represents the Glow.js JavaScript file. Because our simple Glow client-side control has no other JavaScript file dependencies, we don’t need to perform any other work.

The GetScriptDescriptors() method looks like this:

public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor des = new ScriptControlDescriptor("myControls.Glow", this.ClientID);
    des.AddProperty("text", _Text);
    des.AddProperty("backgroundColor", _BackgroundColor);
    List<ScriptDescriptor> colDes = new List<ScriptDescriptor>();
    colDes.Add( des );
    return colDes;
}

This method creates a single ScriptControlDescriptor. When the control renders the ScriptControlDescriptor, it looks like this:

$create(myControls.Glow, {"backgroundColor":"orange","text":"Glow Control"}, null, null, $get("Glow1"));

Notice that the user control’s OnPreRender() and Render() methods are overridden. In the OnPreRender() method, the user control is registered with the ScriptManager control. The Render() method causes the user control’s ScriptDescriptors ($create() shortcuts) to be rendered.

Finally, notice that the user control contains a single <div> element that looks like this:

<div id='<%= this.ClientID %>'></div>

This <div> element is rendered to the browser. The $create() shortcut transforms the <div> element into a client-side Glow control.

Listing 33.28 illustrates how you can use the user control in an AJAX page.

Example 33.28. ShowUserControlGlow.aspx

<%@ Page Language="C#" %>
<%@ Register TagPrefix="user" TagName="Glow" Src="~/Glow.ascx" %>
<!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>Show User Control Glow</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <user:Glow
            ID="Glow1"
            Text="The First One"
            BackgroundColor="red"
            runat="server" />

        <br /><br />

        <user:Glow
            ID="Glow2"
            Text="The Second One"
            BackgroundColor="yellow"
            runat="server" />

    </div>
    </form>
</body>
</html>

Notice that there is absolutely no code (client side or server side) in the AJAX page in Listing 33.28. Two instances of the Glow user control are declared with different Text and BackgroundColor properties. When the page is rendered, the following two $create() shortcuts are rendered automatically (you can see these statements by selecting View Source in your browser):

Sys.Application.add_init(function() {
  $create(myControls.Glow, {"backgroundColor":"red","text":"The First One"}, null, null, $get("Glow1"));
});
Sys.Application.add_init(function() {
  $create(myControls.Glow, {"backgroundColor":"yellow","text":"The Second One"}, null, null, $get("Glow2"));
});

Creating a Server-Side AJAX Custom Control

Another option for launching a client-side control from the server side is to create a custom server-side control. When creating a custom server-side control, you can implement the IScriptControl interface just like we did in the previous section. Alternatively, you can derive a custom control from the base ScriptControl class.

The control in Listing 33.29 illustrates how you can launch the client-side Glow control by deriving a server-side control from the ScriptControl class.

Example 33.29. App_CodeGlow.cs

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;

namespace MyControls
{
public class Glow : ScriptControl

{
    private string _Text = "Glow Control";
    private string _BackgroundColor = "orange";

    public string Text
    {
        get { return _Text; }
        set { _Text = value; }
    }

    public string BackgroundColor
    {
        get { return _BackgroundColor; }
        set { _BackgroundColor = value; }
    }

    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        ScriptReference sref = new ScriptReference("Glow.js");
        List<ScriptReference> colSRefs = new List<ScriptReference>();
        colSRefs.Add(sref);
        return colSRefs;
    }

    protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        ScriptControlDescriptor des = new ScriptControlDescriptor ("myControls.Glow", this.ClientID);
        des.AddProperty("text", _Text);
        des.AddProperty("backgroundColor", _BackgroundColor);
        List<ScriptDescriptor> colDes = new List<ScriptDescriptor>();
        colDes.Add(des);
        return colDes;
    }
}
}

The ScriptControl class is an abstract class. It has two methods that you must implement:

  • GetScriptReferences()Returns a list of JavaScript files required by the client-side control.

  • GetScriptDescriptors()Returns a list of $create() shortcuts for instantiating the client-side control.

These methods should look familiar because they are the same methods you are required to implement when implementing the IScriptControl interface.

Notice that you are not required to call the RegisterScriptControl() and RegisterScriptDescriptors() methods like we did in the previous section. The base ScriptControl class calls these methods for us automatically.

The page in Listing 33.30 illustrates how you can use the server-side Glow control in an AJAX page.

Example 33.30. ShowCustomControlGlow.aspx

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="MyControls" %>
<!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>Show Custom Control Glow</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <custom:Glow
            ID="Glow1"
            Text="The First One"
            BackgroundColor="red"
            runat="server" />

        <br /><br />

        <custom:Glow
            ID="Glow2"
            Text="The Second One"
            BackgroundColor="yellow"
            runat="server" />

    </form>
</body>
</html>

The page in Listing 33.30 has two instances of the Glow control. Notice, once again, that the page does not contain any server-side or client-side control. The AJAX client-side control was launched completely declaratively.

Creating Client-Side Behaviors

Client-side behaviors are closely related to client-side controls. Like a client-side control, a client-side behavior executes entirely within the browser. A client-side behavior is cobbled together out of JavaScript and DOM elements.

Whereas a DOM element can be associated with only a single client-side control, it can be associated with multiple client-side behaviors. You can use client-side behaviors to layer multiple features onto a single DOM element.

Note

Almost the entire ASP.NET AJAX Control Toolkit was implemented as client-side behaviors. For example, the AutoComplete extender renders a client-side behavior that adds auto-complete functionality to a TextBox control.

In this section, we create a HelpBehavior client-side behavior. This behavior adds a pop-up help box to an <input> element (see Figure 33.19). The source code for the HelpBehavior is contained in Listing 33.31.

Using the HelpBehavior behavior.

Figure 33.19. Using the HelpBehavior behavior.

Example 33.31. HelpBehavior.js

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("myControls");

myControls.HelpBehavior = function(element)
{
    myControls.HelpBehavior.initializeBase(this, [element]);
    // Initialize fields
    this._text = "Help Text...";
    this._helpBox = null;
}

myControls.HelpBehavior.prototype =
{
    initialize: function()
    {
        myControls.HelpBehavior.callBaseMethod(this, 'initialize'),

        // Initialize help box
        this._initializeHelpBox();

        // Wire-up delegates
        $addHandlers
            (
                this.get_element(),
                {
                    focus: this._onFocus,
                    blur: this._onBlur
                },
                this
            );
    },

    _initializeHelpBox: function()
    {
        // Create div element for help box
        this._helpBox = document.createElement("DIV");

        // Hard code a bunch of inline styles
        this._helpBox.style.display = "none";
        this._helpBox.style.position = "absolute";
        this._helpBox.style.width = "100px";
        this._helpBox.style.backgroundColor = "lightyellow";
        this._helpBox.style.border = "solid 1px black";
        this._helpBox.style.padding = "4px";
        this._helpBox.style.fontSize = "small";
        this._helpBox.innerHTML = this._text;

        // Position box right below input element
        var bounds = Sys.UI.DomElement.getBounds(this.get_element());
        Sys.UI.DomElement.setLocation(this._helpBox, bounds.x, bounds.y + bounds.height);

        // Append box to body
        document.body.appendChild(this._helpBox);
    },

    dispose: function()
    {
        $clearHandlers(this.get_element());
        myControls.HelpBehavior.callBaseMethod(this, 'dispose'),
    },

    _onFocus: function()
    {
        this._helpBox.style.display = "";
    },

    _onBlur: function()
    {
        this._helpBox.style.display = "none";
    },

    get_text: function()
    {
        return this._text;
    },

    set_text: function(value)
    {
        this._text = value;
    }

}
myControls.HelpBehavior.registerClass('myControls.HelpBehavior', Sys.UI.Behavior);

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Notice that the skeleton for HelpBehavior.js looks almost exactly the same as the skeleton for an AJAX client-side control. You create a client-side behavior in the same way you create a client-side control. You declare a constructor function to generate a class. In the class prototype, you create both an initialize() and a dispose() method.

The only difference between a client-side control and a client-side behavior is the base class. A client-side behavior inherits from the Sys.UI.Behavior class instead of the Sys.UI.Control class.

After you create a behavior, you can use the $create() shortcut to instantiate the behavior and associate it with a DOM element. The page in Listing 33.32 illustrates how you can use the HelpBehavior with an <input> element.

Example 33.32. ShowHelpBehavior.aspx

<%@ Page Language="C#" %>
<!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>Show Help Box</title>
    <script type="text/javascript">

      function pageLoad()
      {
        $create
            (
                myControls.HelpBehavior,
                {text:"Enter your first name"},
                null,
                null,
                $get("txtFirstName")
            );

        $create
            (
                myControls.HelpBehavior,
                {text:"Enter your last name."},
                null,
                null,
                $get("txtLastName")
            );
      }

    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/HelpBehavior.js" />
        </Scripts>
        </asp:ScriptManager>
        <label for="txtFirstName">First Name:</label>
        <input id="txtFirstName" />

        <br /><br />

        <label for="txtLastName">Last Name:</label>
        <input id="txtLastName" />

    </div>
    </form>
</body>
</html>

When you move focus between the first name and last name input boxes, different help messages are displayed.

Launching a Client-Side Behavior from the Server

In the same way that you can launch a client-side control from the server, you can launch a client-side behavior from the server. A server-side control that launches a behavior is called an extender control.

Note

The extenders in the AJAX Control Toolkit ultimately derive from the ExtenderControl class. However, the Toolkit uses an intermediate base class named the ExtenderControlBase class. This intermediate class is included in the source code download of the AJAX Control Toolkit.

You can create an extender control in one of two ways: You can implement the IExtenderControl interface, or you can derive a control from the base ExtenderControl class. The control in Listing 33.33 illustrates how you can create a new extender control by inheriting from the ExtenderControl class.

Example 33.33. HelpExtender.cs

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;

namespace MyControls
{
[TargetControlType(typeof(TextBox))]
public class HelpExtender: ExtenderControl
{
    private string _Text = "Help Text...";

    public string Text
    {
        get { return _Text; }
        set { _Text = value; }
    }

    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        ScriptReference sref = new ScriptReference("~/HelpBehavior.js");
        List<ScriptReference> colSRefs = new List<ScriptReference>();
        colSRefs.Add(sref);
        return colSRefs;
    }

    protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors
    (
      Control targetControl
    )
    {
        ScriptControlDescriptor des = new ScriptControlDescriptor ("myControls.HelpBehavior", TargetControlID);
        des.AddProperty("text", _Text);
        List<ScriptDescriptor> colDes = new List<ScriptDescriptor>();
        colDes.Add(des);
        return colDes;
    }
}
}

The extender control in Listing 33.33 derives from the base ExtenderControl class. The ExtenderControl class is an abstract class. You must implement the following two methods:

  • GetScriptReferences()Returns a list of JavaScript files required by the client-side behavior.

  • GetScriptDescriptors()Returns a list of $create() shortcuts for instantiating the client-side behavior.

These are the same methods you would need to implement for a client-side control. The one difference is that the ExtenderControl.GetScriptDescriptors() method, unlike the ScriptControl.GetScriptDescriptors() method, has the target control passed to it.

Notice that the extender control in Listing 33.33 includes the following class-level attribute:

[TargetControlType(typeof(TextBox))]

This attribute restricts the type of control to which you can apply the extender control. In particular, this attribute prevents you from applying the extender control to anything other than a TextBox control.

Note

Unfortunately, an extender control must be applied to a server-side control. You cannot use an HTML element as the target of an extender control. For this reason, when building pure client-side AJAX applications, I would stick with the server-side ScriptControl class as a launch vehicle for both client-side controls and behaviors.

The page in Listing 33.34 demonstrates how you can use the HelpExtender control to extend the functionality of two TextBox controls.

Example 33.34. ShowHelpExtender.aspx

<%@ Page Language="C#" %>
<%@ Register TagPrefix="custom" Namespace="MyControls" %>
<!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>Show Help Extender</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <asp:Label
            id="lblFirstName"
            Text="First Name:"
            AssociatedControlID="txtFirstName"
            Runat="server" />
        <asp:TextBox
            id="txtFirstName"
            Runat="server" />
        <custom:HelpExtender
            id="he1"
            TargetControlID="txtFirstName"
            Text="Enter your first name."
            Runat="server" />

        <br /><br />

        <asp:Label
            id="lblLastName"
            Text="Last Name:"
            AssociatedControlID="txtLastName"
            Runat="server" />
        <asp:TextBox
            id="txtLastName"
            Runat="server" />
        <custom:HelpExtender
            id="he2"
            TargetControlID="txtLastName"
            Text="Enter your last name."
            Runat="server" />

    </div>
    </form>
</body>
</html>

When you move focus between the two TextBox controls rendered by the page in Listing 33.34, the different help boxes appear.

Summary

This chapter was about the future. Times are changing. People are demanding more interactivity and responsiveness from their web applications. The future is Ajax.

In the first part of this chapter, you learned how to take advantage of the features of the Microsoft AJAX Library. You learned how to make JavaScript feel more like C#. In particular, you learned how to use the methods of the Microsoft AJAX Library to create classes and namespaces. You also learned how to debug a client-side AJAX application.

In the next part of this chapter, you learned how to call web services from the browser. You learned how to call a web method in an external web service. You also learned how to call a static web method exposed from a server-side page. We also discussed the three built-in web services included with the Microsoft AJAX Framework: the Authentication service, the Role service, and the Profile service.

In the final part of this chapter, you learned how to create AJAX client-side controls and behaviors. You learned how to create AJAX controls and behaviors programmatically by using the $create() shortcut. You also learned how to launch a client-side control and client-side behavior from the server side by building server-side ScriptControl and ExtenderControl classes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset