Default.aspx 頁面預覽
默認情況下SignIn.aspx在登錄成功后會導航到Default.aspx頁面,所以我們先簡單的構建一下Default.aspx頁面,看看實現的效果:
<asp:LoginView ID="LoginView1" runat="server">
<AnonymousTemplate>
歡迎訪問, 游客 !
</AnonymousTemplate>
<LoggedInTemplate>
你好, <asp:LoginName ID="LoginName1" runat="server" /> ! <br />
<strong>UserData值:</strong>
<asp:Literal ID="lbUserData" runat="server" />
</LoggedInTemplate>
</asp:LoginView>
<br />
<asp:LoginStatus ID="LoginStatus1" runat="server" LogoutPageUrl="~/Logout.aspx" LogoutAction="Redirect" />
類似地,我們放置了一個LoginView控件,只是這里我們多放置了一個LoginStatus控件。接下來我們看一下后置代碼:
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
if (Request.IsAuthenticated) {
FormsIdentity identity = User.Identity as FormsIdentity;
string userData = identity.Ticket.UserData;
Literal lbUserData = LoginView1.FindControl("lbUserData") as Literal;
lbUserData.Text = userData;
}
}
}
最后我們先進行登錄,然后再打開Default.aspx頁面,會看到類似這樣的輸出:
至此,我們已經看到了如何利用FormsAuthentionTicket來附帶額外的用戶數據,但是我們應該看到這種做法存在的問題:可以保存的數據過於單一,僅僅只是一個字符串。而我們第一節中所介紹的用戶表包括各種類型的各種數據。如果你看過了 從一個范例看XML的應用 這篇文章,你應該立刻想到此處又是一個“單一字符串保存多種不同類型數據”的應用場景,我們可以定義XML來解決。對於這種方式,我不再演示了。實際上,我們可以自定義一個IPrincipal和IIdentity來完成,接下來就來看一下。
自定義IPrincipal和IIdentity
不管是在Windows上還是在Web上,.Net都使用這兩個接口來實現用戶的身份驗證。它們不過是一個接口,實現了這兩個接口的類型附帶了用戶的信息,最終被賦予線程(Windows)或Cookie(Web)來對用戶進行驗證。我們在App_Code下添加CustomPrincipal和CustomIdentity來實現這兩個接口:
public class CustomPrincipal : IPrincipal {
private CustomIdentity identity;
public CustomPrincipal(CustomIdentity identity) {
this.identity = identity;
}
public IIdentity Identity {
get {
return identity;
}
}
public bool IsInRole(string role) {
return false;
}
}
public class CustomIdentity : IIdentity {
private FormsAuthenticationTicket ticket;
private HttpContext context = HttpContext.Current;
public CustomIdentity(FormsAuthenticationTicket ticket) {
this.ticket = ticket;
}
public string AuthenticationType {
get { return "Custom"; }
}
public bool IsAuthenticated {
get { return true; }
}
public string Name {
get {
return ticket.Name;
}
}
public FormsAuthenticationTicket Ticket {
get { return ticket; }
}
// 這里可以是任意來自數據庫的值,由Name屬性取得
// 需要注意此時已通過身份驗證
public string Email {
get {
HttpCookie cookie = context.Request.Cookies["Email"];
if (cookie==null || String.IsNullOrEmpty(cookie.Value)) {
string type = "jimmy_dev[at]163.com"; // 實際應根據name屬性從數據庫中獲得
cookie = new HttpCookie("UserType", type);
cookie.Expires = DateTime.Now.AddDays(1);
context.Response.Cookies.Add(cookie);
}
return cookie.Value;
}
}
public string HomePage {
get {
HttpCookie cookie = context.Request.Cookies["HomePage"];
if (cookie==null || String.IsNullOrEmpty(cookie.Value)) {
string name = "www.tracefact.net"; // 實際應根據name屬性從數據庫中獲得
cookie = new HttpCookie("NickName", name);
cookie.Expires = DateTime.Now.AddDays(1);
context.Response.Cookies.Add(cookie);
}
return cookie.Value;
}
}
}
注意這里的HomePage和Email這兩個屬性,它們攜帶了我們的用戶數據,這里我僅僅是對它們進行了一個簡單的賦值,實際的數值應該是來自於數據庫。還要注意獲取到它們的值后被保存在了Cookie中,以避免頻繁的對數據庫進行訪問。
定義了實現這兩個接口的對象之后,我們還需要把它嵌入到應用程序的生命周期中,具體的做法就是掛接到HttpModule或者是重寫Global.asax中的事件,這里我采用了重寫Global.asax事件的方式,因此創建一個Global.asax文件,然后添加如下代碼:
void Application_OnPostAuthenticateRequest(object sender, EventArgs e) {
IPrincipal user = HttpContext.Current.User;
if (user.Identity.IsAuthenticated
&& user.Identity.AuthenticationType == "Forms") {
FormsIdentity formIdentity = user.Identity as FormsIdentity;
CustomIdentity identity = new CustomIdentity(formIdentity.Ticket);
CustomPrincipal principal = new CustomPrincipal(identity);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
}
這段代碼很好理解,它不過是在應用程序的PostAuthenticateRequest事件中用我們自定義的CustomPrincipal和CustomIdentity替換掉了默認的IPrincipal和IIdentity實現。
Default.aspx頁面預覽
我們再次對Default.aspx進行修改,添加兩個Literal控件,用於顯示我們自定義的數值:
自定義Identity中的值:<br />
<strong>Email:</strong>
<asp:Literal ID="ltrEmail2" runat="server"></asp:Literal><br />
<strong>HomePage:</strong>
<asp:Literal ID="ltrHomePage" runat="server"></asp:Literal><br />
然后修改頁面的代碼,使用我們的自定義CustomIdentity,然后從中獲得自定義的屬性值:
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
if (Request.IsAuthenticated) {
CustomIdentity identity = User.Identity as CustomIdentity;
if (identity != null) {
// 獲得UserData中的值
string userData = identity.Ticket.UserData;
Literal lbUserData = LoginView1.FindControl("lbUserData") as Literal;
lbUserData.Text = userData;
// 獲得identity中的值
ltrEmail2.Text = identity.Email;
ltrHomePage.Text = identity.HomePage;
}
}
}
}
如果你現在打開頁面,將會看到類似下面的頁面:
可以看到我們獲得了定義在CustomIdentity中的屬性。注意這里我只是做了一個示范,因此只在CustomIdentity中包含了Email和HomePage兩個屬性值,如果看到此處你便以為大功告成,然后將所有未完成的屬性都添加到CustomIdentity中去就大錯特錯了。Identity的目的只是為你提供一個已經登錄了的用戶的名稱,而不是攜帶所有的用戶信息,這些信息應該由其他的類型提供。因此微軟才定義了MemberShipUser類型和Profile。從這個角度上來看,自定義IPrincipal和IIdentity並沒有太大的意義。
這里,我們最好是定義一個自己的類型來承載用戶數據,下面我們就看下如何完成。
自定義類型攜帶用戶數據
在App_Code中新建一個SiteUser類,它的實現如下,簡單起見,我使用了公有字段而非屬性:
public class SiteUser
{
public string Name;
public string UserImage;
public DateTime RegisterDate;
public string Email;
public string HomePage;
public int PostCount;
public int ReplyCount;
public byte Level;
public SiteUser(AuthDataSet.UserRow userRow) {
this.Email = userRow.Email;
this.HomePage = userRow.Homepage;
this.Level = userRow.Level;
this.Name = userRow.Name;
this.PostCount = userRow.PostCount;
this.RegisterDate = userRow.RegisterDate;
this.ReplyCount = userRow.ReplyCount;
this.UserImage = userRow.UserImage;
}
// 實際應該由數據庫獲得
public static SiteUser GetUser(string name) {
AuthDataSetTableAdapters.UserTableAdapter adapter
= new AuthDataSetTableAdapters.UserTableAdapter();
AuthDataSet.UserDataTable userTable = adapter.GetUserTable(name);
if(userTable.Rows.Count >0){
return new SiteUser((AuthDataSet.UserRow)userTable.Rows[0]);
}
// 因為調用這個方法時,name應該是有效的,
// 如果name無效,直接拋出異常
throw new ApplicationException("User Not Found");
}
}
它的GetUser()靜態方法根據用戶的名稱獲得了一個SiteUser對象,這里需要注意的是通常調用這個方法時,用戶已經登錄過了,也就是說其name參數總是有效的,因此當搜索數據庫找不到記錄時,我簡單地拋出了異常。