5.2. ASP.NET Application Services

ASP.NET AJAX provides a client framework that simplifies JavaScript development, but as you have seen, it also provides server-focused solutions for easily accessing Web services and localizing script resources. In addition, ASP.NET AJAX makes it easy to leverage the user authentication, roles, and profile management features of ASP.NET.

Authentication and role services allow you to remotely check user credentials in JavaScript against a central user repository. ASP.NET provides a default database and authentication scheme that you can use. The profile service allows you to store and retrieve data for each authenticated user on the server. ASP.NET AJAX makes this information available from JavaScript running in the browser.

5.2.1. Authentication Service

ASP.NET Forms Authentication allows you to store user credentials in a database or other back end without creating individual Windows accounts for every user. ASP.NET AJAX makes it possible to authenticate users against the server storage directly from JavaScript in the browser.

First, Forms Authentication must be enabled for the application that requires credentials using the authentication section under system.web in the application configuration file. The following configuration data from a web.config file shows setting the authentication mode to Forms and defining that cookies should be used to store the authentication ticket. ASP.NET supports carrying the authentication ticket in the URL of requests, but when authenticating from JavaScript, cookies must be used.

<authentication mode="Forms">
  <forms cookieless="UseCookies" />
</authentication>
<authorization>
  <deny users="?" />
  <allow users="*" />
</authorization>

That configuration will enable Forms Authentication using cookies. The deny element uses the question mark to indicate that the anonymous user is not allowed. The allow element uses the asterisk to allow all authenticated users.

That is enough to get Forms Authentication going for a Web request, but accessing it from JavaScript also requires explicitly setting it in the system.web.extensions configuration section. This is shown in the following configuration entry from an ASP.NET AJAX application.

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

By default, ASP.NET will create a database using Microsoft SQL Express for development. You can use the aspnet_regsql tool to populate your existing database with the tables and schema for using the default authentication providers.

With the configuration in place, you can call login and logout against the ASP.NET Authentication Service from JavaScript. The login method requires a username and password. You can also specify that the authentication cookie be persisted beyond when the browser is closed, any custom data that should be available in the callback, and the redirectUrl to use upon successful login. Of course, you can provide callback functions for success and failure.

Listing 5-11 (Login.aspx) includes code for logging a user in and out in the click handler of a button. The logout function has fewer parameters: the redirectUrl to use (use null for the current page), completion callbacks for logout, and a failed attempt to logout, and the userContext object to pass to the callbacks. The userContext is not sent to the server and then returned to the client. Instead, the AJAX infrastructure sets up the call to the server, puts the custom data aside, and passes it to the completion function when the call to the server is complete.

Example 5-11. Accessing the authentication service (Login.aspx)
<!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 id="Head1" runat="server">
 <title>ASP.NET AJAX Login</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="ScriptManager1" >
</asp:ScriptManager>
<div>Username: <input type="text" name="username" /></div>
<div>Password: <input type="password" name="password"</div>
<div>
    <input type="button" id="loginButton" value="Login"/><br />
    <input type="button" id="logoutButton" value="Logout" />
</div>

<div id="Results"></div>
 </form>
</body>
<script type="text/javascript">

function pageLoad() {
    document.getElementById('loginButton').attachEvent('onclick', login);
    $addHandler($get('logoutButton'), 'click', logout);
}

function login() {
    var u = $get('username').value;
    var p = $get('password').value;
    var isPersistent = false;
    var customInfo = null;
    var redirectUrl = null;
    Sys.Services.AuthenticationService.login(u, p, isPersistent, customInfo,
redirectUrl, loginCompleted, loginFailed);
}

function loginCompleted(result, context, methodName) {
    if(result) {
        alert("Successfuly logged in.");
    }
    else {
        alert("Failed to login.");
    }
}

function loginFailed(error, context, methodName) {
    alert(error.get_message());
}
function logout() {
    Sys.Services.AuthenticationService.logout('Default.aspx'),
}
</script>
</html>

When using the built-in Authentication Service, it is enabled through configuration, as you've seen. But you have the flexibility to customize the membership feature beyond what is provided natively by ASP.NET. The AuthenticationService element of the ScriptManager allows you to set the path of the service to a custom Web service: <AuthenticationService path="authenticate.asmx" />. You can write your own service with the Login and Logout method signatures required and use it to validate credentials against any custom middle tier or back-end data store that you want. Listing 5-12 is an example of a service with the appropriate method signatures so that the JavaScript AuthenticationService classes will successfully call the custom implementation.

Example 5-12. A customized authentication service
<%@ WebService Language="C#" Class="Wrox.ASPAJAX.Samples.Authenticate" %>
using System.Web;
using System.Web.Security;
using System.Web.Services;
using System.Web.Script.Services;

namespace Wrox.ASPAJAX.Samples
{

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

        public bool Login(string userName, string password)
        {
            //storing passwords in code is a BAD security practice
            if (userName == "Matt" && password == "MyPassword!")
            {
                FormsAuthentication.SetAuthCookie(userName, false);
                return true;
            }
            return false;
        }

        [WebMethod]

        public void Logout()
        {
            FormsAuthentication.SignOut();
        }
    }
}

5.2.2. Role Service

The ASP.NET role management feature allows you to assign roles to user accounts. For example, some users may have the "premium member" role, or the "administrator" role. The capabilities of the site available to the user can depend on what roles they are a member of. Role data for the currently authenticated user can be made available from JavaScript using the built-in role service that ASP.NET AJAX provides.

To enable the role management feature, you use the roleManager section inside system.web. This is shown in the following example section from a web.config file.

<system.web>
   <roleManager enabled="true" />
</system.web>

The role service for access from JavaScript must be enabled separately. This is so that you have to opt in to the ability to retrieve role information from the client. Only the roles for the currently authenticated user can ever be retrieved, but it is still a good idea only to enable client access to them if you need it.

To enable the Role service, you use the roleService section inside the system.web.extensions section, as shown here.

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

The role service allows you to retrieve the current user's list of roles using the load function. This initiates a request to the server, which completes asynchronously. That operation will either succeed or fail, so the load function also takes success and failure callback parameters. After roles have been successfully loaded, you can use the isUserInRole method to test for the membership of the user in a particular role, or you can use the get_roles property to access the list directly. Listing 5-13 (Role.aspx) shows a simple page that loads the roles, displays them, and tests for the PremiumMember role.

Example 5-13. Accessing the role service (Role.aspx)
<!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 id="Head1" runat="server">
    <title>ASP.NET AJAX Role Service</title>
    <script type="text/javascript">
    function pageLoad() {
        Sys.Services.RoleService.load(loadCompleted, loadFailed);
    }

    function loadCompleted() {
        var roles = Sys.Services.RoleService.get_roles();
        var isPremium = Sys.Services.RoleService.isUserInRole("PremiumMember");
        var rolesSpan = $get('roles'),
        rolesSpan.innerHTML = roles.join(',') + " Premium Member? " + isPremium;
    }

    function loadFailed(error, context, methodName) {
        alert(error.get_message());
    }
    </script>

</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div>
        Your roles: <span id="roles"></span>
    </div>
    </form>
</body>
</html>

Loading roles from the server takes time to complete because the server must issue a request to the Role service asynchronously. If you would rather the roles be available immediately when the page loads, you can configure ScriptManager to emit them with the page rendering, eliminating the need to load them asynchronously later. To load roles automatically, you set the LoadRoles property of the RoleService element of the ScriptManager to true.

<asp:ScriptManager runat="server" ID="ScriptManager1">
    <RoleServices LoadRoles="true" />
</asp:ScriptManager>

Even if you automatically load roles, you might still have a need to load them asynchronously later on. For example, if you use the ASP.NET AJAX authentication service to log a different user in, the roles will be incorrect until they are loaded again. You might also be performing asynchronous operations that cause changes to the roles on the server, which would also require a refresh.

5.2.3. Profile Service

The ASP.NET profile feature allows you to automatically store and retrieve data for each user of a Web application. It can even be enabled for anonymous users that have not been authenticated. ASP.NET will issue a unique identifier to the user to be able to retrieve the profile data on subsequent requests. The service must be enabled for ASP.NET and additionally for JavaScript access from the browser. In your application's web.config file, you use the profile section inside system.web to enable profile management. This is shown in the following example section from a web.config file.

<system.web>
    <anonymousIdentification enabled="true"/>
    <profile>
        <properties>
            <add name="favoriteArtist" type="System.String"/>
            <add name="favoriteAlbum" type="System.String"/>
        </properties>
    </profile>
</system.web>

There are no default properties tracked for personalization. They must be defined inside the properties element of the profile configuration section. I have defined favoriteArtist and favoriteAlbum string properties. Because space is consumed in the database to track the personalization data for each user, it is disabled by default for anonymous users. Once it is enabled with the anonymousIdentification element, every unique visitor to the Web application will be issued a unique identifier, and an entry will be created for them in the database.

The profile service must be enabled separately for direct client access. This is so that you have to opt in to the ability to retrieve and update personalization data remotely. Without requiring users to be authenticated, it is possible for spoof requests to be sent to the application and modify the data.

The system.web.extensions section has a profileService section where it is enabled. You also define what profile properties should be readable and which should be writable through the service, which may not be the same as those available through directly accessing the profile management APIs on the server. In other words, when accessed directly from the server, all properties are accessible, but you can limit the access further for JavaScript access. The following configuration element from the <system.web.extensions> section of the web.config file sets up both properties for read and write access.

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

Now JavaScript code in a page can directly store and retrieve user personalization data. In Listing 5-14 (Profile.aspx), several items are populated in the user profile from code running on the server. Once the data is stored in the profile, it will be available automatically in subsequent requests. In the ProfileService element of the ScriptManager, the LoadProperties attribute can be set to a comma-delimited list of Profile properties that should be prepopulated in the JavaScript sent to the browser, similar to the role service LoadRoles feature. This is useful for profile information used often, and you want to avoid having to call back to the server later to retrieve it. This code tells it to load the favoriteArtist property but not favoriteAlbum.

ASP.NET allows you to plug in your own provider so you can easily customize where the data is stored. For these examples, I am using a custom Profile data provider that just stores the data in memory temporarily rather than setting up a database.

There are two buttons on the page. One is the showArtist button and the other is showAlbum. The click handler for the showArtist button accesses the profile property directly and displays it. Because the album information was not prepopulated, the showAlbum handler has to perform a callback to get the data. It sets up success- and failure-completion functions to be called when the callback is complete. The success callback updates the album to a new value. After a postback, the showAlbum handler will retrieve the new value from the profile service.

Example 5-14. Accessing the profile service (Profile.aspx)
<%@ Page Language="C#"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Profile Service</title>
</head>

<script runat="server" language="c#">
void Page_Load() {
    if(!this.IsPostBack) {
        Profile.favoriteArtist = "Phish";
        Profile.favoriteAlbum = "Rift";
    }
}
</script>
<body>
 <form id="form1" runat="server">
 <asp:ScriptManager runat="server" ID="ScriptManager1" >

 <ProfileService LoadProperties="favoriteAlbum" />
 </asp:ScriptManager>
 <div>
 <input type="button" value="Get Artist" id="showArtist" /><br />
 <input type="button" value="Get Album" id="showAlbum" /></div>
 </form>
</body>
<script type="text/javascript">

function pageLoad() {
    $addHandler($get('showArtist'), 'click', displayArtist);
    $addHandler($get('showAlbum'), 'click', displayAlbum);
}

function displayArtist() {
    var p = Sys.Services.ProfileService;
    p.load(["favoriteArtist"], loadCompleted, loadFailed);
}

function loadCompleted(count) {
    alert(Sys.Services.ProfileService.properties.favoriteArtist);
}

function loadFailed() {
    alert('load failed'),
}

function displayAlbum() {
    alert(Sys.Services.ProfileService.properties.favoriteAlbum);
    Sys.Services.ProfileService.properties.favoriteAlbum = "Round Room";
    Sys.Services.ProfileService.save(["favoriteAlbum"]);
}
</script>
</html>

The Profile Service example makes use of basic string properties, but you can also easily use more complex types. ASP.NET AJAX uses JSON (JavaScript Object Notation) serialization to move data back and forth between client and server. If you define an Album type with properties for AlbumName and Artist, the Profile Service will automatically manage the type in .NET code on the server and in JavaScript in the browser. Listing 5-15 (Album.cs) is such a type.

Example 5-15. Using custom types (Album.cs)
using System;
using System.Web;
using System.Web.UI;

namespace Wrox.ASPAJAX.Samples {

public class Album {
    private string _artist = String.Empty;
    private string _name = String.Empty;

    public Album() {
    }

    public Album(string artist, string name) {
        _artist = artist;
        _name = name;
    }

    public string Artist {
        get {
            return _artist;
        }
        set {
            _artist = value;
        }
    }

    public string AlbumName {
        get {
            return _name;
        }
        set {
            _name = value;
        }
    }
}
}

Adding this type to the Profile section of web.config makes the type available for inclusion in the LoadProperties attribute of the ProfileService element of ScriptManager. The Profile Service will then serialize the type as a JSON object both when reading and writing the property. Instead of setting each property individually, as shown here:

Sys.Services.ProfileService.properties.nextAlbumToBuy.Artist = 'Phish';
Sys.Services.ProfileService.properties.nextAlbumToBuy.AlbumName = 'Round Room';

You can assign it as a JSON object and know that the Profile Service will be able to serialize it and send it to the server for deserialization:

Sys.Services.ProfileService.properties.nextAlbumToBuy = { Arist: 'Phish',
AlbumName: 'Round Room' };

One thing that caught me off guard in building this sample was the behavior of the success callback for saving Profile properties. Notice that the save and load methods of the Sys.Services.ProfileService class take an array of properties. This allows you to save and load multiple profile properties in one call. The success callback will be called even when there is a problem updating all of the Profile properties. The success callback has an argument that is the count of the number of properties that were successfully updated. If there are problems updating some, or even all, of the properties included, the success callback will be called unless there is some fundamental problem with the service call itself.

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

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