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. 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.
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.
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 For example, the file in Listing
contains a single JavaScript function called sayMessage() that displays a
JavaScript alert with a message.
LISTING 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 illustrates how you add a script
reference to the myLibrary.js library.
LISTING 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>
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.
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.
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).
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.
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.
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.
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.
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:
. altKey—Returns true when the Alt key is pressed.
. button—Returns a value from the Sys.UI.MouseButton enumeration: leftButton, middleButton, or rightButton.
. charCode—Returns
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.
. clientX—Returns the horizontal position of the mouse relative to the client area of the browser window, excluding the scroll bars.
. clientY—Returns the vertical position of the mouse relative to the client area of the browser window, excluding the scroll bars.
. ctrlKey—Returns true when the Ctrl key is pressed.
. offsetX—Returns the horizontal position of the mouse relative to the element that raised the event.
. offsetY—Returns the vertical position of the mouse relative to the element that raised the event.
. screenX—Returns the horizontal position of the mouse relative to the entire screen.
. screenY—Returns the vertical position of the mouse relative to the entire screen.
. shiftKey—Returns true when the Shift key is pressed.
. target—Returns the original element that raised the event (as distinct from the element associated with the event handler).
. type—Returns the name of the event (for example, click).
The Sys.UI.DomEvent class also has two useful methods:
. preventDefault—Stops the default action associated with the event.
. stopPropagation—Stops the event from bubbling up to its parent element.
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.
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:
. Auto—This is the default value. Use the compilation setting from the Web.config file.
. Debug—Use debug scripts.
. Inherit—Same as Auto.
. Release—Use 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:
. assert—Enables
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.
. clearTrace—Enables you to clear all messages from the trace console.
. fail—Enables you to display a message in the debugger and the trace console and break into the debugger.
. trace—Enables you to output a message to the debugger and trace console.
. traceDump—Enables 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.
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, weexamine 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. The first step is to
create the web service. The web service is contained in Listing.
LISTING 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 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 calls the web service in order to display a random quotation.
LISTING 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>
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 includes a
server method named GetQuote(). The server GetQuote() method is called
by a client method named GetQuote().
LISTING 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)
{
1732 CHAPTER 33 Using Client-Side ASP.NET AJAX
LISTING 33.18 Continued
$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, 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. Both operations are performed
through AJAX calls so that a postback is never necessary.
LISTING 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 =
FIGURE 33.15 Displaying and inserting database records with AJAX.
1734 CHAPTER 33 Using Client-Side ASP.NET AJAX
LISTING 33.19 Continued
{
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 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.
LISTING 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>
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. The page in Listing 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
If you want to try the ShowLogin.aspx page, a valid username and password are Steve and secret#, respectively.
LISTING 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 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:
. userName—The username to authenticate.
. password—The password to authenticate.
. isPersistent—Determines whether a session or persistent cookie is created after successful authentication.
. customInfo—Not used.
. redirectUrl—The page to which the user is redirected after successful authentication.
. loginCompletedCallback—The method to call when the web service call completes.
. failedCallback—The method to call when the web service call fails.
. userContext—Additional 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.
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.
LISTING 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, 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. 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.
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>
Now that we
have the profile configured on the server, we can use it in an AJAX
page. The page in Listing 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.
LISTING 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>
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.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 developedIn 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.
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
LISTING 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.
LISTING 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.
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. 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 illustrates how you can
create a new extender control by inheriting from the ExtenderControl
class.
LISTING HelpExtender.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
namespace MyControls
{
1767
33
Creating Custom AJAX Controls and Behaviors
[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 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 clientside behavior.
GetScriptDescriptors()—Returns a list of $create() shortcuts for instantiating the client-side behavior.
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.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 demonstrates how you can use the HelpExtender control to extend
the functionality of two TextBox controls.
LISTING 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, the different help boxes appear.