Built-in Application Services
Overview
The HTTP protocol is simple, effective, and ubiquitous, but it is not stateful. So two consecutive and logically related calls to the same URL (that is, a postback) are treated as independent calls, and for each of them a new and especially clean environment is created. To add some state and global data containment to Web applications, virtually all server-side runtime environments build an abstraction layer and provide additional services to applications. Common services for ASP.NET applications include session state, authentication, role management, caching, profiling and, in general, any operation that can manipulate an intrinsic ASP.NET object.
These application services are available only to server-side code and are not directly exposed to JavaScript; as such, they can’t be invoked directly from the client page. Despite their rich and advanced user-interface capabilities, ASP.NET AJAX applications are primarily ASP.NET applications. So they still need a way to use traditional ASP.NET application services, such as Forms authentication, session state management, server-side caching, user profiling, and role and membership information verification. In the native server environment of ASP.NET, developers find a number of facilities such as the HttpContext object (including its child objects Session, Cache, Profile, and User) and static service-specific classes to accomplish common tasks such as managing a profile or authenticating a user. But on the client?
ASP.NET AJAX Extensions wraps some of these base application services into JavaScript classes defined in the Microsoft AJAX library. (See Chapter 2, “The Microsoft Client Library for AJAX.”) Such wrapper classes expose server-side services through a number of built-in Web services, making services callable directly from the client.
In ASP.NET AJAX, you find client-side wrappers for two key and related server-side services: Forms authentication and user profiles.
Note |
If you want to expose other system services to client script code, you have to use page methods or write ad hoc Web services, as we’ll see in Chapter 7, “Remote Method Calls with ASP.NET AJAX.” Note, though, that in the next version of ASP.NET a third built-in service will be added to the list-the Role service. |
Forms Authentication Services
To protect Web pages from unauthorized users, you place some boilerplate code on top of each page and redirect the user to a login page. On the login page, the user is prompted for credentials. Next, if successfully authenticated, the user is redirected to the originally requested page. Forms authentication is the ASP.NET built-in infrastructure that implements the aforementioned pattern for login.
Server-side Forms authentication requires an additional login page and two HTTP redirect commands. Can you authenticate directly from the client without any page redirection? You bet.
The System Infrastructure for Authentication
The infrastructure of AJAX-accessible ASP.NET services is made of two key components: a client-side JavaScript class and a server-side Web service. Authentication and user profile services are both implemented according to this model.
A JavaScript class provides you with methods to send credentials to a built-in Web service. You receive a Boolean answer that indicates whether or not the user has been authenticated. The Web service uses the server-side ASP.NET authentication and membership API to validate the user information.
By using a client JavaScript class to command a remote Web service, you authenticate users more quickly because you incorporate login forms into the home page of the site (or any pages a user would visit regularly) rather than force a redirect to a specialized login page.
The Client-Side Authentication Service Class
To verify the credentials that a user typed in a client-side login box, you invoke the methods of a class defined in the Microsoft AJAX Library. The class is named Sys.Services._AuthenticationService and, as the leading underscore in the name suggests, it should be considered private to the library. More importantly, the class is a singleton, meaning that it has only one instance with a global point of access. You don’t have to instantiate the _AuthenticationService class; you just use the global instance of it that is always available to your application and returned by the object named Sys.Services.AuthenticationService.
The global entry point is set in the Microsoft AJAX Library following the definition of the class. Here’s an excerpt from the source code:
Sys.Services.AuthenticationService = new Sys.Services._AuthenticationService();
The authentication service class features the methods listed in Table 6-1.
Method |
Description |
---|---|
login |
This method verifies credentials and issues an authentication ticket if the credentials are good. |
logout |
This method clears the authentication ticket. |
Table 6-2 details the properties defined on the object.
Property |
Description |
---|---|
isLoggedIn |
A Boolean read-only property, it indicates whether the current user is logged in. |
path |
This property gets and sets the URL to reach the authentication Web service. |
timeout |
This property gets and sets the timeout for the user authentication process. |
The Sys.Services._AuthenticationService class acts as a client-side proxy for the remote Web service. It inherits from a class named Sys.Net.WebServiceProxy that is the base class for any JavaScript proxy of a remote Web service. As we’ll see in Chapter 7, a class derived from Sys.Net.WebServiceProxy is generated on the fly for each ASP.NET Web service registered with the script manager. For the authentication Web service, this class is simply hard-coded in the Microsoft AJAX Library.
The Server-Side Authentication Web Service
The back end of the authentication service is implemented in the AuthenticationWebService class. The class is contained in the AJAX assembly within the System.Web.Security.AuthenticationService namespace. The AuthenticationWebService class is mapped to the following URL:
Authentication_JSON_AppService.axd
The URL points to an HTTP handler registered in the web.config file. The handler simply captures incoming requests, deserializes input parameters and method names, executes the call, and sends values back to the client in a JavaScript Object Notation (JSON) stream.
After the AJAX authentication service is enabled in the site configuration, any page served from the site includes the following script:
As you can see, a private member of the JavaScript _AuthenticationService class is set to the default path of the server Web service. However, by setting the path property via JavaScript, you can make the client invoke an authentication Web service elsewhere. The Web service, though, must support JSON and expose a contract identical to that of the _AuthenticationService class.
The Programming Interface of the Service
As you would expect, the AuthenticationWebService class features the same two methods-login and logout-that we have in the proxy client and with the same signatures. The login method, for example, has the following signature:
[WebMethod] public bool login( string userName, string password, bool createPersistentCookie)
The first two arguments (userName and password) set the credentials; the final Boolean argument indicates whether a persistent cookie is required. When you authenticate a user through Forms authentication, a cookie is created and attached to the response. This cookie is referred to as the authentication ticket, and its name defaults to .ASPXAUTH.
If you set the createPersistentCookie argument to false, the issued authentication ticket expires after 30 minutes. If you opt for a persistent cookie, the authentication ticket lasts as long as 50 years. What if you need a ticket timeout that falls in between 30 minutes and 50 years? You can simply set the timeout attribute in the section of the application’s web.config file to the desired number of minutes.
The login method of the Web service uses the following code to do its work:
if (Membership.Provider.ValidateUser(userName, password)) { FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); return true; } return false;
As you can see, it validates using the current membership provider and sets the cookie using the SetAuthCookie method on the FormsAuthentication class. This means that most settings defined in the section of the configuration file are taken into proper account while creating the authentication ticket.
Note |
The AJAX authentication service doesn’t work with applications that use a cookieless authentication scheme. SetAuthCookie works as it usually does for classic ASP.NET applications and modifies the return URL to include the authentication ticket. In classic ASP.NET, though, a system component intervenes, moving the ticket to a header and rewriting the URL to that originally requested. This component is an ISAPI filter (aspnet_filter.dll) that hooks up any ongoing request. If you authenticate through a Web service call, no new request is fired that the filter can intercept to extract the authentication ticket from the URL. As a result, when the user is successfully authenticated, the caller page receives a positive response, but no cookie (or equivalent information) is attached to the response. Any further requests for a secured resource are therefore destined to fail. |
The logout method takes no arguments and simply removes the cookie from the response. Internally, the method ends up calling the SignOut method of the FormsAuthentication class.
Note |
ASP.NET AJAX authentication can happen only through a membership provider. If you need to implement custom logic to authenticate users, you must build a custom provider. For more information about ASP.NET Forms authentication and custom membership providers, refer to Chapter 15 of my book Programming Microsoft ASP.NET 2.0 Applications: Core Reference (Microsoft Press, 2005). More details on ASP.NET providers can be found in Chapter 4 of Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press, 2006). |
Configuration of the Authentication Service
The ASP.NET AJAX authentication service requires you to set a few attributes in the application’s web.config file. In particular, you need to enable the service as follows:
Note that the service is disabled by default. A second attribute can be specified for the ticationService> node-the requireSSL Boolean attribute. Set to false by default, the attribute indicates whether a Secure Sockets Layer (SSL) connection is required to transmit the authentication cookie.
Warning |
A frequent security hole in too many Web applications is when a nonsecured HTTP page (for example, the site’s home page) hosts the login box. If the submit button of the login form points to an HTTPS page, your credentials are sent out properly protected. However, can you be sure that you’re sending out your credentials from a site that is really the site it claims to be? If you’re a victim of a DNS-poisoning attack, you might have been redirected to an apparently identical Web site that is run by a hacker. In this scenario, it is easy for the hacker to steal your credentials and then redirect you to the right site. To be absolutely sure this cannot happen to users of your site, you should display a login box only within an HTTPS page. |
When SSL is not required, credentials are sent out as clear text. This is a known limitation of Forms authentication. If this is a concern to you, use SSL or Transport Layer Security (TLS) to secure your login page.
The section is located under a new AJAX-specific section:
All sections must be properly registered, as discussed in Chapter 3, “The Pulsing Heart of ASP.NET AJAX.” An ASP.NET AJAX application built using the Microsoft Visual Studio 2005 template already incorporates required settings in its default web.config file.
In addition to configuring AJAX-specific sections, to take advantage of ASP.NET AJAX out-of-band authentication you also need to have Forms authentication turned on in the
Note |
Why are two different settings needed to enable authentication in ASP.NET AJAX applications? ASP.NET AJAX applications are still ASP.NET applications, even though they add a lot of new functionalities. The runtime environment that processes requests is the same for classic and ASP.NET AJAX applications. You need two different settings because currently AJAX Extensions is implemented as a bolted-on framework that hooks up certain behaviors of ASP.NET. The next version of ASP.NET will fully incorporate AJAX functionalities and expose them as part of a unique framework. When this happens, you can expect to find settings for the AJAX authentication service directly in the <authentication> section of |
Using the Authentication Service in an Application
In a classic ASP.NET application configured to use Forms authentication, you group secured pages in one or more subfolders with a local web.config file that contains the following:
The script blocks anonymous users-by using the question mark symbol (?)-and lets any other user pass. In this way, when the user navigates to any page in such a folder, the request is captured by the Forms authentication module. If the request has no valid ticket attached, or its URL hasn’t been mangled to contain the ticket, the user is redirected to a login page. The key facts in this model are that the authentication occurs on demand and is automatically facilitated by the system. Sure, you can provide in the master page a link to a login page so that users can be authenticated before trying to access secured pages. However, this is a bonus feature you implement and is not strictly required from a functional point of view. Because the capability to execute Forms authentication is built into ASP.NET itself, this model works without needing to change anything in an ASP.NET AJAX application.
ASP.NET AJAX applications, though, make a point of reducing the number of page redirects and full postbacks. In an AJAX application, you typically incorporate the login form in the master page and call the authentication service when the user clicks the Log In button. In Figure 6-1, you see a sample ASP.NET AJAX page with a login panel.
Anonymous and authenticated users both can access the page and use a number of features; only registered users can access other features.
The Login Process
The login form can be as easy to create as a table by using a couple of text boxes and a push button. Needless to say, you can decorate the input fields with as many extenders as you want. In an ASP.NET AJAX application, the Log In button is clearly a client button (so that it doesn’t cause a postback) and points to some JavaScript code that invokes the authentication service:
The OnLogin function looks like in the following code:
As you can see, the login method of the JavaScript class includes a few arguments beyond the user name and password. Let’s review the signature in detail. Table 6-3 lists the accepted parameters.
Parameter |
Optional |
Description |
---|---|---|
userName |
No |
Indicates the user name to verify against the list of registered users. |
password |
No |
Indicates the password of the specified user. The parameter can be null. |
createPersistentCookie |
Yes |
Boolean value. It indicates whether a persistent cookie should be created. |
customInfo |
Yes |
String value. It indicates any additional text you want to pass. This property doesn’t appear to be used in the current version of ASP.NET AJAX Extensions. |
redirectUrl |
Yes |
String value. It indicates the URL to redirect the user to after a successful authentication. |
loginCompletedCallback |
Yes |
Indicates the JavaScript callback to invoke after a successful authentication. |
failedCallback |
Yes |
Indicates the JavaScript callback to invoke if the authentication fails. |
userContext |
Yes |
Any object you want the library to pass to the callbacks. |
Only the user name and password are mandatory parameters and can’t be omitted. Note that for the the password field a value of null is acceptable. You can indicate two callbacks: one for a successful completion of the login process and one to use in case of failure. Here’s a possible implementation of a login-completed callback:
function onLoginCompleted(results) { // On success, there will be a Forms authentication cookie in the browser // Adjust the user interface to reflect the logged-in status of the user HandleLoginLogoutState(results); // Clean up the login form if (results) { username.value = ""; password.value = ""; } else { alert("Sorry, your credentials appear to be invalid. Try again."); } }
In ASP.NET AJAX, any JavaScript callback used to process the results of a remote Web service call has the following prototype:
function methodCompleted(results, context, methodName)
The results argument contains the return value of the Web service method, whereas context mirrors the contents of the userContext parameter of the login method. Finally, methodName indicates the name of the client-side method that triggered the callback. Of course, context and methodName arguments can be null.
The return value of the authentication Web service method is passed to the callback. In this case, it is a Boolean value indicating the result of the authentication.
You can handle the possible failure of a login operation by using a made-to-measure callback, as shown here:
function onLoginFailed(results, context, methodName) { alert("Login failed. <" + results.get_message() + ">"); }
In this case, the results parameter is the JavaScript transposition of the exception that was raised on the server. It is a JavaScript Error object with as many properties as the server-side exception object.
It should be noted, though, that the login-failed callback is triggered only if an exception occurs on the server while the credentials are being verified. If the credentials are simply invalid, no exception is thrown, a value of false is returned to the client, and the login-failed callback isn’t invoked.
The Logout Process
Figure 6-2 shows the HTTP request and response for a login attempt. (The tool in the figure is Web Development Helper, which we briefly discussed in Chapter 4, “Partial Page Rendering.”) As you can see, the HTTP response contains an additional cookie, named .ASPXAUTH. That is exactly the authentication ticket issued on the server and delivered to the client. From now on, the user can access any secured pages in the application until the ticket expires.
Figure 6-2: The HTTP response for a call to the login method on the authentication service
To give users a chance to log out directly from the client, you add another button to your client user interface and bind it to the following JavaScript code:
function Logout() { Sys.Services.AuthenticationService.logout(onLogoutCompleted); return false; }
When this function returns, the HTTP response looks like the one shown in Figure 6-3.
Figure 6-3: The HTTP response for a call to the logout method on the authentication service
The .ASPXAUTH cookie now has an empty body and can no longer be used to enable access to the secured pages of the application.
Typical User Interface Enhancements
Any applications based on authentication services should provide optional user interface (UI) elements to welcome the logged-in user and give her a chance to log out. In addition, some pages might also include conditional controls that show up only if the current request is authenticated. How would you know whether the current request is authenticated?
In classic ASP.NET, you simply check the Request.IsAuthenticated property in Page_Load or wherever else you need to know it. In ASP.NET AJAX, things will be slightly different.
When the JavaScript login method returns, you know whether or not the current user is authenticated. Suppose that the user navigates to another page in the application and then clicks the Back button or any link that returns the user to the original page. Once back to the original page, the user’s browser will have lost any information about the authentication status-in the end, the authentication status was simply the value of a JavaScript global variable. If not properly designed, the user interface might show that the user is not logged in, while the authentication module correctly handles her further requests for secured pages. Let’s consider a block of user interface code that displays the name of the logged-in user:
Hey,
Not logged in
The first
will be used when any user is logged in; the second block comes in handy when no user is connected. You toggle them based on the result of the login method and the authentication status of the request. To facilitate this, add the following code to the Page_Load event of the code file:
protected void Page_Load(object sender, EventArgs e) { if (Request.IsAuthenticated) { LoggedIn.Style["display"] = String.Empty; NotLoggedIn.Style["display"] = "none"; LoggedUser.Text = User.Identity.Name; // Show any UI element specific to registered users ... } else { LoggedIn.Style["display"] = "none"; NotLoggedIn.Style["display"] = ""; LoggedUser.Text = ""; // Hide any UI element specific to registered users ... } }
The code ensures that UI elements are correctly initialized when the page first loads and every time the page is refreshed, loaded through a link, or loaded via the Back/Forward buttons. In addition, you will need to extend the client-side onLoginCompleted and onLogoutCompleted callback functions to update the user interface dynamically as the user connects or disconnects directly from the page. For example, you can use the following function:
You can call the HandleLoginLogoutState function from both onLoginCompleted and onLogoutCompleted by passing true and false, respectively.
If the user has been successfully authenticated, you clear the user interface of the login form and enable the secured portions of the page. (See Figure 6-4.)
Figure 6-4: A typical user interface when the current user is logged in
Design Guidelines for Protected Resources
In ASP.NET, to guarantee that protected resources (for example, Web pages) are exclusively accessed by authorized users, you place them in a site folder controlled by a modified version of the web.config file. The configuration file requires that a valid authentication ticket is presented by requests aimed at any of the resources. From a UI perspective, you might or might not apply special measures to let users know about the requirements of a protected page. You can leave any links to such pages visible and active; if the user clicks, the system infrastructure blocks any unauthorized access.
More precisely, if the user follows the link to a protected page, ASP.NET first attempts to locate the authentication ticket. If no valid ticket is found, the user is automatically redirected to the configured login page. This behavior is good enough for classic ASP.NET pages; it’s not a solution for AJAX-enabled sites. The redirect, in fact, will break the continuity between users and applications that is a characteristic of AJAX applications.
Although there’s no way to prevent users from typing the URL of a protected page on the address bar-in that case, a full page reload is inevitable-there are two possible approaches to prevent users from blindly navigating to protected URLs from within the current page. You can hide links to protected pages, or you can add some script code to buttons to enable the operation only if the user is currently logged in. To hide protected resources to unauthenticated users, you set cascading style sheet (CSS) style attributes as shown in the preceding code snippet. To dynamically verify the status of the current user, you attach some script to the user interface element as shown here:
<a href="securedlistinvoices.aspx" onclick="return checkFirst()"> View invoices (Must be logged in) </a>
Next, you write a JavaScript function that checks the logged-in status and prevents the default action if the user is not authenticated:
function checkFirst() { var loggedIn = Sys.Services.AuthenticationService.get_isLoggedIn(); if (!loggedIn) { alert("You must be logged in to follow this link"); return false; } return true; }
To check the status of the user, you use the isLoggedIn property of the Sys.Services.AuthenticationService object.
User Profiling Services
To make your ASP.NET pages friendlier, more functional, and more appealing to use, you can build a personalization layer into your Web application that gives users a chance to set and retrieve their preferences. The personalization capabilities of ASP.NET 2.0 are built around the concept of the user profile.
The user profile is a collection of properties that the ASP.NET 2.0 runtime groups into a dynamically generated class. The application defines its own data model in the configuration file, and the ASP.NET runtime does the rest by parsing and compiling that model into a class. Each member of the class corresponds to a piece of information specific to the currently logged-in user. Any profile data is persisted on a per-user basis and is permanently stored on the server until someone with administrative privileges deletes it.
When the application runs and a page is displayed, ASP.NET dynamically creates a profile object that contains, properly typed, the properties you defined in the data model. The object is then added to the current HttpContext object and is available to pages through the Profile property. The ASP.NET profile service is implemented through an HTTP module. The module kicks in when the request has been authenticated and retrieves data for the specific user. (If the user is anonymous, a unique ID is generated for that user and stored in the .ASPXANONYMOUS cookie.) At the end of the request, the profile object is saved back to the data store ready for the next time.
From within a server page, you can read and write profile properties using the HttpContext.Profile object. Can you do the same from within an ASP.NET AJAX page in an out-of-band postback? In fact, you can.
The System Infrastructure for Profiling
Just as with the authentication service, the user profile service relies on a client-server infrastructure. A JavaScript class provides methods to load and save user-specific information to the server. You receive a collection of properties representing the profile of the current user and save it back to the server’s data store with any changes made on the client. The underlying Web service uses the ASP.NET profile provider to persist information.
The user profile can be used to update properties used by server-side controls and page components. However, it also opens up a whole new world of possibilities. In fact, you can have client controls to persist a portion of their state to the server on a per-user basis.
The Client-Side Profiling Service Class
The Microsoft AJAX Library that wraps the user profiling service is named Sys.Services._ProfileService and is a private class that is available to programmers through a single point of access-the Sys.Services._ProfileService object. The global entry point is set in the Microsoft AJAX Library following the definition of the class, as the following code snippet shows:
Sys.Services.ProfileService = new Sys.Services._ProfileService();
The profile service class features the methods listed in Table 6-4.
Method |
Description |
---|---|
load |
This method loads properties saved in the server’s profile data store for the current user. |
save |
This method saves the current user profile to the data store. |
Table 6-5 details the properties defined on the object.
Property |
Description |
---|---|
properties |
This property returns the collection of properties that form the user profile. |
path |
This property gets and sets the URL to reach the profile Web service. |
timeout |
This property gets and sets the timeout for the user profile process. |
Inherited from the Sys.Net.WebServiceProxy base class, the class Sys.Services._ProfileService acts as a client-side proxy for the remote Web service and is the bridge between the client page and the server-side HttpContext.Profile object.
Note |
The data store for a given profile service can be configured in the web.config file through ad hoc components known as the profile providers. For more information on profile providers, take a look at Chapter 4 of my book Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press, 2006). |
The Server-Side Profiling Web Service
The ASP.NET AJAX profile service is a Web service coded through the class ProfileWebService located in the System.Web.Profile namespace. The class is mapped to the following URL:
Profile_JSON_AppService.axd
The URL points to an HTTP handler registered in the web.config file. The handler captures incoming requests and input parameters, executes the call, and then sends values back to the client in a JSON stream.
Once the AJAX authentication service is enabled in the site configuration, any page served from the site includes the following script:
You can replace the default Web service with another of choice as long as the new Web service supports JSON and is an ASP.NET Web service in the same Internet Information Services (IIS) application as the caller.
The Programming Interface of the Service
Table 6-6 lists the methods of the server-side profile Web service.
Method |
Description |
---|---|
GetAllPropertiesForCurrentUser |
This method fills a dictionary of name/value pairs with values of all profile properties. |
GetPropertiesForCurrentUser |
This method takes a collection of property names and returns a dictionary filled with name/value pairs. |
SetPropertiesForCurrentUser |
This method takes a dictionary filled with name/value pairs and serializes such content to the profile data store. The method returns the number of properties updated. |
The exact signatures of the get methods are shown in the following code segment:
[WebMethod] public IDictionary GetAllPropertiesForCurrentUser(); [WebMethod] public IDictionary GetPropertiesForCurrentUser( string[] properties)
The properties argument lists the profile properties that you want to load in the JavaScript Sys.Profile object.
The SetPropertiesForCurrentUser method has the following signature:
[WebMethod] public int SetPropertiesForCurrentUser(IDictionary values)
It takes a dictionary of name/value property pairs as the input argument and updates the data store.
Because the ASP.NET AJAX service works on top of the ASP.NET profile subsystem, you can choose to bring in only a portion of the application data model coded in the web.config file. This tactic is normally a good move because it lets you move only the profile data you need to access from the client.
Configuration of the Profiling Service
The profile service requires a bit of configuration work. In particular, you need to explicitly enable the service through the section in the new section under :
...
To allow profile properties to be retrieved and modified from within ASP.NET AJAX applications, you also need to list properties in the readAccessProperties and writeAccessProperties attributes. The value of the readAccessProperties attribute is a comma (,) separated list of property names that you want to read. The writeAccessProperties attribute is a comma (,) separated list of property names that you want to write. If you intend for all properties to be available, you can use an asterisk (*) or list all properties explicitly.
The data model on which the profile service is based is defined through the
section under
Subsequently, the
section will take the form shown here:
As mentioned, writeAccessProperties and readAccessProperties can list a subset of the properties defined in the data model. The two sets of properties don’t have to match, meaning that you can enable a given profile property for reading from ASP.NET AJAX pages but not for writing (or vice versa). If your data model includes groups, you use the Group.Property convention to list the property in the
section. For example, in the preceding example the BackColor property belongs to the UI group; therefore, it is expressed as UI.BackColor in the configuration file.
Using the Profile Service in an Application
The profile service is tightly coupled with the authentication service in the sense that the profile object contains information specific to a given authenticated user. Anonymous users are allowed to have their own profile as long as you enable the anonymous user service in the configuration file. The anonymous user service will generate a Guid to identify the user; the profile of an anonymous user can then be migrated to the profile of a registered user when the anonymous user registers. In the next example, we consider only registered users.
Note |
For more information on the ASP.NET profile subsystem, take a look at Chapter 5 of my book Programming Microsoft ASP.NET 2.0 Applications: Core Reference (Microsoft Press, 2005). |
Profile-enabled applications should implement a number of features, including the following ones:
- Pages using profile information should have some server-side code in their Page_Load handler to ensure that child controls are correctly set up in case of full postbacks, redirects, or direct browser access.
- There should be one or more pages where registered users can edit their profile. These pages should then save changes to the profile data store.
- Login/Logout buttons should be provided to let users log in and log out at will.
If ASP.NET AJAX Extensions is employed, the user interface of profile-enabled pages should also be updated via script to immediately reflect a new logged-in user or changes to the profile object.
Initializing a Profile-Enabled Page
Figure 6-5 illustrates a sample page that uses the authentication service to let users log in and uses the profile service to read and update the profile.
After the user is connected, the user interface adapts to reflect the contents of the profile. The code-behind class can hide and display child controls based on the authentication state of the request. If a user is connected, the code in Page_Load typically applies profile information. In this case, it simply consists of setting the page background color:
protected void Page_Load(object sender, EventArgs e) { if (Request.IsAuthenticated) { LoggedIn.Style["display"] = ""; NotLoggedIn.Style["display"] = "none"; LoggedUser.Text = User.Identity.Name; ApplyProfile(); } else { LoggedIn.Style["display"] = "none"; NotLoggedIn.Style["display"] = ""; LoggedUser.Text = ""; } } protected void ApplyProfile() { // Set page background color string bgColor = Profile.UI.BackColor; if (!String.IsNullOrEmpty(bgColor)) Body.Style["background-color"] = bgColor; }
The preceding code is purely classic ASP.NET code. This ensures that the page works correctly if invoked from the address bar, if invoked from a hyperlink, or if the user navigates back or forward to it. The same logic must also be expressed through script code for when the user connects using the ASP.NET AJAX authentication service or edits the profile using the profile Web service from the client.
Loading the User Profile
The following script loads the profile data after the user logs in. It uses the load method on the Sys.Services._ProfileService object to connect to the server data store and download the user profile.
function OnLogin() { Sys.Services.AuthenticationService.login( username.value, password.value, false, onLoginCompleted); return false; } function onLoginCompleted(result) { if (result) loadProfile(); else alert("Sorry, your credentials appear to be invalid. Try again."); } function loadProfile() { Sys.Services.ProfileService.load(null, loadProfileCompleted, loadProfileFailed, null); } function loadProfileCompleted(result, context, methodName) { var bgColor; bgColor = Sys.Services.ProfileService.properties["UI"]["BackColor"]; $get("PageBody").style.backgroundColor = bgColor; } function loadProfileFailed(result, context, methodName) { alert(result.get_message()); }
The first argument of the load method is an array that indicates the properties to load-a string array. If set to null, the method retrieves all properties in the profile. The load method works asynchronously. For this reason, you need a callback to execute any code that uses the profile information.
To access a particular profile property, you use the properties collection. You select an element in the collection using the property name as the indexer, as shown here:
var temp = Sys.Services.ProfileService.properties["MyProperty"];
If properties are grouped, you use a multidimensional array, as shown next:
var temp = Sys.Services.ProfileService.properties["Group"]["MyProperty"];
In the preceding code, the loadProfileCompleted function sets the page background color. The code assumes that the page’s body is flagged runat=server and is given an ID named PageBody. The name of the ID is arbitrary. The tag must be a server-side tag for you to change the background color programmatically from within an ASP.NET page. The tag isn’t necessary if you only want to get and set the color via script. In this case, in fact, the sole ID would suffice. However, as mentioned, profile-enabled pages must also have a server-side version of any client code that manages profile data.
Updating the Profile
The sample application is completed by a page that allows each user to select a color to fill the page background. The page to edit colors is a pure ASP.NET server page that reads and saves profile data using the server Profile object. What would be necessary to save profile information directly from the client? Let’s take a look at the markup at the bottom of the page in Figure 6-5:
When the user clicks the Save Profile button (shown in Figure 6-5), the page saves the profile through the following code:
function saveProfile() { // Proceed only if the user is authenticated var loggedIn = Sys.Services.AuthenticationService.get_isLoggedIn(); if (!loggedIn) { alert("You must be logged in to try this"); return; } // Save client data to the profile var clr = newBgColor.value; Sys.Services.ProfileService.properties["UI"]["BackColor"] = clr; Sys.Services.ProfileService.save(null, saveProfileCompleted, null, null); }
The click event handler first checks whether the current user is authenticated. If so, it updates the profile properties and proceeds calling the save method on the Sys.Services._ProfileService client class. The first argument for the save method is an array with string/object pairs of properties to persist. If the argument is null, all properties in the data model will be saved. Figure 6-6 shows the page we saw in Figure 6-5 slightly updated (only background color) to reflect the profile of the logged-in user.
Note |
The ProfileService class in ASP.NET AJAX Extensions employs a built-in Web service to read and write profile properties on the server. It is just a client wrapper for the ASP.NET server Profile object which, in turn, uses a profile provider to store user-specific data. You can use server pages and AJAX pages in the same application that use the profile data. To be successful, though, do not rely on AJAX only; instead, make sure that your AJAX-enabled pages have a Page_Load event handler to apply profile data each time the page is loaded. |
Conclusion
Web services are components hosted by Web applications whose functions are accessible using standard protocols. Web services represent black-box functionality that can be reused without worrying about how the service is implemented internally. In the context of ASP.NET AJAX applications, Web services also represent a neat way to access server-side functionality from the client. In the next chapter, we’ll explore the techniques and machinery necessary to design and support new Web services that are callable from the client.
In this chapter, we reviewed a couple of built-in Web services that connect two pillars of ASP.NET development with JavaScript classes. User authentication and management of user profile information can be accomplished from the client thanks to a couple of built-in JavaScript proxies and hard-coded ASP.NET Web services. Although in ASP.NET AJAX Extensions the authentication and profile services are presented as two stand-alone pieces of functionality, they share the same pattern and machinery with user-defined Web services invoked from the client. In the next chapter, we’ll focus on learning more about calling remote code from the client.
Remote Method Calls with ASP NET AJAX
Категории