
4.4 AuthenticationService
ASP.NET AJAX内建了两个关于使用者的Service:AuthenticationService及ProfileService,AuthenticationService是用来验证使用者的,它与ASP.NET既有的使用者管理机制结合,提供了以JavaScript来验证使用者的途径。嗯!听来似乎不错,那何时会需要用JavaScript,而非传统的ASP.NET Login系列控件来验证用户呢?嗯,很有趣的问题,笔者能想到的情况只有一种,就是制作类似Yahoo、PCHome等门户网站时,首页上会放上一个区块供使用者登录网站用,在使用者登录网站后,首页上会添加一些只有已登录者才能使用的功能,如修改用户密码等,这原本并不难,只要利用ASP.NET所提供的Login控件便可轻易达到,难的是在原来的首页上本来就有很多内容,如果想使用者登录后不产生网页刷新的动作,那就比较麻烦了。此时单单使用Login控件是很难达到此需求的,即使将Login控件放在UpdatePanel控件内,也不能阻止其内建的行为:登录后转向特定网页!此时便是使用AuthenticationService的最佳时机了。请照着以下步骤做。
1. 建立一个新Web Site,命名为AuthenticationTest。
2. 配置此Web Site的使用者认证机制,并添加使用者。
3. 新建一个文件夹:SecureFolder。
4. 在SecureFolder中加入一个新网页:SecureForm.aspx,内容如程序4-11所示。
5. 在Web Site的根目录下建立一个UserControl:LoginArea.ascx,内容如程序4-12所示。
6. 在Web Site的根目录下建立一个JavaScript程序文件:JScript.js,内容如程序4-13所示。
7. 修改web.config中的authentication区段,如程序4-14所示。
8. 在Default.aspx中加入ScriptManager控件。
9. 在Default.aspx中放入LoginArea这个UserControl。
程序4-11
Samples\4\AuthenticationTest\SecureFolder\SecureForm.aspx <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SecureForm.aspx.cs" Inherits="SecureFolder_SecureForm" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text= "i am secure Form"></asp:Label> </div> </form> </body> </html>
程序4-12
Samples\4\AuthenticationTest\LoginArea.ascx <%@ Control Language="C#" AutoEventWireup="true" CodeFile="LoginArea.ascx.cs" Inherits="LoginArea" %> <table border="0" cellpadding="0" cellspacing="0"> <tr> <td style="width: 57px"><span id="lbAccount">账号:</span></td> <td> <input type="text" id="txtUserName" /> </td> </tr> <tr> <td style="width: 57px"><span id="lbPassword">密码:</span></td> <td> <input type="password" id="txtPassword" /> </td> </tr> <tr> <td style="width: 57px"></td> <td> <input id="btnLogin" type="button" value="登录" onclick="OnClickLogin()" /> <span id="lbErrorInfo"></span></td> </tr> </table>
程序4-13
Samples\4\AuthenticationTest\JScript.js // JScript File //声明储存HTML控件的变量 var username; var password; var buttonLogin; var errorInfo; var lbUserName; var lbPassword; //pageLoad函数是ASP.NET AJAX Client Library在网页载入时默认会调用的函数 function pageLoad() { //取得用户名称的Text控件之HTML对象 username = $get("txtUserName"); //取得密码的Text控件之HTML对象 password = $get("txtPassword"); //取得登录按钮的HTML对象 buttonLogin = $get("btnLogin"); //取得显示错误用的HTML对象 errorInfo = $get("lbErrorInfo"); //取得用户名称之提示HTML对象 lbUserName = $get("lbAccount"); //取得密码之提示HTML对象 lbPassword = $get("lbPassword"); } //于注销时调用 function OnClickLogout() { //调用此函数,可以将现行用户注销 Sys.Services.AuthenticationService.logout(null, null, null, null); } //失败时调用的函数 function OnFailed(error, userContext, methodName) { errorInfo.innerText = error.get_message(); } //登录验证完毕时调用的函数 function OnLoginCompleted(validCredentials, userContext, methodName) { password.value = ""; if (validCredentials == true) { username.value = ""; password.value = ""; username.style.visibility = "hidden"; password.style.visibility = "hidden"; lbUserName.style.visibility = "hidden"; lbPassword.style.visibility = "hidden"; // Hide login fields. buttonLogin.value = "注销"; errorInfo.innerText = ""; } else { errorInfo.innerText = "登录失败"; buttonLogin.value = "登录"; } } //注销完毕后调用的函数 function OnLogoutCompleted(result) { buttonLogin.value = "注销"; username.style.visibility = "visible"; password.style.visibility = "visible"; lbUserName.style.visibility = "visible"; lbPassword.style.visibility = "visible"; } function OnClickLogin() { Sys.Services.AuthenticationService.set_defaultLoginCompletedCallback( OnLoginCompleted); Sys.Services.AuthenticationService.set_defaultLogoutCompletedCallback( OnLogoutCompleted); Sys.Services.AuthenticationService.set_defaultFailedCallback(OnFailed); if(buttonLogin.value == "注销") OnClickLogout(); Sys.Services.AuthenticationService.login(username.value, password.value, false,null,null,null,null,"User Context"); }
程序4-14
Samples\4\AuthenticationTest\web.config <system.web> ............... <authentication mode="Forms"> <forms loginUrl ="Default.aspx"/> </authentication> </system.web> <system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" /> </webServices> </scripting> </system.web.extensions>
这个范例是较复杂的,首先请看程序4-14 的web.config片段,ASP.NET AJAX的Authentication Service默认是不加载的,要使用这个Service必须先在web.config中将其设为自动加载,也就是将authenticationService区段的enabled设为True。接着便是准备登录所使用的UI界面,本例将UI界面设计成UserControl,方便套用至任何网页中,当使用者点击登录按钮后,位于程序4-13中的OnClickLogin函数便会被调用,该函数一开始是通过Authentication Service所提供的JavaScript对象:Sys.Services.AuthenticationService中的3 个函数来设定调用Authentication Service时所需要的3个Callback函数,它们分别是:
1. 登录验证完毕时调用的函数,此处设定为OnLoginCompleted。
2. 注销完毕时调用的函数,此处设定为OnLogoutCompleted。
3. 调用Authentication Service失败时调用的函数,此处设定为OnFailed。
设定好这些Callback函数后,OnClickLogin函数会先判断目前的状态。若为已登录,则执行注销动作,若为未登录则执行登录动作,也就是调用Sys.Services.AuthenticationService.login函数进行该用户的登录程序,当登录验证完毕,OnLoginCompleted函数就会被调用,传入的validCredentials参数代表着该使用者是否验证通过,True表示此使用者已验证通过,False则是验证失败,此处所做的只是调整UI界面的可视与不可视而已。在注销部分,此例是调用了Sys.Services.AuthenticationService.logout函数来注销现行使用者,此动作执行完毕OnLogoutCompleted函数就会被调用,此处同OnLoginCompleted一样,只是调整UI界面的可视与不可视状态。在这个例子中还有两个部分尚未提及,一是pageLoad函数,在ASP.NET AJAX的网页中,如将JavaScript的函数取名为pageLoad,代表着该函数会在网页载入时、Async-Postback刷新完成后调用,这是由ASP.NET AJAX Client Framework所负责调用的,此例中的pageLoad函数以$get函数将所有相关的HTML对象取出并存在变量中,供后面的OnClickLogin等函数使用。第二个未提及的是错误的控管机制,在LoginArea.ascx中有着如程序4-15所示的HTML代码。
程序4-15
<span id="lbErrorINfo"></span>
这是用来显示错误信息用的,当登录验证失败,或者注销失败时,OnLoginCompleted或OnFailed函数会将错误信息设定给HTML对象来显示。执行此程序后,会见到如图4-6 所示的画面。

图4-6
输入用户及密码,点击登录按钮后便完成了登录动作(本例是admin、admin@test),之后原本的登录按钮就会变成注销,其他的UI控件便会变成不可视,这是在OnLoginCompleted函数中完成的,如图4-7所示。

图4-7
接着点击Go Secure Form链接,便会进入只有登录者才能浏览的网页,如图4-8所示。

图4-8
若未登录而企图浏览SecureForm.aspx,将会被导回Default.aspx,这是ASP.NET使用者管理机制的默认行为。Authentication Service的特色是登录及注销时,都不需要任何的网页刷新或是部分刷新动作,这满足了前面提及的希望在不刷新网页情况下完成登录的需求。虽说如此,但是为了这件事而大费周章写下一堆JavaScript程序代码,似乎有点不计时间成本,难道就没有比较简单的方法达到同样的效果吗?事实上是有的,只要利用Login控件及UpdatePanel控件就可以达到同样的效果,等会!刚刚不是说不行吗?怎么这下又说行了?别误会,我只是说不容易达到,因为Login控件在登录成功后会导向特定网页,这个动作会使UpdatePanel控件的部分刷新完全无用武之地,但是!如果可以让Login控件在登录成功后不导向其他网页,那UpdatePanel的部分刷新便可以派上用场,这样一来,登录的动作便不会影响到整个网页,而只是刷新Login控件所在的UpdatePanel控件而已。那具体的做法呢?请照以下的步骤做。
1. 建立一个新网页,命名为Default2.aspx。
2. 加入一个ScriptManager控件。
3. 加入一个UpdatePanel控件。
4. 在UpdatePanel控件中加入一个HyperLink控件,NavigateUrl设为SecureForm.aspx。
5. 在UpdatePanel控件中加入一个Login控件,命名为Login1。
6. 在UpdatePanel控件中加入一个LinkButton控件,命名为LinkButton1,Text设为Logout,Visible设为False。
7. 在LinkButton1的Click事件中键入程序4-16中的LinkButton1_Click函数中的代码。
8. 在Login1的LoggingIn事件中键入程序4-16中的Login1_LogginIn函数中的代码。
程序4-16
Samples\4\AuthenticationTest\Default2.aspx.cs using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class Default2 : System.Web.UI.Page { private bool _customAuthed = false; protected void Page_Load(object sender, EventArgs e) { } protected void Page_PreRender(object sender, EventArgs e) { if (Membership.GetUser() != null) { Login1.Visible = true; LinkButton1.Visible = true; } else if(!_customAuthed) { Login1.Visible = true; LinkButton1.Visible = false; } } protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e) { if (Membership.Provider.ValidateUser(Login1.UserName, Login1.Password)) { FormsAuthentication.SetAuthCookie(Login1.UserName, Login1.RememberMeSet); Login1.Visible = false; LinkButton1.Visible = true; _customAuthed = true; e.Cancel = true; } } protected void LinkButton1_Click(object sender, EventArgs e) { FormsAuthentication.SignOut(); } }
在执行程序前,请先将web.config中的forms区段所设定的loginUrl改为Default2.aspx,完成后便可执行程序,如图4-9所示。

图4-9
输入用户及密码后点击Log In按钮,此时便可看到Logout的联结出现,再点击Go Secure Form,即可浏览仅登录者所能浏览的SecureForm.aspx,重点是!这个登录动作只刷新了Login控件所在的UpdatePanel控件,不会影响到整个网页,如图4-10所示。

图4-10
由程序可以看出,这个例子运用了Login控件所提供的自定义验证用户功能,以手动验证使用者后告知Login控件的验证动作已取消,阻止后续的网页导向动作的方式。这种方式也不是没有缺点,缺点是LoginName、LoginStatus等控件无法在第一次登录时反应过来,致使出现不正常的状态,所以此例才未使用这些控件,改用LinkButton控件来提供Logout功能。那Authentication Service与这个方法哪种较好呢?个人认为,Authentication Service的难用之处在于若要从头一个字一个字地建立JavaScipt程序代码,感觉有点麻烦且难以除错,但是只要有一个既有的Script样板,也就是程序4-13的JScript.js后,日后只要针对使用的网页来进行细微调整即可。第二种使用Login控件的方式虽然看起来很不错,但或许会让人感觉有点取巧,尤其是在性能上,使用UpdatePanel控件虽然可以部分刷新网页,但这仍无法掩盖其执行了一个Postback动作的事实,Server必须在Postback到达时重新建立网页上的所有控件、解读VeiwState,这些都是必须付出的代价。