asp.net|iis
摘要:本文介绍如何通过创建三层结构式 ASP.NET 2.0 应用程序来维护 IIS 生产服务器中的成员身份数据库和角色数据库。
简介
成员身份编辑器
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
• Email
• 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 对本文进行润色提供的所有帮助。从某些方面来讲,本文是对提供过帮助的人的回报,希望他们将来不必回答这么多问题。