Peter Kellner
http://peterkellner.net/
适用于:
Microsoft ASP.NET 2.0
Microsoft Visual Studio 2005
Microsoft Internet 信息服务
摘要:本文介绍如何通过创建三层结构式 ASP.NET 2.0 应用程序来维护 IIS 生产服务器中的成员身份数据库和角色数据库。
单击此处下载本文的代码示例。
本页内容
简介 | |
采用的技术 | |
应用程序和项目 | |
ObjectDataSource 详细信息 | |
Select 方法的返回值(类型为 Collection) | |
Select 方法本身 | |
自定义排序条件 | |
GridView 中的 ObjectDataSource(数据控件) | |
结论 |
简介
成员身份编辑器
Microsoft Visual Studio 2005 版本中没有用于维护 Microsoft IIS 中的成员身份数据库和角色数据库的“现成”解决方案。将开发环境中的应用程序移至 IIS 生产服务器时这就会是个问题。Microsoft 提供的实用程序 ASP.NET Web Configuration 只能在非生产的开发环境中运行。本文及其关联代码将通过对成员和角色管理实现三层式解决方案,同时使用 Microsoft ASP.NET 标准工具,来解决这个问题。这意味着该实用程序将可在任何 ASP.NET 2.0 环境(包括 IIS)中运行。该解决方案十分灵活,可以轻易添加到任何现有的 ASP.NET 2.0 网站项目中。
该解决方案的层定义如下。第一层 ASP.NET 页面(也称为表示层)通过对象数据源与两个业务对象进行连接。这些业务对象起中间层作用,是成员和角色的包装程序。第三层(即后端)由 ASP.NET 提供的成员身份和角色管理器 API 组成。中间层对象可以轻松地加入任何 ASP.NET 2.0 项目,并且几乎无需进行任何更改就可以直接使用。
本文深入地介绍了中间层(即数据对象及其关联 ObjectDataSource)的实现。接着,介绍了如何在使用 Microsoft SQL Server Express 2005(捆绑有 Visual Studio 2005)的 ASP.NET Web 项目中使用这些对象。但是由于 Microsoft 提供的成员身份 API 使用其提供商的技术,因此此处介绍的解决方案与数据库无关。从 LDAP、SQL Server 或 Oracle 即可轻松获得成员身份和角色信息。
采用的技术
ObjectDataSource
定义了两个 ObjectDataSource 实例。一个是有关成员身份数据(用户名、创建日期、批准状态等)的,另一个是有关角色(管理员、朋友等)的。这两个数据源均完全填充了所有数据访问方法,即两者都包含执行插入、更新、删除和选择的 Member 函数。两个 ObjectDataSource 实例都返回 Generic List 类型,这意味着在 GridView 中,列名将自动设置为 ObjectDataSource 的属性值名。此外,还实现了自定义排序,以便用户可以单击 GridView 中的列标题来根据需要对数据进行正向或反向排序。
SQL Server Express 2005 和 Web.Config
成员身份数据库和角色数据库的数据提供程序源是 SQL Server Express 2005。为实现这一点,需要在 web.config 文件中设置相应的条目。本文稍后将对如何从头开始设置新项目进行简要的介绍。web.config 文件中未提及 SQL Server Express 2005 的连接字符串,因为它已在 Microsoft .NET 2.0 Framework 的默认部分 Machine.Config 文件中定义。
支持 IIS(5.1 和 6.0)
Web 服务器可以为 5.1 版,也可以为 6.0 版。若要对登录 Web 应用程序的多个用户进行测试,必须使用 IIS。内置开发 Web 服务器不能正确保持各不同登录用户的状态。内置开发 Web 服务器不能正确保持各不同登录用户的状态。尽管可以使 Asp.net Web 配置工具与 IIS 一起工作,但尚未完成实现这一目的所必需的附加安全工作。
GridView 控件
GridView 用于显示成员身份和角色的数据。如上文所述,由于使用了 ObjectDataSource 的 Generic 类型,GridView 的列名将自动以 ObjectDataSource 的属性值命名。如果没有使用 Generic 类型,则列名恢复为无意义的默认值,必须手动逐个进行编辑。
应用程序和项目
运行此实用程序所需的项目非常简单,并且是独立的。项目文件可以下载,包含功能完整的示例。由于用户和角色没有直接访问数据库的权限,因此所要做的事情就是获取三个数据对象(MembershipDataObject.cs、MembershipUserSortable.cs 和 RoleDataObject.cs,请参见图 2)。
图 2:成员身份编辑器项目
SamplePages 文件夹中有几个其他的示例,演示了前面提及的模块的用法。图 1 中显示的 Membership.aspx 即是其中一例,它可用于选择、更新、插入及删除成员和角色,以及为成员分配角色。
使用已有工作成员身份模块的工作 ASP.NET 2.0 应用程序时,无需对这些页面进行已做配置之外的外部配置。可以将这些文件直接复制到项目中,复制后即可使用。
如果是第一次在应用程序中实现成员身份和角色管理,则创建使用这些对象的解决方案的过程如下,
1. | 使用 Visual Studio 2005 创建类型为 ASP.NET 网站的新 Web 项目。 |
2. | 单击菜单上的 Website / ASP.NET Configuration(网站/ASP.NET 配置)。 |
3. | 按照向导提示的步骤(1 至 7)进行操作来创建一些示例用户和角色。这将在当前项目中有效地创建有效 web.config 文件,其中包含能够启动并运行成员管理的充足信息。默认情况下,它将在其默认配置中使用 SQL Server Express 2005。 |
4. | 在项目中添加三个 .cs 文件,然后添加示例 .aspx 页面作为示例。 |
ObjectDataSource 详细信息
采用 ObjectDataSource 技术可以创建作用与 SqlDataSource 非常相似的数据源,即它提供允许从永久数据存储区(例如数据库)中进行选择、更新、插入和删除记录(或类似记录的对象)的界面。本文以下各部分将讨论 ObjectDataSource 用于操作成员身份的对象(即类文件)。其在项目中的名称为 MembershipUserODS.cs。
类 (MembershipUserODS)
由于是通过 Microsoft 成员身份 API 检索数据,因此使用 ObjectDataSource 来解决问题。第一步是创建独立的类,该类对 MembershipUser 进行包装,以便它可以与 ObjectDataSource 关联。下例中介绍了一组需要实现的典型方法,本文以下各部分将介绍如何实现每个成员函数。本文省略了许多细节,但本文附带的源代码中包含这些细节。
[DataObject(true) public class MembershipUserWrapper { [DataObjectMethod(DataObjectMethodType.Select, true)] static public Collection<MembershipUserWrapper> GetMembers(string sortData) { return GetMembers(true, true, null, sortData); } [DataObjectMethod(DataObjectMethodType.Insert, true)] static public void Insert(string UserName, bool isApproved, string comment, DateTime lastLockoutDate, ...) { } [DataObjectMethod(DataObjectMethodType.Delete, true)] static public void Delete(object UserName, string Original_UserName){ Membership.DeleteUser(Original_UserName, true); } [DataObjectMethod(DataObjectMethodType.Update, true)] static public void Update(string original_UserName,string email,...){ } }
类声明
上面显示的类声明因具有属性 [(DataObject(true)],比较特殊。此属性告诉 Visual Studio 2005 ObjectDataSource 创建向导,在数据类中搜索 DataObject 时只查找具有此特殊属性的成员。请参阅本部分中介绍在何处为 GridView 组件分配此类的示例。
Insert 方法
各部分的细节都涉及以非常简单的方式使用 Microsoft 提供的成员身份 API。例如,下面是一个较详细的典型 Insert 方法。
[DataObjectMethod(DataObjectMethodType.Insert,true)] static public void Insert(string userName, string password,) { MembershipCreateStatus status; Membership.CreateUser(userName, password,); }
此类 Insert 是多态的,这意味着可以存在用于不同目的的多个 Insert 方法。例如,动态决定是否应该根据环境批准创建的用户时,可能需要使用它。又如,在管理屏幕中创建的新用户可能想创建默认为已批准的用户,而用户注册屏幕可能默认为未批准。为此,需要另一个具有额外参数的 Insert 方法。可实现此目标的 Insert 方法大致如下。
[DataObjectMethod(DataObjectMethodType.Insert,false)] static public void Insert(string userName, string password, bool isApproved) { MembershipCreateStatus status; Membership.CreateUser(UserName, password,, isApproved, out status); }
与此处所列的其他方法一样,显示的示例并非附带源中实际存在的示例。此处的示例是为了说明各个方法的典型用法。源代码中包含的用法更为完备且带有注释。
Update 方法
Update 方法是实现成员身份 API 的一种非常简单的方法。与 Insert 方法一样,Update 方法也可以有多种实现。此处只介绍一种实现。在可下载的代码中,有更多 Update 的多态实现,其中一种只设置 IsApproved 属性(如下例所示)。
[DataObjectMethod(DataObjectMethodType.Update,false)] static public void Update(string UserName,bool isApproved) { bool dirtyFlag = false; MembershipUser mu = Membership.GetUser(UserName); if (mu.isApproved != isApproved) { dirtyFlag = true; mu.IsApproved = isApproved; } if (dirtyFlag == true) { Membership.UpdateUser(mu); } }
Delete 方法
Delete 方法是最简单的方法,它只使用一个参数 UserName。
static public void Delete(string UserName) { Membership.DeleteUser(UserName,true); }
具有 Sort 属性的 Select 方法
在本示例中,Select 方法 GetMembers 具有多个组件,每个组件都值得介绍。首先介绍其返回的值,然后是方法本身,最后介绍其如何排序返回值。
Select 方法的返回值(类型为 Collection)
Select 方法(也称为 Get)的返回值为 Generic Collection 类。使用 Generic 是因为最终与该类关联的 ObjectDataSource 使用反射来确定列名和类型。这些名称和类型与返回的每行数据相关联。此方法与 SqlDataSource 使用表或存储过程的数据库元数据来确定每行的列名相同。由于 Select 方法的返回类型为 MembershipUserWrapper(继承自 MembershipUser),此类的大多数属性都是与 MembershipUser 关联的相同属性。这些属性包括,
• | ProviderUserKey |
• | UserName |
• | LastLockoutDate |
• | CreationDate |
• | PasswordQuestion |
• | LastActivityDate |
• | ProviderName |
• | IsLockedOut |
• | |
• | LastLoginDate |
• | IsOnline |
• | LastPasswordChangedDate |
• | Comment |
在此插一句,属性值有一个非常好的特点 - 它们可以是只读的(无设置方法)、只写的(无读取方法),当然也可以是读/写的。ObjectDataSource 向导考虑到了这一点,并创建了相应的参数,这样在使用 ObjectDataSource 呈现数据控件时,只有可更新(读/写)的字段能够编辑。这意味着您不能更改某些属性,例如 UserName 属性。如果这一点现在还不清楚,稍后在我们更详细地阐述 ObjectDataSource 和数据组件时,就容易明白。
Select 方法本身
与 Insert 和 Update 方法一样,Select 方法也是多态的。有多少种情况,就可以有多少种 Select 方法。例如,最好能够使用 Select 方法按照用户的批准状态(已批准、未批准或两者)来选择用户。通常,有一个 Get 方法具有与其关联的尽可能多的参数,其他 Get 方法对其进行调用。在我们的示例中,有三个 Get 方法,一个检索所有记录,一个根据批准状态检索记录,一个根据选择字符串检索单个记录。下例介绍的是调用返回所有用户的方法。将两个布尔值均设置为 true,可以返回所有用户。
[DataObjectMethod(DataObjectMethodType.Select, true)] static public List<MembershipData> GetMembers(string sortData) { return GetMembers(true,true,null,null); }
下面的示例介绍了一个较详细的 Get 方法。此示例仅介绍方法的开头部分,未介绍方法的详细信息,包括完成属性分配、按批准状态筛选并拒绝不满足条件的记录,以及应用排序条件。此示例后面是有关排序条件的详细说明。(请注意,对包含数百个用户 [不超过五百] 的数据库调用 GetAllUsers,很快就会成为代价非常高昂的操作。)
[DataObjectMethod(DataObjectMethodType.Select, true)] static public List<MembershipData> GetMembers(bool AllApprUsers, bool AllNotApprUsers, string UserToFind, string sortData) { List<MembershipData> memberList = new List<MembershipData>(); MembershipUserCollection muc = Membership.GetAllUsers(); foreach (MembershipUser mu in muc) { MembershipData md = new MembershipData(); md.Comment = mu.Comment; md.CreationDate = mu.CreationDate; ...
自定义排序条件
请注意,在前面的代码中,名为 sortData 的参数字符串传递到了 GetMembers 中。如果在 ObjectDataSource 声明中,SortParameterName 被指定为其一个属性,则此参数将自动传递到所有 Select 方法。其值将为数据控件列中的 SortExpression 属性指定的名称。在我们的示例中,数据控件为 GridView。
Comparer 方法是根据传递给 GetMembers 方法的 sortName 参数调用的。由于这些 ASP.NET 网页无状态,因此必须假定当前排序的方向(正向或反向)存储在视图状态中。每次调用都颠倒前一次调用的方向。即用户单击列标题时,在正向排序和反向排序之间切换。
假定使用的是 GridView,传递到 GetMembers(sortData) 的参数中包含 GridView 列的属性 SortExpression 中的数据。如果请求反向排序,则“DESC”一词附加在排序字符串后面。例如,用户第一次单击 Email 列时,传递到 GetMembers 的 sortData 为“Email”。用户第二次单击该列时,参数 sortData 就变为“Email DESC”,然后是“Email”、“Email DESC”,依此类推。特别需要注意的是,第一次加载页面时,传递的 sortData 参数是零长度的字符串(非空)。下面是 GetMembers 方法的一部分,该方法检索数据并对其进行排序,以便按正确的顺序返回这些数据。
[DataObjectMethod(DataObjectMethodType.Select, true)] static public List<MembershipData> GetMembers(string sortData) { List<MembershipData> memberList = new List<MembershipData>(); MembershipUserCollection muc = Membership.GetAllUsers(); List<MembershipUser> memberList = new List<MembershipUser>(muc); foreach (MembershipUser mu in muc) { MembershipData md = new MembershipData(mu); memberList.Add(md); } ... Code that implements Comparison memberList.Sort(comparison); return memberList; }
在下一部分中,将此并入 GridView 中后,就比较清楚了。
ObjectDataSource 声明
声明 ObjectDataSource 最简单的方法是,先使用 Visual Studio 2005 向导创建一个空的 ASP.NET 页面,然后将数据控件中的数据控件拖放到工具栏中。创建 ObjectDataSource 后,可以获取新建 ObjectDataSource 右上角的小标记;然后单击 Configure Data Source(配置数据源)打开一个向导,其中显示“Configure Data Source-ObjectDataSource1”(配置数据源 - ObjectDataSource1)(请参见图 3)。
图 3:配置 ObjectDataSource
此时,将显示可与 ObjectDataSource 关联的两个类。MembershipUserODS 是本文的主要主题。RoleDataObject 基本相同,但其封装成员身份角色。另外,请记住,此处显示的只是声明具有特殊类属性 [DataObject(true)](在“类定义”中介绍)的对象。
选择 MembershipUserODS 后,将显示一个具有四个选项卡的对话框。要通过 MembershipUserODS 类调用的方法将在这些选项卡中定义。Select、Update、Insert 和 Delete 方法将与 MembershipUserODS 中的成员函数关联。在许多情况下,类中都有多种方法适用于其中每种情况。必须根据所需数据方案选择一个适当的方法。图 4 中显示了这四个选项卡。默认情况下,将在这些选项卡中填充标有特殊属性 [DataObjectMethod(DataObjectMethodType.Select, false)] 的成员。当然,此特殊属性是 Select 的默认值。将表达式 DataObjectMethodType.Select 改为 DataObjectMethodType.Insert、DataObjectMethodType.Update 和 DataObjectMethodType.Delete 将为其他选项卡确定相应的默认值。第二个参数是一个布尔值,表示此方法(请记住,它能以多态方式定义)是默认方法,应在选项卡控件中使用。
Select 方法
如前面在介绍 MembershipUserODS 类的部分中所述,GetMembers 函数返回 Generic Collection 类。这样,此处定义的 ObjectDataSourceMembershipUser 控件可以使用反射,并确定与 GetMembers 调用关联的调用参数。在本示例中,用于调用 GetMembers 的参数是 returnAllApprovedUsers、returnAllNotApprovedUsers、userNameToFind 和 sortData。基于此,新 ObjectDataSource 的实际定义如下。
图 4:指定 Select 方法
<asp:ObjectDataSource ID="ObjectDataSourceMembershipUser"runat="server" SelectMethod="GetMembers" UpdateMethod="Update" SortParameterName="SortData" TypeName="MembershipUtilities.MembershipDataODS" DeleteMethod="Delete" InsertMethod="Insert" > <SelectParameters> <asp:Parameter Name="returnAllApprovedUsers" Type="Boolean" /> <asp:Parameter Name="returnAllApprovedUsers" Type="Boolean"/> <asp:Parameter Name="usernameToFind" Type=" String" /> <asp:Parameter Name="sortData" Type=" String" /> </SelectParameters> ... ... </asp:ObjectDataSource>
Insert 方法
在本示例中,Insert 方法被指定给成员函数 Insert()。请注意,调用此方法时只使用了两个参数,UserName 和 Password(请参见图 5)。参数的数目必须等于 ObjectDataSource 中声明的参数的数目。ObjectDataSource 中的参数声明如下所示。另一个定义的函数为 Insert Member,用于添加第三个参数,approvalStatus。如果此 ObjectDataSource 的功能要包括在设置 approvalStatus 时进行插入操作,则应从下拉列表中选择其他 Insert 方法。这会导致以下 InsertParameters 插入 .aspx 页面中。如果选择包含两个参数的方法,则块中不会包括名为 isApproved 的 asp:Parameter。请记住,本示例可能与附带的源代码不一致,此处仅作为示例。附带的源代码要完整得多。
图 5:指定 Insert 方法
<asp:ObjectDataSource ID="ObjectDataSourceMembershipUser"runat="server" SelectMethod="GetMembers"UpdateMethod="GetMembers" SortParameterName="SortData" TypeName="MembershipUtilities.MembershipDataObject" DeleteMethod="Delete" InsertMethod="Insert"> <InsertParameters> <asp:Parameter Name="userName" Type="String" /> <asp:Parameter Name="password" Type="String" /> <asp:Parameter Name="isApproved" Type="Boolean" /> </InsertParameters> ... </asp:ObjectDataSource>
请记住,如果使用的是具有最少参数的 Insert 方法,则需要在方法中设置默认密码。在生产系统中,这是个糟糕的办法。有关如何处理插入的更好示例,请参阅附带的源代码。具体地说,请参阅 Membership.aspx 页面了解此功能。
Update 方法
在本示例中,Update 方法被指定给成员函数 Update()。请注意,调用此方法时使用了多个参数,UserName、Email、isApproved 和 Comment(请参见图 6)。此外,还有一种 Update 方法,它包含所有可更新参数。如果要创建具有尽可能多的更新功能的控件,这很有用。与 Insert 一样,为此 ObjectDataSource 选择适当的 Update 方法。完成向导后,将自动创建 UpdateParameters,如下所示。
图 6:指定 Update 方法
<asp:ObjectDataSource ID="ObjectDataSourceMembershipUser"runat="server" SelectMethod="GetMembers" InsertMethod="Insert" SortParameterName="SortData" TypeName="MembershipUtilities.MembershipUserODS" UpdateMethod="Update" DeleteMethod="Delete"> <UpdateParameters> <asp:Parameter Name="Original_UserName" /> <asp:Parameter Name="email" Type="String" /> <asp:Parameter Name="isApproved" Type="Boolean" /> <asp:Parameter Name="comment" Type="String" /> </UpdateParameters> ... ... </asp:ObjectDataSource>
Delete 方法
在本示例中,Delete 方法被指定给成员函数 Delete()。当然,只需一个 Delete 方法(请参见图 7)。下面是支持此 Delete 方法的 ObjectDataSource 的声明。
图 7:指定 Delete 方法
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetMembers" InsertMethod="Insert" SortParameterName="SortData" TypeName="MembershipUtilities.MembershipUserODS" UpdateMethod="Update" DeleteMethod="Delete"> <DeleteParameters> <asp:Parameter Name="UserName" /> <asp:Parameter Name="Original_UserName" /> </DeleteParameters> ... </asp:ObjectDataSource>
类 (RoleDataObject)
与成员身份一样,设置角色时也使用其自己的 DataObject。由于角色无特殊之处,本文不对其设置进行详细介绍。了解成员身份 DataObject 的设置方式后,即可了解角色的设置方式。在成员身份中,封装成员身份 API 的 Microsoft C# 对象是 MembershipDataObject.cs。封装角色 API 的相似类是 RoleDataObject.cs。
GridView 中的 ObjectDataSource(数据控件)
本文的前面部分中已建立了成员身份用户和角色的类声明。此外,还在 ASP.NET 页面中加入了完整的 ObjectDataSource 对象。最后一步是创建用户界面,也称为应用程序的用户互动层或表示层。由于创建的对象完成了这么多的工作,因此所需做的只是创建简单的 GridView 并将其与 ObjectDataSource 关联。步骤如下,
1. | 在 ASP.NET 页面设计器的可视模式下,将 GridView 数据组件拖放到先前创建的 ObjectDataSource 关联页面中。 |
2. | 启用选择、删除、更新、插入和排序。 |
图 8 显示的是与配置 Gridview 关联的对话框。
图 8:配置 GridView
此处应特别注意,下面显示的 GridView 控件中的 DataKeyNames 是自动设置的。这是因为,在具有属性 [DataObjectField(true)] 的 MembershipUserSortable 类中对主键添加了标记,如下所示。请注意,由于 UserName 是 MembershipUser 类的属性,需要在扩展 MembershipUser 的类中提供默认属性。由于是只读属性,因此只声明了 Get 方法(对于 MembershipUser,UserName 是公共虚拟的)。
[DataObjectField(true)] public override string UserName { get { return base.UserName; }
GridView 中有一个属性必须手动设置,必须在控件中设置主键。为此,需要将属性 DataKeyName 与 UserName 相关联。GridView 声明如下。
<asp:GridView ID="GridView1" DataKeyNames="UserName" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataSourceID="ObjectDataSourceMembershipUser" AllowSorting="True"> <Columns> ... ...
结论
至此,您现在应熟悉如何创建自己的三层结构式 ASP.NET 应用程序。此外,目前还要有两个可任意使用来封装成员和角色的对象。例如,现在可以使用 DetailView 控件,在几分钟内创建一个针对成员的完整 DetailView 界面,用于对成员进行导航、插入、更新及删除操作。试一试吧!
我并未具体介绍如何实现添加、更新和删除成员或角色。如果您查看源代码,就会发现我使用 API 的方法非常简单。在此详细介绍那些调用并无多大用处,因为我确信,如果您仍在阅读本文,您会和我一样,边学边实践。
今年我有幸参加了在奥兰多举办的 MS TechEd 和在洛杉矶举办的 PDC,有机会向 ASP.NET 小组请教了许多问题。特别感谢 Brad Millington 和 Stefan Schackow 在这几周解答了我提出的许多问题,感谢 Jeff King 和 Brian Goldfarb 对本文进行润色提供的所有帮助。从某些方面来讲,本文是对提供过帮助的人的回报,希望他们将来不必回答这么多问题。
作者简介
Peter Kellner 于 1990 年创办了 73rd Street Associates,在此成功地为全国 500 多家客户提供了有关大学医务室调度、保险公司管理和一站式医生诊所管理的系统。十年后(即 2000 年),一家大型保险公司收购了 73rd Street Associates,于是 Peter 开始了作为独立软件顾问的新职业生涯。目前,他涉及的技术中包括 ASP.NET、Oracle、Java、VOiP,很快会包括 SQL Server。不工作时,Peter 将他的大部分空闲时间花在骑车旅行上。他已骑车周游了全世界。最近,他和妻子 Tammy 只用了 27 天就完成了从美国加利福尼亚州骑车到乔治亚州的旅程。
他的博客站点为 http://peterkellner.net/。您可以在下载区域找到本文和所列的代码。