ASP.NET Application Services

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 and profile management features of ASP.NET 2.0.

Authentication 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. Profile services allow 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.

Forms Authentication

ASP.NET Forms Authentication allows you to store user credentials in a database or other backend 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>
    <authenticationService enabled="true" />
</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, completion callbacks for logout and a failed attempt to logout, and the userContext object to pass to the callbacks. The userContext ele-ment 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.

Listing 5-11
Image from book
<!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')
    $addHandler($get('logoutButton'),
}

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.
}
</script>
</html>
Image from book

Notice in Listing 5-12 (Authenticate.asmx) that there is an AuthenticationService element without any attributes. It is actually not required for this example. When using the built-in Authentication service, it is enabled through configuration, as you’ve seen. But you see that you have the flexibility to customize the membership feature beyond what is provided natively by ASP.NET. The AuthenticationService element 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 backend data store that you want. Listing 5-20 is an example of a service with the appropriate method signatures so that the JavaScript AuthenticationService classes will successfully call the custom implementation.

Listing 5-12
Image from book
<%@ 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();
        }
    }
}
Image from book

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 the service. 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 ele-ment, 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 writeable. When accessed directly from the server, all properties are read-write, 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.

<profileService enabled="true"
readAccessProperties="favoriteArtist,favoriteAlbum"
writeAccessProperties="favoriteArtist,favoriteAlbum" />

Now I can include JavaScript code in a page that directly stores and retrieves user personalization data. In Listing 5-13 (Profile.aspx), I first populate several items 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. 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 the favoriteAlbum.

Listing 5-13
Image from book
<%@ 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'),
    $addHandler($get('showAlbum'),
}

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>
Image from book
Tip 

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.

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. Album.cs in Listing 5-14 is such a type.

Listing 5-14
Image from book

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;
        }
    }
}
}
Image from book

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. 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.