經過上一章的例子 我們已經建立了一個標准的,有很多有趣(甚至有些是專業級)功能的登陸系統了。
可是我們如何管理這個系統呢?難道我們要用m$提供的asp.net管理工具管理一輩子麼?
——當然不!那太可怕了!T_T
——我們要自己寫一個後台,一個可以根據用戶權限自己修改的後台!@_@
當然有一種數據庫狂人,他們只冷冷的瞥了幾眼m$提供的數據庫結構,輕描淡寫的破譯了其中所有的奧妙之處,隨手拖了5-6個grid 寫了10多行SQL 就用數據庫方式搞定了。對於這種高手,我們仰慕,我們恨不得馬上吸光他的百年功力,然後殺之後快,NND.
但是成為這樣的高手需要非常的經驗和手腕。我們這些小菜鳥,沒有寫輪眼,也不是聖斗士,所謂“看穿”技能在我們的身上是不能工作的。我們只有membership標准對象 profile標准對象 和roles標准對象。
難道就不能很方便的通過綁定方式訪問這些對象麼?
通過頁面訪問較為復雜的對象——在.net 1.x 的時代——對我們曾是一種煎熬。明明好多對象有著數據行的特性,為什麼不能直接訪問呢?於是好多人---包括我,嘗試過各種辦法。我是失敗那批5555,也有很多的人成功了,研究出一些很有效的辦法。可是這個狀況沒有持續多久——自從.net 2.0推出了ODS ,我的失敗陰影就再也不復回來~~~
1 用ODS綁定MemberShip的總體思想
如圖所示
從戰略上 我們把每個用戶看成一個行,把Membership中的GetAllUsers ()看成一個Select語句。
但是一個標准的System.Web.Security.MembershipUser並不具有數據綁定對象的特性
比如主鍵/只讀等信息所以我們可以重寫它為它添加上這些特性
2 簡單的綁定方法
這裡采用的是MSDN上的部分c#代碼修改成的VB代碼 順便我也把版權信息粘貼上來
'/* 'Copyright ?2005, Peter Kellner 'All rights reserved. 'http://peterkellner.net 'Redistribution and use in source and binary forms, with or without 'modification, are permitted provided that the following conditions 'are met: '- Redistributions of source code must retain the above copyright 'notice, this list of conditions and the following disclaimer. '- Neither Peter Kellner, nor the names of its 'contributors may be used to endorse or promote products 'derived from this software without specific prior written 'permission. 'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS '"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 'LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 'FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 'COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 'INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, 'BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 'CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 'LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 'ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 'POSSIBILITY OF SUCH DAMAGE. '*/ Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls Imports System.Collections.Generic Imports System.ComponentModel _ '/ <summary> '/ Summary description for MembershipUserWrapper '/ This class is inherited from MembershipUser '/ Using the sytax public class ClassName (..) : base(initializers) allows for calling the '/ contstructor of the base class. In this case MembershipUser. '/ </summary> '/ Namespace Membership_ToolNamespace Membership_Tool Public Class MembershipUserWrapperClass MembershipUserWrapper Inherits MembershipUser '/ <summary> '/ This constructor is used to create a MembershipUserWrapper from a MembershipUser object. MembershipUser is a default type used '/ in the Membership API provided with ASP.NET 2.0 '/ </summary> '/ <param name="mu">MembershipUser object</param> Public Sub New()Sub New(ByVal mu As MembershipUser) MyBase.New(mu.ProviderName, mu.UserName, mu.ProviderUserKey, mu.Email, mu.PasswordQuestion, mu.Comment, mu.IsApproved, mu.IsLockedOut, mu.CreationDate, mu.LastLoginDate, mu.LastActivityDate, mu.LastPasswordChangedDate, mu.LastLockoutDate) End Sub 'New<DataObjectField(True)> _ '/ <summary> '/ This calls the base class UserName property. It is here so we can tag '/ this property as the primary key so that datakeynames attribute gets set in the data control. '/ </summary> '/ <DataObjectField(True)> _ Public Overrides ReadOnly Property UserName()Property UserName() As String Get Return MyBase.UserName End Get End Property '/ <summary> '/ This constructor is used to create a MembershipUserWrapper from individual parameters values. '/ For details of what each parameter means, see the Microsoft Membership class. '/ </summary> '/ <param name="comment">Passes to MembershipUser.comment</param> '/ <param name="creationDate">Passes to MembershipUser.creationDate</param> '/ <param name="email">Passes to MembershipUser.email</param> '/ <param name="isApproved">Passes to MembershipUser.isApproved</param> '/ <param name="lastActivityDate">Passes to MembershipUser.lastActivityDate</param> '/ <param name="lastLoginDate">Passes to MembershipUser.lastLoginDate</param> '/ <param name="passwordQuestion">Passes to MembershipUser.passwordQuestion</param> '/ <param name="providerUserKey">Passes to MembershipUser.providerUserKey</param> '/ <param name="userName">Passes to MembershipUser.userName</param> '/ <param name="lastLockoutDate">Passes to MembershipUser.lastLockoutDate</param> '/ <param name="providerName">Passes to MembershipUser.providerName</param> '/ Public Sub New()Sub New(ByVal comment As String, ByVal creationDate As DateTime, ByVal email As String, ByVal isApproved As Boolean, ByVal lastActivityDate As DateTime, ByVal lastLoginDate As DateTime, ByVal passwordQuestion As String, ByVal providerUserKey As Object, ByVal userName As String, ByVal lastLockoutDate As DateTime, ByVal providerName As String) MyBase.New(providerName, userName, providerUserKey, email, passwordQuestion, comment, isApproved, False, creationDate, lastLoginDate, lastActivityDate, DateTime.Now, lastLockoutDate) End Sub 'New End Class 'MembershipUserWrapper ' This calls a constructor of MembershipUser automatically because of the base reference above End Namespace
這樣MembershipUser 的Username 被標記成 只讀/主鍵 在綁定的時候 GridView會識別這一說明生成相應的編輯模式模板。
數據行已經被我們建立好了,那麼數據從哪裡來呢?我們建立一個新類MembershipUserODS 把提供我們的數據的方法集中在這個類中,它就能起到Dataadepter的作用。
<DataObject(True)> _ Public Class MembershipUserODSClass MembershipUserODS End Class
其中加入如下方法來實現數據的讀取,也就是Select操作:
<DataObjectMethod(DataObjectMethodType.Select, False)> Public Shared _ Function GetMembers()Function GetMembers(ByVal returnAllApprovedUsers As Boolean, ByVal returnAllNotApprovedUsers As Boolean, ByVal usernameToFind As String) As List(Of MembershipUserWrapper) Dim memberList As New List(Of MembershipUserWrapper) '看看是否只需要返回某個特定的用戶 If Not (usernameToFind Is Nothing) Then ' { Dim mu As MembershipUser = Membership.GetUser(usernameToFind) If Not mu Is Nothing Then Dim md As MembershipUserWrapper = New MembershipUserWrapper(mu) memberList.Add(md) End If Else Dim muc As MembershipUserCollection = Membership.GetAllUsers() Dim mu As MembershipUser For Each mu In muc If returnAllApprovedUsers = True And mu.IsApproved = True Or (returnAllNotApprovedUsers = True And mu.IsApproved = False) Then Dim md As New MembershipUserWrapper(mu) memberList.Add(md) End If Next mu return memberList end Function
(在隨後的源代碼包裡面有排序的功能 大家參考下就好)
這時候我們已經可以對 MembershipUserODS 進行數據綁定了----這個方法實現了數據綁定的最基礎的動作:選擇。
利用同樣的方式 我們建立對應 插入、更新和刪除操作的 方法:
插入:
<DataObjectMethod(DataObjectMethodType.Insert, True)> Public Shared _ Sub Insert()Sub Insert(ByVal userName As String, ByVal isApproved As Boolean, ByVal comment As String, ByVal lastLockoutDate As DateTime, ByVal creationDate As DateTime, ByVal email As String, ByVal lastActivityDate As DateTime, ByVal providerName As String, ByVal isLockedOut As Boolean, ByVal lastLoginDate As DateTime, ByVal isOnline As Boolean, ByVal passwordQuestion As String, ByVal lastPasswordChangedDate As DateTime, ByVal password As String, ByVal passwordAnswer As String) ' The incoming parameters, password and passwordAnswer are not properties of the ' MembershipUser class. Membership has special member functions to deal with these ' two special properties for security reasons. For this reason, they do not appear ' in a datacontrol that is created with this user object. ' ' the only reason you may want to have defaults is so you can build insert into your ' datacontrol. A better approach would be to either follow the example shown in the ' Membership.asp page where the parameters are set directly to the userobject, or not ' include "new" at all in your control and use the other controls in the Membership API ' for creating new members. (CreateUserWizard, etc) ' ' It is recommended that you only enable the following lines if you are sure of what you are doing 'if (password == null) '{ ' password = "pass0word"; '} 'if (passwordAnswer == null) '{ ' passwordAnswer = "Password Answer"; '} Dim status As MembershipCreateStatus Membership.CreateUser(userName, password, email, passwordQuestion, passwordAnswer, isApproved, status) If status <> MembershipCreateStatus.Success Then Throw New ApplicationException(status.ToString()) End If Dim mu As MembershipUser = Membership.GetUser(userName) mu.Comment = comment Membership.UpdateUser(mu) End Sub
更新:
<DataObjectMethod(DataObjectMethodType.Update, True)> Public Shared _ Sub Update()Sub Update(ByVal UserName As String, ByVal email As String, ByVal isApproved As Boolean, ByVal comment As String, ByVal lastActivityDate As DateTime, ByVal lastLoginDate As DateTime) Dim dirtyFlag As Boolean = False Dim mu As MembershipUser = Membership.GetUser(UserName) If mu.Comment Is Nothing Or (mu.Comment & "").CompareTo(comment) <> 0 Then dirtyFlag = True mu.Comment = comment End If If mu.Email Is Nothing Or (mu.Email & "").CompareTo(email) <> 0 Then dirtyFlag = True mu.Email = email End If If mu.IsApproved <> isApproved Then dirtyFlag = True mu.IsApproved = isApproved End If If dirtyFlag = True Then Membership.UpdateUser(mu) End If End Sub
刪除:
<DataObjectMethod(DataObjectMethodType.Delete, True)> Public Shared _ Sub Delete()Sub Delete(ByVal UserName As String) Membership.DeleteUser(UserName, True) End Sub
以上的工作,我們在ODS和membership之間 建立了一條橋梁,通過我們的代碼,membership 復雜的對象被我們用簡單的數據屬性所代理,而能夠被ODS正確識別
下面我們簡單的試驗下我們工作的成果,親手綁定一下。
建立一個新頁面Default.aspx 在上面放置一個GridView和一個ODS控件。
效果如下圖示:
選擇“配置數據源”,你剛才建立的、帶有<DataObject>聲明的類便會被枚舉出來:
下一步 選擇每種動作的對應方法:
當你把4種基本動作全部配置完畢 ,在屬性欄把ObjectDataSource1.OldValuesParameterFormatString 的內容設置為{0} 就可以綁定了:
基本上不會出現什麼錯誤拉。。。。
同樣的方式 你可以用如下的代碼建立Roles和Profile 的綁定
Roles
'/* 'Copyright ?2005, Peter Kellner 'All rights reserved. 'http://peterkellner.net 'Redistribution and use in source and binary forms, with or without 'modification, are permitted provided that the following conditions 'are met: '- Redistributions of source code must retain the above copyright 'notice, this list of conditions and the following disclaimer. '- Neither Peter Kellner, nor the names of its 'contributors may be used to endorse or promote products 'derived from this software without specific prior written 'permission. 'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS '"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 'LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 'FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 'COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 'INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, 'BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 'CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 'LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 'ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 'POSSIBILITY OF SUCH DAMAGE. '*/ Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls Imports System.Collections.Generic Imports System.ComponentModel Imports System.Collections.ObjectModel Namespace Membership_ToolNamespace Membership_Tool '/ <summary> '/ A class used to encapsulate the Roles in ASP.NET Membermanagement 2.0 '/ </summary> <DataObject(True)> _ Public Class RoleDataODSClass RoleDataODS ' This attribute allows the '/ <summary> '/ Used to get all roles available '/ </summary> '/ <returns></returns> '/ <DataObjectMethod(DataObjectMethodType.Select, True)> Public Overloads Shared _ Function GetRoles()Function GetRoles() As List(Of RoleData) Return GetRoles(Nothing, False) End Function 'GetRoles '/ <summary> '/ Returns a collection of RoleData type values. This specialized constructor lets you request by '/ an individual user '/ </summary> '/ <param name="userName">if null and showOnlyAssignedRolls==false, display all roles</param> '/ <param name="showOnlyAssignedRolls">if true, just show assigned roles</param> '/ <returns></returns> <DataObjectMethod(DataObjectMethodType.Select, False)> Public Overloads Shared _ Function GetRoles()Function GetRoles(ByVal userName As String, ByVal showOnlyAssignedRolls As Boolean) As List(Of RoleData) Dim roleList As New List(Of RoleData) Dim roleListStr As String() = Roles.GetAllRoles() Dim roleName As String For Each roleName In roleListStr Dim userInRole As Boolean = False ' First, figure out if user is in role (if there is a user) If Not (userName Is Nothing) Then userInRole = Roles.IsUserInRole(userName, roleName) End If If showOnlyAssignedRolls = False Or userInRole = True Then ' Getting usersInRole is only used for the count below Dim usersInRole As String() = Roles.GetUsersInRole(roleName) Dim rd As New RoleData() rd.RoleName = roleName rd.UserName = userName rd.UserInRole = userInRole rd.NumberOfUsersInRole = usersInRole.Length roleList.Add(rd) End If Next roleName ' FxCopy will give us a warning about returning a List rather than a Collection. ' We could copy the data, but not worth the trouble. Return roleList End Function 'GetRoles '/ <summary> '/ Used for Inserting a new role. Doesn't associate a user with a role. '/ This is not quite consistent with this object, but really what we want. '/ </summary> '/ <param name="RoleName">The Name of the role to insert</param> <DataObjectMethod(DataObjectMethodType.Insert, True)> Public Shared _ Sub Insert()Sub Insert(ByVal roleName As String) If Roles.RoleExists(roleName) = False Then Roles.CreateRole(roleName) End If End Sub 'Insert '/ <summary> '/ Delete any given role while first removing any roles associated with existing users '/ </summary> '/ <param name="roleName">name of role to delete</param> <DataObjectMethod(DataObjectMethodType.Delete, True)> Public Shared _ Sub Delete()Sub Delete(ByVal roleName As String) ' remove this role from all users. not sure if deleterole does this automagically Dim muc As MembershipUserCollection = Membership.GetAllUsers() Dim allUserNames(1) As String Dim mu As MembershipUser For Each mu In muc If Roles.IsUserInRole(mu.UserName, roleName) = True Then allUserNames(0) = mu.UserName Roles.RemoveUsersFromRole(allUserNames, roleName) End If Next mu Roles.DeleteRole(roleName) End Sub 'Delete End Class 'RoleDataObject '/ <summary> '/ Dataobject class used as a base for the collection '/ </summary> Public Class RoleDataClass RoleData ' Non normalized column which counts current number of users in a role Private number_OfUsersInRole As Integer Public Property NumberOfUsersInRole()Property NumberOfUsersInRole() As Integer Get Return number_OfUsersInRole End Get Set(ByVal value As Integer) number_OfUsersInRole = value End Set End Property Private role_Name As String <DataObjectField(True)> _ Public Property RoleName()Property RoleName() As String Get Return role_Name End Get Set(ByVal value As String) role_Name = value End Set End Property Public user_Name As String Public Property UserName()Property UserName() As String Get Return user_Name End Get Set(ByVal value As String) user_Name = value End Set End Property Private user_InRole As Boolean Public Property UserInRole()Property UserInRole() As Boolean Get Return user_InRole End Get Set(ByVal value As Boolean) user_InRole = value End Set End Property End Class 'RoleData End Namespace Profile Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls Imports System.Collections.Generic Imports System.ComponentModel Namespace Membership_ToolNamespace Membership_Tool <DataObject(True)> _ Public Class ProfileODSClass ProfileODS <DataObjectMethod(DataObjectMethodType.Select, False)> _ Public Function GetUserProfile()Function GetUserProfile(ByVal UserName As String) As List(Of ProfileEntry) Return GetUserProfile(UserName, Nothing) End Function <DataObjectMethod(DataObjectMethodType.Select, False)> _ Public Function GetUserProfile()Function GetUserProfile(ByVal UserName As String, ByVal PropertyNames As String) As List(Of ProfileEntry) Dim doFilter As Boolean = False Dim PNames As String() = Nothing If PropertyNames Is Nothing Then ElseIf PropertyNames = "" Then Else PNames = PropertyNames.Split("|") doFilter = True End If Dim pelist As New List(Of ProfileEntry) Dim pf As Profile.ProfileBase = System.Web.Profile.ProfileBase.Create(UserName) '因為PrifileCommon的一個未知bug 在沒有訪問任何已知屬性前,屬性的集合將不可訪問,所以需要一個缺省的屬性來支持 '以下為試圖訪問默認的屬性bugjumper來獲得屬性集合 Try Dim x As String = pf.GetPropertyValue("bugjumper") Catch ex As Exception ' Throw New Exception("管理員並沒有為profilecommon的bug設置默認的屬性參數bugjumper") End Try '處理bug過程結束 For Each itm As System.Configuration.SettingsPropertyValue In pf.PropertyValues If itm.Name <> "bugjumper" Then '忽略處理bug用的公共屬性 Dim doAdd As Boolean = False If Not doFilter Then doAdd = True ElseIf Array.IndexOf(PNames, itm.Name) <> -1 Then doAdd = True End If If doAdd Then Dim pe As New ProfileEntry(UserName, itm.Name, itm.PropertyValue) pelist.Add(pe) End If End If Next Return pelist End Function <DataObjectMethod(DataObjectMethodType.Update, False)> _ Public Sub UpdateUserProfile()Sub UpdateUserProfile(ByVal UserName As String, ByVal Key As String, ByVal Value As String) Dim pf As Profile.ProfileBase = System.Web.Profile.ProfileBase.Create(UserName) pf.SetPropertyValue(Key, Value) pf.Save() End Sub End Class Public Class ProfileEntryClass ProfileEntry Private k, v As String Private U As String <DataObjectField(True)> _ Property Key()Property Key() As String Get Return k End Get Set(ByVal value As String) k = value End Set End Property Property Value()Property Value() As String Get Return v End Get Set(ByVal value As String) v = value End Set End Property <DataObjectField(True)> _ Property UserName()Property UserName() As String Get Return U End Get Set(ByVal value As String) U = value End Set End Property Sub New()Sub New(ByVal NewUserName As String, ByVal NewKey As String, ByVal NewValue As String) Me.UserName = NewUserName Me.Key = NewKey Me.Value = NewValue End Sub End Class End Namespace
ProFile可是我自己寫的阿^_^
這裡發現了一個bug,由vs2005 生成的profilecommon類 在第一次成功訪問某屬性前,Properties集合不會正確的枚舉出所有成員,所以我在程序中作了一些小小的調整,具體請看示例工程:DDDD
本文配套源碼