ASP.NET入门帖
=======================================.NET Framework 系统要求
操作系统要求
下列平台支持 .NET Framework。
方案 操作系统
客户端 Microsoft® Windows® 98
Microsoft® Windows® 98 Second Edition
Microsoft® Windows® Millennium Edition
带有 Service Pack 6.0a 或更高版本的 Microsoft® Windows NT® 4.0 Workstation
带有 Service Pack 6.0a 或更高版本的 Microsoft® Windows NT® 4.0 Server
Microsoft® Windows® 2000 Professional
Microsoft® Windows® 2000 Server
Microsoft® Windows® 2000 Advanced Server
Microsoft® Windows® 2000 Datacenter Server
Microsoft® Windows® XP Home Edition
Microsoft® Windows® XP Professional
Microsoft® Windows® Server 2003 系列
注意 在所有这些系统上,还要求有 Microsoft® Internet Explorer 5.01 或更高版本以及 Microsoft® Windows® Installer 2.0 或更高版本。
服务器 带有 Service Pack 2.0 的 Microsoft® Windows® 2000 Professional
带有 Service Pack 2.0 的 Microsoft® Windows® 2000 Server
带有 Service Pack 2.0 的 Microsoft® Windows® 2000 Advanced Server
带有 Service Pack 2.0 的 Microsoft® Windows® 2000 Datacenter Server
Microsoft® Windows® XP Professional
Microsoft® Windows® Server 2003 系列
附加软件要求
若要使用附加功能,如 ASP.NET、COM+ 服务和 SQL Server .NET 数据提供程序,需要下列附加软件。
方案 功能 所需软件
客户端 SQL Server .NET 数据提供程序 Microsoft 数据访问组件 (MDAC) 2.6 或更高版本
对系统管理信息的访问 Windows Management Instrumentation (WMI)(在 Windows 2000、Windows Millennium Edition 和 Windows XP 上随操作系统一起安装)
COM+ 服务 Windows 2000 Service Pack 2.0
服务器 SQL Server .NET 数据提供程序 Microsoft 数据访问组件 (MDAC) 2.7
ASP.NET Microsoft Internet 信息服务 (IIS) 5.0
硬件要求
方案 所需处理器 推荐的处理器 所需 RAM 推荐的 RAM
客户端(Windows 窗体和 Windows 服务) Pentium 90 MHz* Pentium 90 MHz 或更快 32 MB* 96 MB 或更高
服务器 Pentium 133 MHz* Pentium 133 MHz 或更快 128 MB* 256 MB 或更高
*或者操作系统所需的最低要求(二者之中取较高者)。
默认情况下,在已经安装了 .NET Framework 的计算机上安装新的 .NET Framework 时,所有的 ASP.NET 应用程序都会自动更新以使用新安装的 .NET Framework 版本。唯一的例外是那些绑定到不兼容的运行库版本或更新的运行库版本的应用程序。尽管更高版本的 .NET Framework 具有向后兼容性,您仍可能需要相应地配置 ASP.NET 应用程序以使用某种旧版本。以下各节描述了针对 .NET Framework 的特定版本配置 ASP.NET 应用程序的过程。
查看 ASP.NET 应用程序的脚本映射
在管理安装了多个版本的 .NET Framework 的计算机时,查看 ASP.NET 应用程序的脚本映射,以确定应用程序所使用的版本,这样做通常很有用。可以使用 Internet 信息服务管理控制台查看 ASP.NET 应用程序的脚本映射。
查看 ASP.NET 应用程序的脚本映射
打开 IIS 管理控制台,单击加号展开本地计算机,然后定位到包含 ASP.NET 应用程序的文件夹。
右击文件夹,再单击“属性”。出现应用程序的“属性”对话框。
单击“目录”选项卡中的“配置”按钮。出现“应用程序配置”对话框。
在“映射”选项卡中,选择 ASP.NET 应用程序扩展名,如 .asmx 或 .aspx。
对话框的“可执行文件路径”列列出了到应用程序所使用的 ASP.NET ISAPI 版本的路径。默认情况下,ASP.NET ISAPI 被安装在以下位置:
系统根\Microsoft.NET\Framework\版本号
路径中显示的版本号说明了应用程序所使用的 ASP.NET ISAPI 的版本号。ASP.NET ISAPI 版本确定了应用程序所使用的运行库版本。
使用 Aspnet_regiis.exe 更新脚本映射
为使重新配置 ASP.NET 应用程序的脚本映射更容易,每个 .NET Framework 安装都提供了一个关联的 ASP.NET IIS 注册工具 (Aspnet_regiis.exe) 版本。管理员可以使用此工具将 ASP.NET 应用程序重新映射到与此工具关联的 ASP.NET ISAPI 版本。
注意 因为 Aspnet_regiis.exe 链接到特定版本的 .NET Framework,管理员必须使用适当版本的 Aspnet_regiis.exe,以针对 ASP.NET 应用程序重新配置脚本映射。Aspnet_regiis.exe 仅将 ASP.NET 应用程序的脚本映射重新配置为与此工具关联的 ASP.NET ISAPI 版本。
此工具也可用来显示所有已安装的 ASP.NET 版本的状态、注册关联的 ASP.NET 版本、创建客户端脚本目录以及执行其他配置操作。
使用 Aspnet_regiis.exe 更新某一 ASP.NET 应用程序的脚本映射
1 打开命令窗口。(单击“开始”,单击“运行”,键入“cmd”,然后单击“确定”。)
2 定位到要使用的 Aspnet_regiis.exe 版本的目录。记住每一版本的 .NET Framework 都有自己的版本。文件通常位于以下目录:
系统根\Microsoft.NET\Framework\版本号
3 与到应用程序的路径一起使用 Aspnet_regiis.exe 的 -s 或 -sn 选项,以设置脚本映射。以下显示了一个命令行示例,它更新一个名为 SampleApp1 的应用程序的脚本映射。
Aspnet_regiis.exe -s W3SVC/1/ROOT/SampleApp1
禁用脚本映射自动更新
为防止将全部现有应用程序自动重新映射到正在安装的 .NET Framework 版本,请在运行 Dotnetfx.exe 安装程序时使用 /noaspupgrade 命令行选项。以下显示了整个命令行。
Dotnetfx.exe /c:"install /noaspupgrade"
卸载某一版本的 ASP.NET
如果您在一台计算机上安装了多个版本的 ASP.NET,您可以单独卸载某一版本。卸载 ASP.NET 通常采用的方式是:通过“控制面板”中的“添加或删除程序”项卸载 .NET Framework。在卸载过程中,安装程序使用 -u 选项(卸载标志)来调用 ASP.NET IIS 注册工具 (Aspnet_regiis.exe)。
注意 如果只是希望卸载某一版本的 ASP.NET,而不卸载关联的 .NET Framework,可以直接使用 Aspnet_regiis.exe。.NET Framework 的每一份安装都包含该工具的关联版本。如果只卸载 ASP.NET,请使用与要卸载的 ASP.NET 版本相关联的工具版本的 -u 选项。有关更多信息,请参见 ASP.NET IIS 注册工具 (Aspnet_regiis.exe)。
Aspnet_regiis.exe 的 -u 选项执行以下操作:
当前映射到要卸载的 ASP.NET 版本的所有 ASP.NET 应用程序将被重新映射到计算机上的次最新兼容版本。如果不存在相兼容版本,该应用程序的脚本映射将被完全删除。
警告 删除脚本映射后,IIS 将以文本形式提交 ASP.NET 页。这有可能会将源代码公开给客户端。
ASP.NET 状态服务为 ASP.NET 的所有兼容版本所共享,并且总是映射到安装的最新版本的 ASP.NET。如果您要卸载的 ASP.NET 版本正是当前的最新版本,计算机上的下一个兼容的最高版本就会被注册,以取代这一版本。如果不存在兼容版本,ASP.NET 状态服务将被卸载。
特定于要卸载的版本的性能对象及关联的性能计数器将被删除。适用于所安装的所有 ASP.NET、ASP.NET 和 ASP.NET 应用程序版本的一般性性能对象和计数器将定向到计算机上剩下的最新安装版本。
ASPNET 用户帐户为 ASP.NET 的所有兼容版本所共享。如果计算机上已不存在其他的 ASP.NET 安装,ASPNET 用户帐户以及关联的访问控制列表将被删除。
ASP.NET 应用程序的脚本映射
如果在同一计算机上安装了多个版本的 .NET Framework,则这些安装各包含一个关联的 ASP.NET ISAPI 版本。ASP.NET 应用程序使用 ASP.NET ISAPI 确定该应用程序使用哪一版本的 .NET Framework。ASP.NET 应用程序可以配置为使用所安装的 ASP.NET ISAPI 的任一版本。若要指定 ASP.NET 应用程序所使用的 ASP.NET ISAPI 版本,应在 Internet 信息服务 (IIS) 中为该应用程序注册脚本映射。
脚本映射将文件扩展名和 HTTP 谓词与适当的 ISAPI 相关联,以便实现脚本处理。例如,如果 IIS 接收到对某一 .aspx 文件的请求,该应用程序的脚本映射就会指示 IIS 将所请求的文件发送给适当版本的 ASP.NET ISAPI 来处理。每一 ASP.NET 应用程序的脚本映射通常是在 Internet 信息服务管理控制台中设置的,可以直接应用到某一应用程序,也可以从父应用程序继承。默认情况下,安装了 .NET Framework 之后,会自动更新计算机上所有现有 ASP.NET 应用程序的脚本映射以使用与这一安装相关联的 ASP.NET ISAPI 版本,除非应用程序使用更高的版本或者不兼容的版本。
为使重新配置 ASP.NET 应用程序的脚本映射更容易,每个 .NET Framework 安装都提供了一个关联的 ASP.NET IIS 注册工具 (Aspnet_regiis.exe) 版本。默认情况下,该工具安装在以下目录:
<系统根>\Microsoft.NET\Framework\〈版本号〉
管理员可以使用此工具将 ASP.NET 应用程序重新映射到与此工具关联的 ASP.NET ISAPI 版本。
注意 因为 Aspnet_regiis.exe 与特定版本的 .NET Framework 相关联,管理员必须使用适当版本的 Aspnet_regiis.exe,为 ASP.NET 应用程序重新配置脚本映射。Aspnet_regiis.exe 仅将 ASP.NET 应用程序的脚本映射重新配置为与此工具关联的 ASP.NET ISAPI 版本。
此工具也可用来显示所有已安装的 ASP.NET 版本的状态、注册关联的 ASP.NET 版本、创建客户端脚本目录以及执行其他配置操作。
在 Windows Server 2003 系列产品上,在 IIS 管理器中启用 ASP.NET
使用“配置您的服务器向导”,在运行 Windows Server 2003 的服务器上安装 ASP.NET
在任务栏上,单击“开始”按钮,然后单击“管理您的服务器”,在“管理您的服务器”窗口中单击“添加或移除角色”。
在“配置您的服务器向导”中,单击“下一步”,在“服务器角色”对话框中选中“应用程序服务器 (IIS, ASP.NET)”,然后单击“下一步”。
在“应用程序服务器选项”对话框中,单击“启用 ASP.NET”复选框,单击“下一步”,再单击“下一步”。
如有必要,在 CD-ROM 驱动器中插入 Windows Server 2003 安装 CD,然后单击“下一步”。
安装完成后,单击“完成”。
使用“添加/删除程序”对话框,在运行 Windows Server 2003 的服务器上安装 ASP.NET
在任务栏上,单击“开始”按钮,指向“控制面板”,然后单击“添加或删除程序”。
在“添加/删除程序”对话框中,单击“添加/删除 Windows 组件”。
在“Windows 组件向导”的“组件”框中,单击“应用程序服务器”复选框,然后单击“下一步”。
当“Windows 组件向导”配置完 Windows Server 2003 后,单击“完成”。
在 Windows Server 2003 系列产品上,在 IIS 管理器中启用 ASP.NET
在任务栏上单击“开始”按钮,然后单击“运行”。
在“运行”对话框的“打开”框中,输入“inetmgr”,然后单击“确定”。
在“IIS 管理器”中,展开“本地计算机”,然后单击“Web 服务扩展”。
在右窗格中,右击“ASP.NET”,然后单击“允许”。ASP.NET 的状态随即更改为“允许”。
当在运行 Windows XP Professional 或 Windows 2000 Server 的计算机上安装 Visual Studio .NET 时,.NET Framework 和 ASP.NET 也会自动安装。如果要单独安装 ASP.NET 和 .NET Framework,可以从网上下载它们并安装到您的服务器上。下面的过程提供了具体的步骤。
在运行 Windows XP Professional 或 Windows 2000 Server 的计算机上下载和安装 ASP.NET
如有必要,安装并启动 IIS。有关安装说明,请参见操作系统的文档。
在 msdn.microsoft.com/downloads/default.asp 上,展开“Software Development Kits”(软件开发工具包),单击“Microsoft .NET Framework SDK”,然后阅读页面上有关下载 SDK 的要求、说明和选项。
单击所需的下载选项,阅读最终用户许可协议,然后单击“Yes”(是)。
在“文件下载”对话框中,选择保存下载文件,选择要将安装程序和自述文件下载到的文件夹,然后单击“保存”。
查看自述文件中任何最新的说明。
在下载文件所在的文件夹中,双击 .NET Framework 安装程序 Setup.exe。
如果您已经安装并启用了 IIS,安装了 ASP.NET 和 .NET Framework,部署了应用程序并请求了一个页,但是收到以下错误信息之一,这说明还没有为 Web 站点或虚拟目录设置适当的权限:
对“C:\Inetpub\Wwwroot”目录的访问被拒绝。未能开始监视目录更改。
服务器无法访问应用程序目录“C:\Inetpub\Wwwroot\<虚拟目录名>\”。该目录不存在或者因安全设置而无法访问。
在根 Web 站点或任何虚拟目录上,ASP.NET 需要 ASPNET 帐户(Aspnet_wp.exe 进程帐户)的读取、执行和列出权限。必须有这些设置,ASP.NET 才能访问内容文件和监视文件更改。请执行下面的步骤更正此问题。
在根 Web 站点或虚拟目录上添加 ASPNET 帐户的读取、执行和列出权限
在 Windows 资源管理器中,浏览到包含根 Web 站点(默认设置为:C:\Inetpub\Wwwroot)或虚拟目录的文件夹。
右击该文件夹,然后单击“属性”。
在“安全”选项卡上,单击“添加”。
输入 ComputerName \ASPNET(例如,在名为 Webdev 的计算机上输入 Webdev\ASPNET),然后单击“确定”。
允许 ASPNET 帐户的下列权限:读取并执行、列出文件夹内容、读取。
单击“确定”关闭“属性”对话框并保存更改。
注意 如果“Everyone”(每个人)组或“用户”组能够读取根 Web 站点或虚拟目录,则不需要执行这些步骤。
使用 IIS 创建 ASP.NET Web 应用程序根目录
本演练步骤向您说明如何使用 Internet 信息服务 (IIS) 将某个目录标记为应用程序根目录。此演练说明如何创建虚拟目录并将 C:\Inetpub\Wwwroot 目录设置为应用程序的根目录。
准备此次演练
创建一个新目录。此演练使用 C:\myWebApp 目录。
接下来,为 IIS 打开 Microsoft 管理控制台 (MMC),并使用下列过程之一创建一个虚拟目录。
使用 Windows Server 2003 系列产品创建虚拟目录
单击“开始”,指向“管理工具”,然后单击“Internet 信息服务”。
展开“本地计算机”节点(这可能由您的计算机名称表示),展开“网站”,然后单击“默认网站”。
在“操作”菜单上单击“新建”,然后单击“虚拟目录”。
在“虚拟目录创建向导”中,单击“下一步”。
在“别名”文本框中输入您的应用程序名称,然后单击“下一步”。
输入为准备此演练而创建的物理目录 C:\myWebApp,然后单击“下一步”。
在“访问权限”页中,选中您的应用程序所需的访问权限,然后单击“下一步”。
单击“完成”。
使用 Windows XP 创建虚拟目录
单击“开始”,然后单击“控制面板”。
如果尚未完成以上操作,单击“切换到传统型视图”。
双击“管理工具”,然后单击“Internet 信息服务”。
展开“本地计算机”节点(这可能由您的计算机名称表示),展开“Web 站点”,然后单击“默认 Web 站点”。
在“操作”菜单上单击“新建”,然后单击“虚拟目录”。
在“虚拟目录创建向导”中,单击“下一步”。
在“别名”文本框中输入您的应用程序名称,然后单击“下一步”。
输入为准备此演练而创建的物理目录 C:\myWebApp,然后单击“下一步”。
在“访问权限”页中,选中您的应用程序所需的访问权限,然后单击“下一步”。
单击“完成”。
使用 Windows 2000 创建虚拟目录
单击“开始”,指向“程序”,指向“管理工具”,然后单击“Internet 服务管理器”。
展开“本地计算机”节点(这可能由您的计算机名称表示),展开“Web 站点”,然后单击“默认 Web 站点”。
在“操作”菜单上单击“新建”,然后单击“虚拟目录”。
在“虚拟目录创建向导”中,单击“下一步”。
在“别名”文本框中输入您的应用程序名称,然后单击“下一步”。
输入为准备此演练而创建的物理目录 C:\myWebApp,然后单击“下一步”。
在“访问权限”页中,选中您的应用程序所需的访问权限,然后单击“下一步”。
单击“完成”。
也可以在 Inetpub\Wwwroot 中的现有目录下创建应用程序根目录。为准备此演练,请在目录 C:\Inetpub\Wwwroot 下创建一个目录。如前所述,将其命名为 myWebApp。
使用 IIS 将 Inetpub\Wwwroot 中的现有目录标记为 Web 应用程序
将 MMC 打开到“默认 Web 站点”,过程同前。
在“默认 Web 站点”中,单击要指定为应用程序根目录的子目录。在本例中,为 myWebApp。
右击要作为应用程序根目录的目录,然后单击“属性”。
在“虚拟目录”选项卡的“应用程序设置”节中,单击“创建”。
在“应用程序名”文本框中,输入应用程序的名称,然后单击“确定”。
ASP.NET 配置文件相关
可扩展的基础结构是 ASP.NET 配置系统的一大特色,该基础结构使您可以在最初部署 ASP.NET 应用程序时定义配置设置,以便可以随时添加或修改这些配置设置,同时对运作着的 Web 应用程序和服务器产生的影响也将被减至最小。
ASP.NET 配置系统提供以下好处:
配置信息存储在基于 XML 的文本文件中。您可以使用任何标准的文本编辑器或 XML 分析器来创建和编辑 ASP.NET 配置文件。
多个配置文件(名称都是 Web.config)可以出现在 ASP.NET Web 应用程序服务器上的多个目录中。每个 Web.config 文件都将配置设置应用于它自己的目录和它下面的所有子目录。子目录中的配置文件可以提供除从父目录继承的配置信息以外的配置信息,子目录配置设置可以重写或修改父目录中定义的设置。名为 systemroot\Microsoft.NET\Framework\versionNumber\CONFIG\Machine.config 的根配置文件提供整个 Web 服务器的 ASP.NET 配置设置。
在运行时,ASP.NET 使用分层虚拟目录结构中 Web.config 文件提供的配置信息为每个唯一的 URL 资源计算一组配置设置。然后缓存结果配置设置,以供所有后面的对资源的请求使用。请注意,继承是由传入请求路径 (URL) 定义的,而不是到磁盘上资源的文件系统路径(物理路径)定义的。
ASP.NET 检测对配置文件的更改并自动将新配置设置应用于受该更改影响的 Web 资源。不需要重新启动服务器让更改生效。只要层次结构中的配置文件被更改,就将自动重新计算并重新缓存分层配置设置。<processModel> 节例外。
ASP.NET 配置系统是可以扩展的。您可以定义新配置参数并编写配置节处理程序以对它们进行处理。
ASP.NET 通过配置 Internet 信息服务 (IIS) 防止对配置文件的直接浏览器访问来保护配置文件不受外部访问。向任何试图直接请求配置文件的浏览器返回 HTTP 访问错误 403(禁止)。
ASP.NET 资源的配置信息包含在一组配置文件中,每个文件都名为 Web.config。每个配置文件都包含 XML 标记和子标记的嵌套层次结构,这些标记带有指定配置设置的属性。因为这些标记必须是格式正确的 XML,所以标记、子标记和属性是区分大小写的。标记名和属性名是 Camel 大小写形式的,这意味着标记名的第一个字符是小写的,任何后面连接单词的第一个字母是大写的。属性值是 Pascal 大小写形式的,这意味着第一个字符是大写的,任何后面连接单词的第一个字母也是大写的。true 和 false 例外,它们总是小写的。
所有配置信息都驻留在 <configuration> 和 </configuration> 根 XML 标记之间。标记间的配置信息分为两个主区域:配置节处理程序声明区域和配置节设置区域。
配置节处理程序声明出现在配置文件顶部 <configSections> 和 </configSections> 标记之间。包含在 <section> 标记中的每个声明都指定提供特定配置数据集的节的名称和处理该节中配置数据的 .NET Framework 类的名称。
配置节设置区域位于 <configSections> 区域之后,它包含实际的配置设置。<configSections> 区域中的每个声明都有一个配置节。每个配置节都包含子标记,这些子标记带有包含该节设置的属性。
下面的 Web.config 文件示例声明两个配置 <section> 处理程序。一个管理应用程序设置,另一个管理会话状态。
<configuration>
<configSections>
<section name="appSettings"
type="System.Configuration.NameValueFileSectionHandler,
System, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<section name="sessionState"
type="System.Web.SessionState.SessionStateSectionHandler,
System.Web, Version=1.0.3300.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
allowDefinition="MachineToApplication"/>
</configSections>
<appSettings>
<add key="dsn" value="localhost;uid=MyUserName;pwd=;"/>
<add key="msmqserver" value="server\myqueue"/>
</appSettings>
<sessionState cookieless="true" timeout="10"/>
</configuration>
您只需要声明配置节处理程序一次。您可以将其放置在服务器的根 Machine.config 文件中或包含 Web 应用程序文件的虚拟目录的 Web.config 文件中。子目录中的配置文件自动继承父目录中声明的配置处理程序。有关更多信息,请参见配置继承。
配置设置在节分组标记下经常嵌套在一起。这些顶级节标记通常表示配置设置应用到的命名空间。例如,顶级 <system.net> 标记表示网络类的设置,<system.web> 标记表示 ASP.NET 类的设置。
配置 <location> 设置
通过使用具有适当的 path 属性的 <location> 标记,可以将配置设置应用于特定的资源。path 属性可用于标识对其应用唯一配置设置的特定的文件或子目录。
例如,下面的配置文件在三个级别指定设置:
应用于当前目录和所有子目录的设置(全部内容包含在顶部 <configuration> 标记中)。
应用于 Sub1 子目录的设置(全部内容包含在 <location> 标记中,路径属性设置为 Sub1)。
应用于 Sub2 子目录的设置(全部内容包含在 <location> 标记中,路径属性设置为 Sub2)。
<configuration>
<system.web>
<sessionState cookieless="true" timeout="10"/>
</system.web>
<!— Configuration for the "Sub1" subdirectory. -->
<location path="sub1">
<system.web>
<httpHandlers>
<add verb="*" path="Sub1.Scott" type="Sub1.Scott"/>
<add verb="*" path="Sub1.David" type="Sub1.David"/>
</httpHandlers>
</system.web>
</location>
<!— Configuration for the "Sub2" subdirectory. -->
<location path="sub2">
<system.web>
<httpHandlers>
<add verb="*" path="Sub2.Scott" type="Sub2.Scott"/>
<add verb="*" path="Sub2.David" type="Sub2.David"/>
</httpHandlers>
</system.web>
</location>
</configuration>
访问 ASP.NET 配置设置
您可以从 ASP.NET 应用程序使用 ASP.NET 公开的内部静态方法来访问公共配置设置。例如,若要读取 <sessionState> 节 cookieless 属性的值,您可以使用下面的代码行。
[Visual Basic]
Dim nocookies As Boolean = Session.IsCookieless
[C#]
bool nocookies = Session.IsCookieless;
可以使用 ConfigurationSettings.AppSettings 静态字符串集合来访问 Web.config 文件顶级 <appSettings> 节中存储的应用程序特定的设置。例如:
[Visual Basic]
Dim dsn As String = ConfigurationSettings.AppSettings("dsn")
[C#]
String dsn = ConfigurationSettings.AppSettings["dsn"];
创建新的配置节
您可以用自己的 XML 配置标记扩展标准的 ASP.NET 配置设置集。若要完成该操作,您必须创建自己的配置节处理程序。该处理程序必须是一个实现 IConfigurationSectionHandler 接口的 .NET Framework 类。节处理程序解释并处理 Web.config 文件特定部分中 XML 标记中定义的设置并根据配置设置返回适当的配置对象。处理程序类返回的配置对象可以是任何数据结构;它不限于任何基配置类或配置格式。
下面的示例定义 IConfigurationSectionHandler 接口。
[Visual Basic]
Namespace System.Web.Configuration
Public Interface IConfigurationSectionHandler
Function Create(parent As Object, input As Object, _
node As XmlNode) As Object
End Interface
End Namespace
[C#]
namespace System.Web.Configuration
{
public interface IConfigurationSectionHandler
{
public Object Create(Object parent, Object input,
XmlNode node);
}
}
您还可以定义自己的节,该节与 <appSettings> 节使用相同的配置处理程序。例如:
<configuration>
<configSections>
<sectionGroup name="myGroup">
<sectionGroup name="nestedGroup">
<section name="mySection" type=
"System.Configuration.NameValueSectionHandler,System"/>
</sectionGroup>
</sectionGroup>
</configSections>
<myGroup>
<nestedGroup>
<mySection>
<add key="key_one" value="1"/>
<add key="key_two" value="2"/>
</mySection>
</nestedGroup>
</myGroup>
</configuration>
您可以读取上面的示例中定义的新配置节的值,如下:
[Visual Basic]
Dim config As NameValueCollection =
ConfigurationSettings.GetConfig("myGroup/nestedGroup/mySection")
Response.Write("The value of key_one is " & config("key_one") & "<br>")
Response.Write("The value of key_two is " & config("key_two") & "<br>")
[C#]
NameValueCollection config = (NameValueCollection)
ConfigurationSettings.GetConfig("myGroup/nestedGroup/mySection");
Response.Write("The value of key_one is " + config["key_one"] + "<br>");
Response.Write("The value of key_two is " + config["key_two"] + "<br>");
部署 ASP.NET Web 应用程序
部署 ASP.NET 应用程序非常简单。只需将所创建的应用程序文件从开发计算机复制到将承载应用程序的成品 Web 服务器。可以使用 XCOPY 命令行工具或喜欢的 FTP 应用程序,将文件从一个位置复制到另一个位置。
要部署希望在 Web 应用程序间共享的程序集(比如包含自定义 ASP.NET 服务器控件的程序集),应将其部署到全局程序集缓存。
全局程序集缓存我在这里简单介绍一下:
安装有公共语言运行库的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存。全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集。
应当仅在需要时才将程序集安装到全局程序集缓存中以进行共享。一般原则是:程序集依赖项保持专用,并在应用程序目录中定位程序集,除非明确要求共享程序集。另外,不必为了使 COM interop 或非托管代码可以访问程序集而将程序集安装到全局程序集缓存。
注意 在有些情况下,您显然不希望将程序集安装到全局程序集缓存中。如果您将组成应用程序的某个程序集置于全局程序集缓存中,则将不再能够通过使用 xcopy 命令复制应用程序目录来复制或安装该应用程序。您还必须在全局程序集缓存中移动该程序集。
有若干方法可以将程序集部署到全局程序集缓存中:
使用专用于全局程序集缓存的安装程序。该方法是将程序集安装到全局程序集缓存的首选方法。
使用 .NET Framework SDK 所提供的名为全局程序集缓存工具 (Gacutil.exe) 全局程序集缓存工具使您可以查看和操作全局程序集缓存和下载缓存的内容。gacutil [options] [assemblyName | assemblyPath | assemblyListFile]的开发人员工具。
使用 Windows 资源管理器将程序集拖到缓存中。
从命令行部署 ASP.NET 应用程序
单击“开始”,然后单击“运行”。
在“运行”对话框的“打开”文本框中,输入“cmd”,然后单击“确定”。
在命令提示处,键入下列命令:
xcopy <源路径> <目标路径>
在此命令中,<源路径> 是需要复制的源文件的完整路径,包括驱动器、目录和要复制的文件名。如果目录下的全部文件都需要复制,可以省略文件名。<目标路径> 是文件应该复制到的目录的完整路径。
以下的命令示例将 c:\myWebApp 目录下的所有文件复制到 d:\liveapp 目录下。
xcopy c:\devapp d:\liveapp
回答所有关于所复制的文件或目录的问题。
当需要更新应用程序的 \Bin 目录下存储的 DLL 或更新任何其他应用程序文件时,都可以使用此过程。下面的示例将一个驱动器的 \bin 目录中的一个 DLL 复制到另一个驱动器的 \bin 目录下。
xcopy c:\devapp\bin\myAssembly.dll d:\liveapp\bin\
同样,一旦部署了应用程序,就可以使用此命令更新应用程序中的文件。虽然可以复制整个目录,但是在复制个别文件时,在两个目录之间一次只能复制一个文件。可以使用 XCOPY /exclude 选项,从复制中排除子目录、带指定文件扩展名的文件或具体的文件名。有关如何使用 XCOPY 工具的更多信息,请打开操作系统的文档并搜索 XCOPY。
注意 对于 XCOPY 工具必须使用物理目录名。不能使用虚拟目录名。
Visual Studio 中设计时的 Web 应用程序安全性
当您在 Visual Studio 中工作以创建 Web 应用程序时,将需要在开发过程中访问一些资源,与这种访问相关的安全性存在一些特定的要求。这些要求不同于那些适用于应用程序用户的要求。设计时的安全性要求包括:
访问您的工作服务器、数据库服务器以及其他属于应用程序一部分的资源。
您选择了哪种 Web 访问方法(您如何向 Web 服务器传输数据)。
以完全受信任模式运行代码。
调试。
部署您的应用程序。
访问开发资源
为了在 Visual Studio 中创建和测试 Web 应用程序,您必须能够访问运行 Internet 信息服务 (IIS) 的计算机。在此服务器(即开发服务器,它不同于您在其中部署应用程序的生产服务器)上,必须具有某些最低程度的特权,其中包括能够将文件写入服务器并运行这些文件。
Web 服务器不必位于用于开发的同一台计算机上。但是,您必须至少在 Web 服务器上安装 Visual Studio 的服务器组件,以使其支持应用程序的调试和部署。
对资源的设计时访问通常使用 Windows NT 身份验证来处理,也就是说,作为开发人员,您使用自己的网络登录凭据来获取对所需资源的访问权。当 Visual Studio 安装完毕后,它会在服务器上创建一个名为“VS Developers”(VS 开发人员)的组。该组具有在此计算机上开发 Web 项目所必需的全部文件、共享和 IIS 权限。(计算机管理员可以进一步为该组配置自定义权限,但这并不是必需的。)作为开发人员,您必须作为个人或您所属的另一个组的成员成为该组的成员。授予“VS Developers”(VS 开发人员)组的特定权限有:
访问 Web 服务器计算机的 wwwroot 目录的权限。
创建、修改和执行 Web 目录中文件的权限。
调试远程 Web 应用程序的权限。
安全说明 开发人员并不需要为使用某服务器进行开发而具有该服务器的管理员权限。(但是,所有开发人员都必须是“VS Developers”(VS 开发人员)组的成员。)如果向开发人员授予不必要的管理员权限,则可能会给网络造成安全性风险。
如果您的应用程序需要访问进一步的资源,您必须同样地作为个人或作为组的成员建立对这些资源的访问权限。一个典型的示例就是 SQL Server。作为 Web 应用程序开发人员,您将需要能够读取(并在可能时更新)应用程序所需的数据库表。对于某些方案,您还需要具有将存储过程写入服务器的权限;在其他方案中,则可能需要具有添加、改变或删除表的权限。
Web 访问方法
Visual Studio 允许您以两种方式访问 Web 应用程序项目:
使用文件共享访问(UNC 访问)。在这种方式中,Visual Studio 使用基于 Windows 的文件管理命令将文件复制到服务器上。
使用 FrontPage 服务器扩展访问。在这种方式中,将使用 HTTP 来传输文件。
文件共享访问要求您与 Web 服务器位于同一域中。实际上,这意味着您只能在自己的网络上使用文件共享访问。
对比之下,FrontPage 访问允许跨越防火墙创建和管理应用程序(只要防火墙传递 HTTP 请求)。这样,就可以通过 Internet 执行操作(当然也可以在您首选 HTTP 访问的本地设置中执行操作)。
这两种访问方法仍然要求使用 Visual Studio 服务器组件、“VS Developers”(VS 开发人员)组等正确配置服务器。同样,您仍然需要在服务器上具有足够的特权才能创建、编写和运行文件。
以完全受信任模式运行代码
当 Visual Studio 运行时,设计器在设计时运行的用户代码将始终以完全受信任模式运行。即使该代码最终部署到的环境将以限制较大的安全性来运行,情况也是如此。这包括两层含义:
可能会因为将不安全代码导入项目而给本地计算机带来安全性风险。由于 Visual Studio 以完全受信任模式运行,导入的代码会从 Visual Studio 继承其权限,而不安全的代码将作为安全代码来运行。只有在恶意用户创建具有破坏性的代码段(例如自定义控件),而您随后因疏忽导入并运行该代码段的情况下,才需要注意这一问题。因此,在将代码导入项目时,务必要加倍小心。
您在 Visual Studio 中创建的应用程序可能会在部署后无法正确运行,因为该代码是在不同的安全性上下文中运行。
调试
调试要求您能够附加到在本地计算机(如果 Web 服务器位于另一台计算机上,则为远程计算机)上运行的进程。调试要求您使用“管理员”特权运行。如果您是在远程计算机上进行调试,则必须在本地计算机上和远程计算机上都具有相应的权限。
当 Visual Studio 安装完毕后,它将创建一个名为“Debugger Users”(调试器用户)的组,该组具有进行调试所必需的权限。通过将开发人员和调试用户划分到不同的组,服务器管理员可以向一个经过精选的组授予调试所必需的特权,而不是向所有使用 Visual Studio 的用户授予这些特权。
安全说明 “Debugger Users”组中的成员可有效地授予用户管理级别特权。不要使不应具有这种级别特权的任何人成为此组的成员。
部署应用程序
当完成应用程序后,您必须能够将它部署到生产服务器。如果生产服务器不属于您,则对该服务器的访问可能会受到很多在您的开发服务器上没有的限制。
部署 Web 应用程序的一种方法是使用 Visual Studio 部署工具。这种部署允许您执行应用程序的完全安装(包括注册和配置)。但是,它要求您对承载 Web 服务器的计算机具有管理员权限。
此外,如果生产服务器上安装有 FrontPage 服务器扩展,您也可以选择通过 HTTP 使用 Copy Project 命令来进行部署。这种部署为您提供的部署功能较少,但却允许您跨越防火墙进行部署。为了将文件写入目标服务器,您仍然必须在该服务器上具有足够的特权。
运行时的 Web 应用程序安全性
身份验证
ASP.NET 支持通过以下方法对用户进行身份验证。其中几种方法与 IIS 身份验证相重叠。有关详细信息,请参见介绍 Web 应用程序安全性。
身份验证类型 说明
匿名访问 用于未知用户将在其中发出请求的应用程序(通常是公共 Web 应用程序)。与 IIS 重叠。
基本和简要身份验证 (与 IIS 重叠)在此方案中,将提示无凭据的用户提供用户名和密码。
Windows 集成安全性 (NTLM) (与 IIS 重叠)如果发出请求的用户已经在基于 Windows 的网络中进行了身份验证,则在请求对资源的访问时,IIS 就可以通过该用户的凭据。
证书身份验证 (与 IIS 重叠)在此方案中,客户端具有已从第三方来源获取的证书(数字标识)。该证书通过请求传递到您的应用程序。
Kerberos (与 IIS 重叠)Kerberos 身份验证协议定义客户端与称作密钥分发中心 (Key Distribution Center, KDC) 的网络身份验证服务之间的交互。Windows 2000 在每个域控制器上以身份验证服务的形式来实现一个 KDC。有关详细信息,请参见 Kerberos 协议的基本概念。
Forms 身份验证 如果需要对某用户进行身份验证,ASP.NET 则会将请求重定向到您指定的页。该页通常包含一个您要在其中获取用户名信息的窗体。(如需额外的安全性,可以使用 HTTPS 协议交换该窗体。)当应用程序获取窗体信息时,它可以对用户的凭据执行应用程序特定的检查。值得注意的是,身份验证进程在您的控制之下(与 IIS 不同),这使您能够指定窗体的外观并选择存储用户信息的方式。
如果某用户成功通过身份验证,ASP.NET 将颁发一个包含特定标记的加密 Cookie,该标记将为后继的访问标识该用户。
Passport 身份验证 利用 ASP.NET,您可以检查那些具有从符合 Microsoft Passport 的应用程序中获取的凭据的用户。
如果某用户成功通过身份验证,ASP.NET 将颁发一个包含特定标记的加密 Cookie,该标记将为后继的访问标识该用户。
在 ASP.NET 应用程序中,Forms 身份验证通常是最佳的选择,因此它使您能够在很大程度上控制如何对用户进行身份验证并允许您将身份验证存储在一个标记中。
XML Web services 安全性
XML Web services 使用 ASP.NET 并作为 Web 应用程序来运行,因此它们所参与的安全性模型与任何 ASP.NET 应用程序所参与的安全性模型都相同。例如,XML Web services 可能配置为使用基本身份验证或 Windows 集成安全性。
在设计时,当您尝试向 XML Web services 添加引用(即请求 XML Web services 的发现文档)时,该 XML Web services 将按照它的配置来执行标准的 Web 应用程序身份验证。例如,如果 XML Web services 配置为使用基本身份验证,该服务将需要从发出请求的客户端获取用户名和密码。在 Visual Studio 中,通常使用添加 Web 引用对话框来发现 XML Web services。例如,如果 XML Web services 使用的是基本身份验证,“添加 Web 引用”对话框将提示您提供凭据。
如果您正在生成的应用程序包含对 XML Web services 的调用,则需要确保在进行调用之前具有正确的凭据,否则该调用将会失败。在运行时,可以通过在调用 XML Web services 的方法之前设置表示 XML Web services 的客户端对象的 Credentials 属性来向 XML Web services 传递凭据。
由于其他 ASP.NET 安全性选项可能不够灵活,XML Web services 可以实现一种凭据信息在 SOAP 标头中进行传递的自定义身份验证解决方案。在该解决方案中,凭据在客户端和服务器之间交换的消息的可选部分进行传递。然后可以编写一个自定义的 HttpModule(实现 IHttpModule 接口),它可以侦听标头中的信息并调用您的身份验证代码。
与其他 ASP.NET 应用程序相同,XML Web services 可能会实现基于特定角色的授权,以限制对应用程序特定部分的访问。
建立 SSL 和加密
若要使用 SSL 和加密,必须获取您所在公司或标识的服务器证书。证书是一种数字签名,它标识您的站点,使其无法被模拟。对于 Internet(公共)应用程序,可以从公认的第三方证书颁发机构获取服务器证书。对于私有 (Intranet) 应用程序,您自己就可以颁发服务器证书。您这样做的目的可能是为了确保内部应用程序(如个人站点)的安全。
服务器证书还使您能够使用加密。SSL 使用一种称作“公钥加密”(public key encryption) 的加密方法。这种形式的加密机制中有两个密钥:公钥用于对数据进行加密;私钥由您密存,用于对用公钥加密的信息进行解密。您所获取的服务器证书包含一个公钥。当用户需要使用 SSL 时,您的应用程序将向浏览器发送证书和公钥。然后,浏览器和服务器使用公钥来建立一种对它们的信息交换进行加密的方式。
注意 若要使用 SSL,浏览器必须支持长度至少为 40 位的加密。大多数浏览器都提供这种级别的加密。
当获取服务器证书后,可以指示用户将 https:// 用作前缀来获取和发送 Web 页,以使用 SSL。IIS 和浏览器将自动使用加密信道来交换信息。
Web 应用程序的基本安全实施策略
常规 Web 应用程序安全性建议
有些最基本的安全性建议也是最显然易见的。但是,如果恶意用户可以使用简单方法进入您的计算机,即使是最精心设计的应用程序安全性也会失败。
经常进行备份,并将备份存放在安全的场所。
将您的 Web 服务器计算机放置在安全的场所,这样,未经授权的用户就无法使用它、关闭它、带走它,等等。
使用 Windows NTFS 文件系统,不使用 FAT32。NTFS 的安全性比 FAT32 高得多。有关详细信息,请参见 Windows 文档。
使用不易破解的密码,确保 Web 服务器计算机和同一网络上的所有计算机的安全。
确保 IIS 的安全。有关详细信息,请参见 Microsoft 安全性 Web 站点 ([url]http://microsoft.com/technet/treeview/default.asp?url=/technet/security/bestprac/mcswebbp.asp)[/url] 上的文章“管理 Windows IIS Web 服务的安全性”。
关闭不使用的端口并关闭不使用的服务。
运行监视通信量的病毒检查程序。
建立并实施以下策略:禁止用户将其密码记在容易找到的位置。
使用防火墙。有关建议,请参见 Microsoft 安全性站点上的“核对清单:安装防火墙”。([url]http://microsoft.com/security/articles/firewall.asp)[/url]
了解和安装来自 Microsoft 和其他供应商的最新安全修补程序。例如,Microsoft 安全站点 ([url]www.microsoft.com/security)[/url] 中有所有 Microsoft 产品的最新安全公告的列表。其他供应商也有类似站点。
使用 Windows 事件日志记录,并且经常检查这些日志,以查找可疑活动。这样的活动包括:反复尝试登录您的系统,以及向您的 Web 服务器发出数量巨大的请求。
使用最少特权运行应用程序
当您的应用程序运行时,它运行在一个具有本地计算机(还可能是远程计算机)的特定特权的上下文中。请遵循这些指导:
在具有最少实用特权的上下文中运行应用程序。默认情况下,在 IIS 5 版中,ASP.NET 应用程序运行在名为 ASPNET 的本地用户的上下文中。在 Windows Server 2003 上的 IIS 6 上,此帐户称为 NETWORK SERVICE。有关详细信息,请参见 ASP.NET 进程标识和配置 ASP.NET 进程标识。
不要在系统用户(管理员)的上下文中运行应用程序。
设置应用程序所需的所有资源上的权限(ACL 或访问控制列表)。使用最少的容许设置。例如,如果在您的应用程序中是可行的,则将文件设置为只读。有关详细信息,请参见 Windows 文档。
将您的 Web 应用程序的文件保存在应用程序根目录下的一个文件夹中。不要让用户指定在应用程序中进行文件访问的路径。这样有助于防止用户访问服务器的根目录。
不要使不需要调试 Visual Studio 应用程序的任何人成为“Debugger Users”(调试器用户)组的成员。此组具有比“VS Developers”(VS 开发人员)组更高的特权。有关详细信息,请参见添加调试器用户和配置 DCOM。
了解您的用户
在许多应用程序中,用户有可能不必提供凭据即可访问站点。如果是这样,则您的应用程序通过在预定义用户的上下文中运行即可访问资源。默认情况下,此上下文是 Web 服务器计算机上的本地 ASPNET 用户(Windows 2000 或 Windows XP)或 NETWORK SERVICE 用户 (Windows Server 2003)。
若要仅允许已授权用户进行访问,请遵循以下准则:
如果您的应用程序是 Intranet 应用程序,则将其配置为使用 Windows 集成安全性。这样,用户的登录凭据就可以用于访问资源。
如果您需要从用户收集凭据,则使用其中的一种 ASP.NET 身份验证策略。有关更多信息,请参见 ASP.NET 身份验证。有关示例,请参见简单 Forms 身份验证。
防止恶意用户的输入
通常,决不假定从用户获得的输入是安全的。对恶意用户来说,从客户端向您的应用程序发送潜在危险的信息是很容易的。若要防止恶意输入,请遵循以下准则:
在窗体中,筛选用户输入以查找 HTML 标记,其中可能包含脚本。有关详细信息,请参见在 Web 应用程序中防止脚本利用。
决不回显(显示)未经筛选的用户输入。在显示不受信任的信息之前,对 HTML 进行编码以将潜在有害的脚本转换为显示字符串。
类似地,决不将未经筛选的用户输入存储在数据库中。
如果要接受来自用户的一些 HTML,则手动筛选它。在您的筛选器中,显式定义将要接受的内容。不要创建一个试图筛选出恶意输入的筛选器;因为预料到所有可能的恶意输入是非常困难的。
不要假定您从标头(通常通过 Request 对象)获得的信息是安全的。对查询字符串、Cookie 等采取安全措施。注意浏览器向服务器报告的信息(用户代理信息)可以假冒,假如它在您的应用程序中是重要的。
如有可能,不要将敏感信息(如隐藏域或 Cookie)存储在可从浏览器访问的位置。例如,不要将用户名或密码存储在 Cookie 中。
注意 视图状态是以编码格式存储在隐藏域中的。默认情况下,它包含消息身份验证代码 (MAC),以便页可以确定视图状态是否已被篡改。
创建安全的错误信息
如果您不小心,恶意用户就可以从应用程序显示的错误信息推断出有关您的应用程序的重要信息。请遵循这些指导:
不要编写会回显可能对恶意用户有用的信息(例如用户名)的错误信息。
将应用程序配置为不向用户显示详细错误。如果为进行调试而要显示详细错误信息,请先检查该用户是否为 Web 服务器的本地用户。有关详细信息,请参见显示安全的错误信息。
对于容易发生错误的情况(如数据库访问)创建自定义错误处理方式。
安全地保守秘密
“秘密”是指您不希望某人知道的信息。一个典型的秘密是密码或加密密钥。当然,如果恶意用户能够获得秘密,则由该秘密保护的数据会受到危害。请遵循这些指导:
如果您的应用程序在浏览器和服务器之间传输敏感信息,请考虑使用安全套接字层 (SSL)。有关如何实现 SSL 的详细信息,请参见 Microsoft 知识库文章 Q307267“HOW TO:在 Windows 2000 中使用安全套接字层来保障 XML Web services 的安全”(HOW TO: Secure XML Web Services with Secure Socket Layer in Windows 2000)。
如果您必须存储秘密,即使是以您认为人们将无法看到它的形式(如在服务器代码中)进行保存,也不要将它保存在 Web 页中。
如果您对信息加密,请不要创建您自己的加密算法。请使用 Windows 数据保护 API (DPAPI)。
安全地使用 Cookie
为了让用户特定的信息保持可用,Cookie 是一种容易而有用的方法。但是,由于 Cookie 会被发送到浏览器所在的计算机,因此它们容易被假冒或用于其他恶意用途。请遵循这些指导:
不要将任何关键信息存储在 Cookie 中。例如,不要将用户的密码存储在 Cookie 中,即使是暂时存储也不要这样做。通常,不要将任何信息保存在 Cookie 中(一旦它被假冒,就会给您带来麻烦),而是在 Cookie 中保存对信息在服务器上的位置的引用。
设置 Cookie 的到期日期,使其有效期尽可能最短。如有可能,避免设置永久性 Cookie。
考虑对 Cookie 中的信息加密。
防止拒绝服务威胁
恶意用户危害您的应用程序的一种间接方式是使其不可用。恶意用户可以使应用程序太忙而无法为其他用户提供服务,或者仅仅使应用程序出现故障。请遵循这些指导:
关闭或释放您使用的任何资源。例如,在使用完毕后,始终关闭数据连接和数据读取器,而且始终关闭文件。
使用错误处理(例如,try-catch)。包含 finally 块,以便万一失败就可以在其中释放资源。
将 IIS 配置为使用进程调节,这样可以防止应用程序消耗过多的 CPU 时间。有关详细信息,请参见技术文章“使用 Internet Information Server 5.0 的新功能:第 1 部分”。
在使用或存储用户输入之前,测试它的大小限制。
对数据库查询的大小加以限制以确保安全。例如,在 Web 窗体页中显示查询结果之前,检查包含的记录数是否不合理。
如果文件上载是您的应用程序的一部分,则对它们的大小加以限制。您可以使用类似如下的语法在 Web.config 文件中设置限制(其中 maxRequestLength 值以千字节为单位):
<configuration>
<system.web>
<httpRuntime maxRequestLength="4096" />
</system.web>
</configuration>
紧跟 #1 猪猪 的帖子
脚本利用从浏览器的角度来看,Web 页只是一个长字符串。浏览器会顺序处理这个字符串,在此过程中,会显示某些字符,同时按特殊规则解释其他字符(如 <b> 和 <script>)。如果恶意用户可以将某些特殊字符插入到页中,则浏览器将不知道这些字符不应该处于该位置,将作为页的一部分处理它们。
一个简单化的脚本利用的工作方式如下所示。假定一个应用程序允许用户发送有关最新电影的评论以供其他用户阅读。利用步骤可能为:
应用程序显示一个用户可以输入评论的窗体。恶意用户编写了一个其中包含 <script> 块的评论。
发送窗体,恶意用户的评论将存储在数据库中。
另一用户访问该站点。在构造页时,应用程序会从数据库中读取评论并将它们放在页中。恶意用户的 <script> 块将写入页中,就好像它是文本评论一样。
当第二个用户的浏览器显示此页时,它将遇到 script 块并执行它。
恶意用户还可以使用其他方法来利用脚本。常见的情况是:大多数脚本利用都会要求应用程序接受恶意输入,并将其插入到页中(回显它),浏览器将在该页中执行它。这种利用带来的潜在损害取决于所执行的脚本。它可以是无足轻重的,如在浏览器中弹出的烦人的消息。但是,它也可以产生严重的损害,方法是偷窃 Cookie、偷窃用户输入(如密码),甚至在用户的计算机上运行本机代码(如果对 Internet 安全性的要求不严格)。
防止脚本利用
防止脚本利用的主要方法就是决不信任来自用户的信息。假定从浏览器发送到您的应用程序的任何数据都包含恶意脚本。
同样,每次将字符串写入页时,您都应该假定字符串可能包含恶意脚本。(除非您自己以编程方式创建了该字符串。)例如,在从数据库中读取字符串时,您应该假定它们可能包含恶意脚本。安全意识很强的开发人员甚至不信任他们自己的数据库,理由是他们认为恶意用户可能有办法篡改数据库。
ASP.NET 为您提供了几种防止脚本利用的方法:
ASP.NET 通过请求验证来自动防止脚本利用。默认情况下,如果 Request 对象包含 HTML 编码的元素或某些 HTML 字符(如表示长破折号的 —),则 ASP.NET 页框架将引发一个错误。建议您为此错误在应用程序中创建一个处理程序。
如果您要在应用程序中显示字符串,但不信任它们,则向它们应用 HTML 编码。例如,进行编码后,标记 <b> 将变成 <b>。如果您正在显示的字符串来自您尚未确定信任其内容的数据库时,您可能会这样做。
如果您希望应用程序接受某些 HTML(例如,来自用户的某些格式设置指令),请关闭自动检查,并创建筛选器来精确定义应用程序将接受哪些 HTML。
安全说明 决不要禁用自动请求验证而不添加您自己的检查或筛选器。
注意 不要创建试图只筛选出不可接受元素的筛选器,因为预料每个可能的错误输入十分困难。相反,如果您确实要创建筛选器,请创建具有可接受输入的已定义列表的筛选器。
在 Web 应用程序中防止脚本利用
大多数脚本利用发生在用户可以将可执行代码(脚本)插入您的应用程序时。默认情况下,ASP.NET 提供请求验证。不管窗体发送包含什么样的 HTML,该验证都会引发错误。
您可以使用下列方法防止脚本利用:
在接受或显示字符串之前,将 HTML 编码应用于它们,以便字符串不包括任何可执行元素。
如果您的应用程序需要接受某些 HTML,则禁用请求验证并创建您自己的 HTML 筛选器。
本主题中的过程说明如何执行这些任务。
应用 HTML 编码
HTML 编码使用 HTML 保留字符转换 HTML 元素,以便显示它们而不是执行它们。
应用 HTML 编码
在显示字符串之前,调用 Server 对象的 HtmlEncode 方法。HTML 元素会转换为浏览器将显示(而不解释为 HTML)的字符串表示形式。
以下示例说明 HTML 编码。在一个实例中,在显示用户输入之前对其进行编码。在第二个实例中,在显示数据库中的数据之前对其进行编码。
注意 只有通过向 @ Page 指令中添加 alidateRequest="false" 来在页中禁用请求验证时,此示例才将起作用。决不禁用请求验证而不添加您自己的检查或筛选器。
' Visual Basic
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e _
As System.EventArgs) Handles Button1.Click
Label1.Text = Server.HtmlEncode(TextBox1.Text)
Label2.Text = _
Server.HtmlEncode(dsCustomers.Customers(0).CompanyName)
End Sub
// C#
private void Button1_Click(object sender, System.EventArgs e)
{
Label1.Text = Server.HtmlEncode(TextBox1.Text);
Label2.Text =
Server.HtmlEncode(dsCustomers1.Customers[0].CompanyName);
}
筛选 HTML 元素
默认情况下,Web 窗体页检测发送到服务器的信息中的任何 HTML 元素和保留字符。这样,将防止用户试图将脚本嵌入您的应用程序。当页检测到 HTML 时,它会引发一个错误。您可以使用 Page_Error 或 Application_Error 处理程序捕捉此错误。有关详细信息,请参见显示安全的错误信息。
但是,如果您的应用程序需要接受某些 HTML 元素,可关闭请求验证,并创建一个只允许使用要接受的 HTML 元素的筛选器。
注意 不要创建试图只筛选出不可接受元素的筛选器,因为预料每个可能的错误输入十分困难。相反,如果您创建筛选器,则创建一个定义可接受输入的筛选器。
筛选 HTML 元素
通过将属性 ValidateRequest="false" 添加到 @ Page 指令中禁用请求验证。
安全说明 决不要禁用自动请求验证而不添加您自己的检查或筛选器。
使用 HtmlEncode 方法对字符串进行编码。
调用 String.Replace 方法,将要接受的已编码 HTML 标记转换回它们的 HTML 形式。
提示 如果您熟悉正则表达式,则可以使用一个正则表达式来高效地执行筛选。有关详细信息,请参见 .NET Framework 正则表达式。
以下示例说明一个简单的筛选器,它接受加粗元素和带下划线的元素(<b>、</b>、<u>、</u>)。在显示所有其他用户输入之前都对其进行编码。
安全说明 许多 HTML 标记都允许在其属性中使用脚本。例如,标记 <img src="javascript:alert('hi')"> 是合法的。如果您希望接受比简单的格式设置标记更复杂的 HTML 标记,则必须确保恶意用户无法假借允许的 HTML 标记将脚本传递到您的应用程序。
' Visual Basic
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim userinput As String = TextBox1.Text
userinput = Server.HtmlEncode(userinput)
' Accepts <b>, </b>, <u>, </u>, case-insensitive
userinput = userinput.Replace("<b>", "<b>")
userinput = userinput.Replace("</b>", "</b>")
userinput = userinput.Replace("<B>", "<B>")
userinput = userinput.Replace("</B>", "</B>")
userinput = userinput.Replace("<u>", "<u>")
userinput = userinput.Replace("</u>", "</u>")
userinput = userinput.Replace("<U>", "<U>")
userinput = userinput.Replace("</U>", "</U>")
Label1.Text = userinput
End Sub
// C#
private void Button1_Click(object sender, System.EventArgs e)
{
String userinput = TextBox1.Text;
userinput = Server.HtmlEncode(userinput);
// Accepts <b>, </b>, <u>, </u>, case-insensitive
userinput = userinput.Replace("<b>", "<b>");
userinput = userinput.Replace("</b>", "</b>");
userinput = userinput.Replace("<B>", "<B>");
userinput = userinput.Replace("</B>", "</B>");
userinput = userinput.Replace("<u>", "<u>");
userinput = userinput.Replace("</u>", "</u>");
userinput = userinput.Replace("<U>", "<U>");
userinput = userinput.Replace("</U>", "</U>");
Label1.Text = userinput;
}
SQL 语句利用
还有一种与脚本利用类似的利用,它导致恶意 SQL 语句的执行。如果应用程序提示用户输入信息并将用户的输入串联为表示 SQL 语句的字符串,则会出现这种情况。例如,应用程序可能提示输入客户姓名,目的是为了执行类似如下的语句:
"Select * From Customers where CustomerName = " & txtCustomerName.Value
但是,对数据库有所了解的恶意用户可能使用文本框输入包含客户姓名的嵌入式 SQL 语句,产生类似如下的语句:
Select * From Customers Where CustomerName = 'a' Delete From
Customers Where CustomerName > ''
执行该查询时,就会危害数据库。
为了防止 SQL 语句利用,决不使用字符串串联创建 SQL 查询。相反,使用参数化查询并将用户输入分配给参数对象。
数据适配器命令中的参数
数据适配器的命令(在 SelectCommand、InsertCommand、UpdateCommand 和 DeleteCommand 对象的 CommandText 属性中定义)经常涉及参数。在运行时,参数用于向命令代表的 SQL 语句或存储过程传递值。
参数在两种上下文中使用:
选择参数 - 在产品应用程序中,经常只获取数据库中数据的一个子集。其做法是,使用包含 WHERE 子句的 SQL 语句或存储过程,该子句具有用于获得选择判据(在运行时获取)的参数。此外,当更新或删除记录时,将使用 WHERE 子句指出要更改的一条或多条记录。WHERE 子句中使用的值通常在运行时导出。
更新参数 - 当更新现有记录或插入新记录时,已更改记录或新记录中列的值将在运行时建立。此外,开放式并发检查中使用的值也使用参数来建立。
注意 对于 Oracle,在 SQL 语句或存储过程中使用命名参数时,必须在参数名称前加冒号 (:)。但是,当在代码中的其他地方引用命名参数时(例如,当调用 Add 时),不要在命名参数前加冒号 (:)。数据提供程序自动提供冒号。有关更多信息,请参见 OracleParameter 类。
选择参数
当选择记录来填充数据集时,经常在 WHERE 子句中包括一个或多个参数,以便能够在运行时指定要获取哪些记录。例如,用户可能会搜索书籍数据库以查找其键入 Web 页的特定书名关键字。为了允许该操作,可以指定如下 SQL 语句作为 SelectCommand 的 CommandText 属性。参数用占位符(问号)或命名参数变量指示。涉及 OleDbCommand 和 OdbcCommand 对象的查询的参数使用问号;使用 SqlCommand 对象的查询使用以 @ 符号开头的命名参数,而使用 OracleCommand 对象的查询使用以冒号 (:) 开头的命名参数。
使用占位符的查询可能如下所示:
SELECT BookId, Title, Author, Price from BOOKS
WHERE (Title LIKE ?)
使用 SqlCommand 命名参数的查询可能如下所示:
SELECT BookId, Title, Author, Price from BOOKS
WHERE (Title LIKE @title)
使用 OracleCommand 命名参数的查询可能如下所示:
SELECT BookId, Title, Author, Price from BOOKS
WHERE (Title LIKE :title)
在应用程序中,您提示用户输入书名关键字。然后设置参数值并运行命令。
注意 有时,您可能需要获取数据库表的全部内容(例如在建立查找表时),但通常只需要获取所需数据以使应用程序效率较高。
在 Visual Studio 中,可以使用“查询生成器”生成带参数的 SQL 语句。如果从“服务器资源管理器”拖动元素,Visual Studio 可在某些情况下(但非所有情况下)配置参数,并且您需要手动完成配置。
更新参数
无论适配器的 SelectCommand 对象是否包含参数化命令,UpdateCommand、InsertCommand 和 DeleteCommand 属性的命令始终包含参数。
UpdateCommand 和 InsertCommand 属性的命令对于数据库中要更新的每一列都需要参数。此外,UpdateCommand 和 DeleteCommand 语句需要参数化的 WHERE 子句,它标识要更新的记录,其方式与经常配置 SelectCommand 对象的方式类似。
设想用户可以用其购买书籍的应用程序。用户在购物时将维护一个购物车(以数据表的形式实现)。在 ShoppingCart 表中,用户为要购买的每本书维持一条记录,以书籍 ID 和客户 ID 一起作为购物车记录的键。
当用户向购物车添加书籍时,应用程序可能调用 SQL INSERT 语句。在适配器中,该语句的语法可能如下所示:
INSERT INTO ShoppingCart
(BookId, CustId, Quantity)
Values (?, ?, ?)
三个问号代表参数占位符,它们将在运行时以客户 ID、书籍 ID 和数量的值填写。如果打算使用命名参数,同样的查询可能如下所示:
INSERT INTO ShoppingCart
(BookId, CustId, Quantity)
Values (@bookid, @custid, @quantity)
如果用户决定更改购物车中的某一项(例如更改其数量),则应用程序可能调用 SQL UPDATE 语句。该语句的语法可能如下所示:
UPDATE ShoppingCart
SET (BookId = ?, CustId = ?, Quantity = ?)
WHERE (BookId = ? AND CustId = ?)
或者如果打算使用命名参数,则可能如下所示:
UPDATE ShoppingCart
SET (BookId = @bookid, CustId = @custid, Quantity = @quantity)
WHERE (BookId = @bookid AND CustId = @custid)
在该语句中,SET 子句中的参数以已更改记录的更新值填写。WHERE 子句中的参数标识要更新哪条记录,并且以来自该记录的原始值填写。
用户还可以从购物车移除项。在此情况下,如果打算使用参数占位符,则应用程序可能调用具有如下语法的 SQL DELETE 语句:
DELETE FROM ShoppingCart
WHERE (BookId = ? AND CustId = ?)
或者如果打算使用命名参数,则可能如下所示:
DELETE FROM ShoppingCart
WHERE (BookId = @bookid AND CustId = @custid)
参数集合和参数对象
为允许您在运行时传递参数值,数据适配器的四个命令对象均支持 Parameters 属性。该属性包含单个参数对象的集合,这些对象与语句中的占位符一一对应。
下表显示了与每个数据适配器相对应的参数集合:
数据适配器 参数集合
SqlDataAdapter SqlParameterCollection
OleDbDataAdapter OleDbParameterCollection
OdbcDataAdapter OdbcParameterCollection
OracleDataAdapter OracleParameterCollection
注意 对于 Oracle,在 SQL 语句或存储过程中使用命名参数时,必须在参数名称前加冒号 (:)。但是,当在代码中的其他地方引用命名参数时(例如,当调用 Add 时),不要在命名参数前加冒号 (:)。Oracle .NET Framework 数据提供程序会自动提供冒号。
使用参数集合,您可省去必须手动将 SQL 命令构造为具有运行时值的字符串的麻烦。此外,还获得在参数中进行类型检查的好处。
如果使用“数据适配器配置向导”配置适配器,则为所有四个适配器命令自动建立和配置参数集合。如果从“服务器资源管理器”将元素拖动到窗体或组件上,则 Visual Studio 可以执行下列配置:
如果将表或某些列拖动到设计器上,则 Visual Studio 将生成一个不带参数的 SelectCommand 对象(明确地说是一条 SQL SELECT 语句),以及参数化的 UpdateCommand、InsertCommand 和 DeleteCommand 对象。如果希望 SelectCommand 对象语句有参数,则必须手动进行配置。
如果将存储过程拖动到设计器上,则 Visual Studio 将生成一个 SelectCommand 对象,带有存储过程所需的参数。但是,如果需要,您必须自己配置 UpdateCommand、InsertCommand 和 DeleteCommand 对象及其参数。
一般而言,如果想为适配器创建参数化查询,则应使用“数据适配器配置向导”。但是,如果需要,可以使用“属性”窗口手动配置参数。
参数集合的结构
命令的参数集合中的项与相应的命令对象所需的参数一一对应。如果命令对象是一条 SQL 语句,则集合中的项对应于该语句中的占位符(问号)。以下 UPDATE 语句需要有五个参数项的集合:
UPDATE ShoppingCart
SET (BookId = ?, CustId = ?, Quantity = ?)
WHERE (BookId = ? AND CustId = ?)
以下是带有命名参数的相同语句:
UPDATE ShoppingCart
SET (BookId = @bookid, CustId = @custid, Quantity = @quantity)
WHERE (BookId = @bookid AND CustId = @custid)
如果命令对象引用的是存储过程,则集合中的参数项数由该过程本身决定。参数与 SQL 语句中的占位符可能不完全对应。
在存储过程中,还可以对参数进行命名。在此情况下,参数在集合中的位置并不重要。相反,集合中的每个参数项都有一个 ParameterName 属性,用于使自身与存储过程中的相应参数匹配。
当手动配置参数集合时,必须确切理解哪些参数是存储过程需要的。许多存储过程返回值;如果是这样,该值在参数集合中传递回应用程序,因此您必须允许返回值。此外,某些存储过程包含多条 SQL 语句,您必须确保参数集合反映传递给过程中的全部语句的所有值。
如果参数不是命名参数(如在存储过程中那样),则集合中的项按位置映射到命令所需的参数。如果命令是一个存储过程并且返回值,则为该返回值保留集合中的第一项(零项)。
因此,可以根据集合中的索引位置引用单个参数对象。但是,参数对象还支持 ParameterName 属性,它提供一种独立于参数顺序来引用参数的方法。例如,下面两条语句可能等效(假定集合中的第二个参数被命名为 Title_Keyword):
' Visual Basic
' Encloses the keyword in SQL wildcard characters.
titleKeyword = "%" & txtTitleKeyword.Text & "%"
OleDbDataAdapter1.SelectCommand.Parameters(1).Value = titleKeyword
OleDbDataAdapter1.SelectCommand.Parameters("Title_Keyword").Value = titleKeyword
// C#
// Encloses the keyword in SQL wildcard characters.
string titleKeyword = "%" + txtTitleKeyword.Text + "%";
this.OleDbDataAdapter1.SelectCommand.Parameters[1].Value = titleKeyword;
this.OleDbDataAdapter1.SelectCommand.Parameters["Title_Keyword"].Value = titleKeyword;
编程时使用参数名通常比通过索引值引用参数好得多,因为它减少了在参数个数变化时的维护需要,并使您不必记忆存储过程是否返回值。通过名称而不是索引值来引用参数存在少许附加系统开销,但这可通过编程方便和应用程序的可维护性抵消。
建立参数值
建立参数值的方式有两种:
通过显式设置参数的 Value 属性。
通过将参数映射到数据集表中的列,以便可以在需要时从数据行提取值。
在填充数据集或调用命令时显式设置参数值(即对于选择参数)。例如,在上面搜索书籍的示例中,应用程序可能有一个文本框,用户在该框中输入书名关键字。然后,您在调用适配器的 Fill 方法之前,将参数的值显式设置为文本框的文本。完成该操作的代码可能如下所示,它在填充数据集之前将文本框的内容建立为参数。
' Visual Basic
' Encloses the keyword in SQL wildcard characters.
titleKeyword = "%" & txtTitleKeyword.Text & "%"
OleDbDataAdapter1.SelectCommand.Parameters("Title_Keyword").Value = titleKeyword
OleDbDataAdapter1.Fill(dsAuthors1)
// C#
// Encloses the keyword in SQL wildcard characters.
titleKeyword = "%" + txtTitleKeyword.Text + "%";
this.OleDbDataAdapter1.SelectCommand.Parameters["Title_Keyword"].Value = titleKeyword;
this.OleDbDataAdapter1.Fill(dsAuthors1);
映射的参数值在更新中使用。当调用适配器的 Update 方法时,该方法依次处理数据集表中的记录,分别为每条记录进行适当更新(更新、插入或删除)。在此情况下,参数值已可以作为数据集记录中的列使用。例如,当更新进程到达数据集表中的一个新记录(必须为其在数据库中调用 INSERT 语句)时,INSERT 语句的 VALUE 子句的值可以从该记录直接读出。
这些是典型方案,但不是唯一的几个方案。存储过程有时使用 out 参数或通过过程的返回值返回数据。如果是这样,返回值应映射到数据集表中的列。
也有可能显式设置更新参数。适配器支持 RowUpdating 事件,每次更新行时都将调用该事件。可以为该事件创建一个处理程序,并在其中设置参数值。这使您可以对参数值进行十分精确的控制,并可以执行诸如下面的处理:在参数值写入数据库记录之前动态创建参数值。
显示安全的错误信息
在您的应用程序显示错误信息时,它不应该泄露有助于恶意用户攻击您系统的信息。例如,如果您的应用程序试图登录数据库时没有成功,则显示的错误信息不应该包括它正在使用的用户名。
有许多方法可以控制错误信息:
将应用程序配置为不向远程(应用程序)用户显示详细错误信息。您也可以选择将错误重定向到应用程序页。
只要可行就包括错误处理,并编写您自己的错误信息。在您的错误处理程序中,您可以进行测试以确定用户是否为本地用户并作出相应的响应。
在捕捉所有未处理异常并将它们发送到一般错误页的页级别或应用程序级别上,创建全局错误处理程序。这样,即使您没有预料到某个问题,至少用户不会看到异常页。
将应用程序配置为不向远程用户显示错误
在应用程序的 Web.config 文件中,对 customErrors 元素进行以下更改:
将 mode 属性设置为 RemoteOnly(区分大小写)。这就将应用程序配置为仅向本地用户(您和开发人员)显示详细的错误。
(可选)包括指向应用程序错误页的 defaultRedirect 属性。
(可选)包括将错误重定向到特定页的 <error> 元素。例如,您可以将标准 404 错误(未找到页)重定向到您自己的应用程序页。
以下示例显示 Web.config 文件中的典型 customErrors 块。
<customErrors mode="RemoteOnly" defaultRedirect="AppErrors.aspx">
<error statusCode="404" redirect="NoSuchPage.aspx"/>
<error statusCode="403" redirect="NoAccessAllowed.aspx"/>
</customErrors>
包括错误处理
在可能产生错误的任何语句前后使用 try-catch-finally 块。
(可选)使用 Context 对象的 UserHostAddress 属性对本地用户进行测试并相应地修改错误处理。值 127.0.0.1 等效于“localhost”并指示浏览器与 Web 服务器位于同一台计算机上。
下面显示的是一个示例错误处理块。如果发生错误,则用有关消息的详细信息加载 Session 状态变量,然后应用程序显示可以读取 Session 变量并显示错误的页。(有意写入此错误以便不向用户提供任何可利用的详细信息。)如果用户是本地用户,则提供不同的错误详细信息。在 finally 块中,释放开放式资源。
' Visual Basic
Try
SqlConnection1.Open()
SqlDataAdapter1.Fill(Me.DsPubs1)
Catch ex As Exception
If HttpContext.Current.Request.UserHostAddress = "127.0.0.1" Then
Session("CurrentError") = ex.Message
Else
Session("CurrentError") = "Error processing page."
End If
Server.Transfer("ApplicationError.aspx")
Finally
SqlConnection1.Close()
End Try
// C#
try
{
sqlConnection1.Open();
sqlDataAdapter1.Fill(dsCustomers1);
}
catch (Exception ex)
{
if(HttpContext.Current.Request.UserHostAddress == "127.0.0.1")
{ Session["CurrentError"] = ex.Message; }
else
{ Session["CurrentError"] = "Error processing page."; }
Server.Transfer("ApplicationError.aspx");
}
finally
{
this.sqlConnection1.Close();
}
您也可以创建一个这样的错误处理程序,它在页级别上或为整个应用程序捕捉所有未处理的异常。
创建全局错误处理程序
要创建页中的全局处理程序,请创建 Page_Error 事件的处理程序。要创建应用程序范围的错误处理程序,请在 Global.asax 文件中将代码添加到 Application_Error 方法。只要您的页或应用程序中发生未处理的异常,就会调用这些方法。您可以从 HttpServerUtility.GetLastError 方法获取有关最新错误的信息。
注意 如果您具有全局错误处理程序,则它优先于在 Web.config customErrors 元素的 defaultRedirect 属性中指定的错误处理。
下面显示的是一个示例处理程序,它获取有关当前错误的信息,将其放在 Session 变量中,并调用可以提取和显示错误信息的一般错误处理页。
' Visual Basic
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
Session("CurrentError") = "Global: " & Server.GetLastError.Message
Server.Transfer("lasterr.aspx")
End Sub
// C#
protected void Application_Error(Object sender, EventArgs e)
{
Session["CurrentError"] = "Global: " +
Server.GetLastError().Message;
Server.Transfer("lasterr.aspx");
}
从 Web 应用程序访问 SQL Server
当 Web 应用程序涉及数据库访问时,它必须向 SQL Server 提供凭据(即它必须登录到 SQL Server),就像任何其他用户或进程一样。在 Web 应用程序中,这样会使问题复杂化。例如,如果 Web 应用程序以匿名方式运行,则可能没有要传递到 SQL Server 的凭据。
为 Web 应用程序设计 SQL Server 访问的方法有多种。您选择什么样的策略,取决于您的计算机是如何配置的以及您是否在 Intranet 上。最简单的选择有:
使用 Windows 集成安全性。此选择将用户的凭据传递到 SQL Server。由于委托问题,这样做是切实可行的,条件是 SQL Server 和 IIS 位于同一台计算机上,而且用户必须与 Web 服务器计算机位于同一域中。
使用 Windows 集成安全性访问 SQL Server
如果您的应用程序运行在基于 Windows 的 Intranet 上,则也许可以将 Windows 集成安全性用于数据库访问。集成安全性要求:
SQL Server 与 IIS 在同一台计算机上运行。
所有应用程序和用户位于同一域中,以便其凭据可由 Web 服务器使用。(也就是说,对于公共 Web 站点,Windows 集成安全性是不可行的。)
注意 如果 SQL Server 和 IIS 不在同一台计算机上,则请使用其他访问方法。有关详细信息,请参见使用显式凭据访问 SQL Server 和使用映射的 Windows 域用户访问 SQL Server。
若要使用 Windows 集成安全性访问 SQL Server,则需要配置应用程序的以下四个不同方面:
IIS
应用程序配置文件 (Web.config)
连接字符串
SQL Server 本身
配置 IIS
您需要在 IIS 中将应用程序配置为禁用匿名访问并启用 Windows 身份验证。
为 Windows 集成安全性配置 IIS
在 Windows 中,打开“Internet 信息服务”管理工具。
在 Windows 2000 中,单击“开始”菜单,依次指向“程序”、“管理工具”,然后单击“Internet 服务管理器”,即可打开该工具。
在 Windows XP 中,从“控制面板”中的“管理工具”中可打开该工具。
打开服务器的节点,然后继续打开节点,直到找到您的应用程序的节点(通常在“默认 Web 站点”之下)。
右击您的应用程序,然后选择“属性”。
在“目录安全性”选项卡中,单击“编辑”。
在“验证方法”对话框中,清除“匿名访问”框并确保选中“集成 Windows 身份验证”。
单击“确定”关闭所有对话框。
配置 Web.config 文件
在应用程序配置文件 (Web.config) 中,您可以建立应用程序使用的身份验证模式,并建立应用程序将模拟用户凭据(即该应用程序将以这名用户的身份运行)的模式?
配置 Web.config 以允许 Windows 集成安全性
打开应用程序的 Web.config 文件并向其添加以下元素:
<authentication mode="Windows" />
<identity impersonate="true"/>
<authentication> 元素可能已经存在。
注意 Web.config 中的元素区分大小写。
创建连接字符串
在创建连接字符串以访问 SQL Server 时,您必须包含通知 SQL Server 您正在使用 Windows 集成安全性的属性。
为 Windows 集成安全性配置连接字符串
在 SQL Server 的任何连接字符串中,包含属性 Trusted_Connection=Yes 并移除用户名和密码属性。
下面显示的是一个为 Windows 集成安全性配置的典型连接字符串:
"workstation id=WebServer1;packet size=4096;
Trusted_Connection=Yes;data source=mySqlServers";
persist security info=False;initial catalog=northwind"
配置 SQL Server
您必须将 SQL Server 设置为可以识别将访问它的用户。
为 Windows 集成安全性配置 SQL Server
从 Windows 的“开始”菜单中,选择“Microsoft SQL Server”,然后选择“企业管理器”。
打开服务器的节点,并展开希望将其权限授予用户的数据库的节点。
右击“用户”节点,并选择“数据库用户”。
在“数据库用户属性”对话框中,在“登录名”框中输入“域\用户名”,然后单击“确定”。或者,将 SQL Server 配置为允许所有域用户访问数据库。
作为本地 ASPNET 或 NETWORK SERVICE 进程(应用程序中 Web 应用程序的默认用户标识)访问 SQL Server。此选项非常适合于匿名访问,但只有在 SQL Server 与 Web 服务器在同一台计算机时才起作用。
以本地用户身份访问 SQL Server
当 ASP.NET 应用程序运行时,默认情况下它运行在名为 ASPNET(在 Windows 2000 和 Windows XP 中)或 NETWORK SERVICE(在 Windows Server 2003 中)的特殊本地用户的上下文中。此用户帐户具有的权限恰好足以在 Web 服务器计算机上运行应用程序。
如果 SQL Server 和 Web 服务器位于同一台计算机上,则可以将 ASPNET 或 NETWORK SERVICE 用户定义为 SQL Server 上的用户。
此方法的优点在于它是安全的,因为 Web 应用程序在有限的安全性上下文内运行。与 SQL Server 一起使用单个用户名也是高效的,因为这使 SQL Server 可以利用连接池增强应用程序的可伸缩性。
授予 ASPNET 或 NETWORK SERVICE 用户 SQL Server 权限
从 Windows 的“开始”菜单中,选择“Microsoft SQL Server”,然后选择“企业管理器”。
打开服务器的节点,并展开希望将其权限授予用户的数据库的节点。
右击“用户”节点,并选择“数据库用户”。
在“数据库用户属性”对话框中,在“登录名”框中输入“计算机名\ASPNET”,然后单击“确定”。
将 Web 应用程序进程映射到 Windows 域用户,然后以该用户身份登录到数据库。如果 SQL Server 与 Web 服务器位于不同的计算机上,此选择非常适合于匿名访问。
使用映射的 Windows 域用户访问 SQL Server
默认情况下,ASP.NET 应用程序运行在名为 ASPNET(在 Windows 2000 和 Windows XP 中)或 NETWORK SERVICE(在 Windows Server 2003 中)的本地用户的上下文中。这些用户帐户具有有限的权限。但是,ASPNET 或 NETWORK SERVICE 用户是 Web 服务器计算机的本地用户,因此未被识别为远程计算机上的用户。要避开此限制,您可以让应用程序在 Windows 域用户(Web 服务器计算机和 SQL Server 计算机都可以识别它)的上下文中运行。
将您的应用程序进程映射到 Windows 域用户要求您配置以下进程:
Web 服务器计算机 您必须确保指定的 Windows 域用户具有足够的特权(但没有多余的特权)运行 Web 应用程序。
您的应用程序 您需要配置 Web.config 文件以指定 ASP.NET 使用集成安全性,这样 ASP.NET 就可以识别映射的用户名。
注意 有关 machine.config 和 Web.config 文件的信息,请参见 ASP.NET 配置。
连接字符串 在为应用程序中的连接对象创建连接字符串时,您需要指定它们将使用集成安全性。
SQL Server 您需要将指定的域用户作为 SQL Server 登录用户进行添加。
在 Web 服务器计算机上配置用户
为 Windows 域用户设置用户权限
使用 Web 服务器计算机上的 Windows 管理工具,确保映射的 Windows 域用户具有所需的特权。有关详细信息,请参见 ASP.NET 必需的访问控制列表 (ACL)。
映射到 Windows 用户并启用模拟
在为 ASPNET 或 NETWORK SERVICE 用户帐户和映射到的用户建立正确权限之后,配置应用程序以模拟该用户。
为集成安全性配置 Web 应用程序
打开应用程序的 Web.config 文件并添加以下元素:
<authentication mode="Windows" />
<identity impersonate="true" userName="domain\username" password="password"/>
<authentication> 元素可能已经存在。
注意 Web.config 中的元素区分大小写。
在连接字符串中使用 Windows 安全性
最后,在为数据库访问创建连接字符串之后,请配置它们以使用 Windows 集成安全性。
在连接字符串中使用 Windows 安全性
在为应用程序创建连接字符串时,不要包含用户名和密码。相反,将连接字符串的 Integrated Security 属性设置为 SSPI。
以下示例显示包含适当属性的连接字符串:
data source=myserver;initial catalog=northwind;Integrated Security=SSPI
为集成安全性配置 SQL Server
从 Windows 的“开始”菜单中,选择“Microsoft SQL Server”,然后选择“企业管理器”。
打开服务器的节点,并展开希望将其权限授予用户的数据库的节点。
右击“用户”节点,并选择“数据库用户”。
在“数据库用户属性”对话框中,在“登录名”框中输入“域\用户名”,然后单击“确定”。
在连接字符串中传递显式用户名和密码。如果您可以提示用户输入凭据,则此选择是切实可行的,但是其安全性可能低于其他选择。您也可以将其作为映射到 Windows 域用户的更容易实现的替代选择。
使用显式凭据访问 SQL Server
连接到 SQL Server 的可靠方法是在连接字符串中传递用户名和密码。您可以以不同的方式使用此策略:
提示用户输入用户名和密码。
使用预设的用户名和密码。
安全说明 使用连接字符串访问 SQL Server 不如使用集成安全性安全。仅当在您的应用程序中使用集成安全性不可行时,才与显式凭据一起使用连接字符串。
提示输入凭据
您可以提示用户输入用户名和密码(例如,使用 Forms 身份验证),然后在连接字符串中将凭据传递到 SQL Server。提示输入凭据具有几个缺点。它要求 SQL Server 分别识别每个用户。而且,它对于连接池也是低效的,因此会限制可伸缩性。有关 Forms 身份验证的示例,请参见简单 Forms 身份验证。
安全说明 在应用程序中提示用户输入用户名和密码是一种潜在的安全威胁。凭据信息必须从浏览器传输到服务器上,而普通的 HTTP 传输未加密。为了获得最佳安全性,在应用程序中提示用户提供凭据时,应使用安全套接字层 (SSL),它在将信息从浏览器发送到服务器之前,对其进行加密。有关如何实现 SSL 的详细信息,请参见 Microsoft 知识库文章 Q307267HOW TO:Windows 2000 中带有安全套接字层的安全 XML Web services。
在连接字符串中动态地传递显式用户名
在获取用户的名称和密码之后,以代码形式将它们串联到连接字符串中,然后将它们分配给连接对象的 ConnectionString 属性。
安全说明 您应该确保用户名和密码不包含恶意内容。以下示例说明做到这一点的一种方法。有关更多信息,请参见脚本利用。
以下示例显示如何动态地创建连接字符串。假定用户名和密码在文本框中是可用的。用一个函数筛选用户输入,以确保它仅包含预设的字符。
提示 如果您熟悉正则表达式的使用,则可以创建此筛选器的更高效版本。有关详细信息,请参见正则表达式类。
' Visual Basic
Private Function ValidateInput(ByVal validchars As String, _
ByVal userinput As String) As Boolean
Dim c As Char
For Each c In userinput
If validchars.IndexOf(c) < 0 Then
Return False
End If
Next
Return True
End Function
Private Sub OpenDatabase()
Dim vChars As String = _
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*&%$#!"
Dim userid As String
Dim password As String
If ValidateInput(vChars, UserName.Text) Then
userid = UserName.Text
Else
Throw New System.Exception("Invalid user name.")
End If
If ValidateInput(vChars, PasswordText.Text) Then
password = PasswordText.Text
Else
Throw New System.Exception("Invalid password.")
End If
Dim connectionString As String
connectionString = "data source=myserver"
connectionString &= ";initial catalog=northwind"
connectionString &= ";user id=" & userid
connectionString &= ";password=" & password
SqlConnection1.ConnectionString = connectionString
SqlConnection1.Open()
' Further code to work with the database
End Sub
// C#
private Boolean ValidateInput(String validchars, String userinput)
{
foreach(Char c in userinput)
{
if(validchars.IndexOf(c) < 0)
{
return false;
}
}
return true;
}
private void OpenDatabase()
{
String vChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*&%$#!";
String userid;
String password;
if(ValidateInput(vChars, UserName.Text))
{
userid = UserName.Text;
}
else
{
throw new System.Exception("Invalid user name.");
}
if(ValidateInput(vChars, PasswordText.Text))
{
password = PasswordText.Text;
}
else
{
throw new System.Exception("Invalid password.");
}
String connectionString;
connectionString = "data source=myserver";
connectionString += ";initial catalog=northwind";
connectionString += ";user id=" + userid;
connectionString += ";password=" + password;
sqlConnection1.ConnectionString = connectionString;
sqlConnection1.Open();
// Further code to work with the database
}
使用预设的凭据
如果没有提示您输入凭据,则可以使用预设的用户名和密码。推荐的方法是将预设的用户名和密码存储在服务器上,然后在运行时读取它并将它添加到连接字符串。此技术的优点是:您的应用程序可以根据它需要在数据库中执行的操作,在不同情况下使用不同的凭据访问数据库。
安全说明 决不将凭据以字符串形式硬编码到您应用程序的程序中。能够访问代码文件(甚至是已编译的代码)的任何人员,都将能够获取凭据。
安全说明 始终将资源的最少访问特权给予预设用户名。决不使用“sa”或任何其他管理级别的用户名。始终使用密码。
您可以将凭据存储在各种不同的位置。两个选项是:
在 Web.config 文件中。这使与应用程序一起部署配置信息变得很容易。虽然配置文件与 ASP.NET 应用程序存储在同一目录中,但是 ASP.NET 可以防止客户端直接访问扩展名为 .config 的文件。为了获得额外的安全性,您可以设置配置文件的 NTFS 文件系统权限并对该文件内的连接字符串进行加密。
在 Windows 注册表中,作为自定义子项。凭据没有存储在可通过 Web 访问的目录中,这样可以降低字符串的潜在可视性。但是,此方法使部署更复杂,因为安装应用程序要求更改目标计算机的注册表。为了获得额外的安全性,您可以使用访问控制列表 (ACL) 限制对注册表子项的访问并对子项内的连接字符串进行加密。
注意 有关存储数据库凭据的进一步信息,请参见数据库安全性。
如果您确实将凭据作为应用程序的一部分进行存储,则应该尝试通过加密来保护它们。一种可能性是使用“数据保护 API”(DPAPI)。有关详细信息,请参见 MSDN 技术文章“Windows 数据保护”。
在 Web.config 中存储凭据
在 Web.config 文件中,在 <appSettings> 元素中创建一个新的 <add> 关键字。<appSettings> 元素必须作为 <configuration> 元素的子元素出现。有关详细信息,请参见配置节架构。
以下示例说明包含标记为“mappedname”和“mappedkey”的用户名和密码的 <add> 关键字。
提示 在应用程序中最好不使用字符串“password”和“user name”(或“username”),这样,搜索这些词的恶意用户就找不到它们。
<appSettings>
<add key="mappedname" value="MyWebAppUser"/>
<add key="mappedkey" value="Aje$31"/>
</appSettings>
在您的应用程序中,从 System.Configuration.ConfigurationSettings 类的静态 AppSettings 属性读取凭据。
以下示例显示如何在运行时读取凭据并将它们串联到连接字符串中:
' Visual Basic
Dim connectionString As String
Dim user As String = System.Configuration.ConfigurationSettings. _
AppSettings("mappedname")
Dim pass As String = System.Configuration.ConfigurationSettings. _
AppSettings("mappedkey")
connectionString = "data source=myserver;initial catalog=northwind"
connectionString &= ";user id=" & user
connectionString &= ";password=" & pass
SqlConnection1.ConnectionString = connectionString
// C#
String connectionString;
String user = System.Configuration.ConfigurationSettings.
AppSettings["mappedname"];
String pass = System.Configuration.ConfigurationSettings.
AppSettings["mappedkey"];
connectionString = "data source=myserver;initial catalog=northwind";
connectionString += ";user id=" + user;
connectionString += ";password=" + pass;
sqlConnection1.ConnectionString = connectionString;
在注册表中存储凭据
向 HKEY_LOCAL_MACHINE 配置单元的 SOFTWARE 项中添加新的子项。
在您的应用程序中,使用 Registry.LocalMachine 对象的 OpenSubKey 方法访问子项。
注意 为了使用 Registry 类,您必须导入 System 和 Microsoft.Win32 命名空间。
以下示例说明如何从子项“Connection”中获取用户名和密码(作为名为“mappedname”和“mappedkey”的字符串值存储):
' Visual Basic
Dim user, pass, connectionString As String
user = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( _
"Software\Connection").GetValue("mappedname").ToString()
pass = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( _
"Software\Connection").GetValue("mappedkey").ToString()
connectionString = "data source=myserver;initial catalog=northwind"
connectionString &= ";user id=" & user
connectionString &= ";password=" & pass
SqlConnection1.ConnectionString = connectionString
// C#
String user, pass, connectionString;
user = Microsoft.Win32.Registry.LocalMachine.
OpenSubKey("Software\\Connection").
GetValue("mappedname").ToString();
pass = Microsoft.Win32.Registry.LocalMachine.
OpenSubKey("Software\\Connection").
GetValue("mappedkey").ToString();
connectionString = "data source=myserver;initial catalog=northwind";
connectionString += ";user id=" + user;
connectionString += ";password=" + pass;
sqlConnection1.ConnectionString = connectionString;
ASP.NET 授权
授权的目的是确定是否应该授予某个标识对给定资源请求的访问权限类型。有两种基本方式来授予对给定资源的访问权限:
文件授权
文件授权由 FileAuthorizationModule 执行,它在使用 Windows 身份验证时处于活动状态。它执行 .aspx 或 .asmx 处理程序文件的访问控制列表 (ACL) 检查以确定用户是否应该具有访问权限。应用程序可以进一步使用模拟在正在访问的资源上进行资源检查。有关模拟的更多信息,请参见 ASP.NET 模拟。
URL 授权
URL 授权由 URLAuthorizationModule 执行,它将用户和角色映射到 URL 命名空间的块上。此模块实现正和负两种授权断言。也就是说,对于某些集、用户或角色,该模块可用于有选择地允许或拒绝对 URL 命名空间的任意部分的访问。
URLAuthorizationModule 在任何时候都是可用的。只需在配置文件的 <authorization> 部分的 <allow> 或 <deny> 元素中放置用户和/或角色的列表即可。
若要建立访问特定目录的条件,则必须将一个包含 <authorization> 部分的配置文件放置在该目录中。为该目录设置的条件也会应用到其子目录,除非子目录中的配置文件重写这些条件。此部分的常规语法如下所示。
<[element] [users] [roles] [verbs]/>
元素是必需的。必须包含 users 或 roles 属性。可以同时包含二者,但这不是必需的。verbs 属性是可选的。
允许的元素有 <allow> 和 <deny>,它们分别授予和撤消访问权限。每个元素支持三个属性,这些属性在下面的表中定义。
属性 说明
roles 标识此元素的目标角色。请求所关联的 IPrincipal 对象确定角色成员。可以将任意 IPrincipal 对象附加到给定请求的上下文中,这些对象可通过您喜欢的任何方式来确定角色成员。例如,默认的 WindowsPrincipal 类使用 Microsoft Windows NT 组来确定角色成员。
users 标识此元素的目标身份。
verbs 定义操作所要应用到的 HTTP 谓词,如 GET、HEAD 和 POST。
还会拒绝匿名用户。
以下示例向 Kim 和管理角色的成员授予权限,而拒绝 John 和所有匿名用户:
<authorization>
<allow users="Kim"/>
<allow roles="Admins"/>
<deny users="John"/>
<deny users="?"/>
</authorization>
用户和角色都可以通过使用逗号分隔的列表来引用多个实体,如下面的示例所示。
<allow users="John, Kim, contoso\Jane"/>
注意,域帐户 (contoso\Jane) 必须同时包括域和用户名的组合。
除身份名称外,还有两种特殊身份,如下表所示。
标识 说明
* 指所有身份
? 指匿名身份
若要允许 John 并拒绝其他任何人,可以构造下面的配置部分。
<authorization>
<allow users="John"/>
<deny users="*"/>
</authorization>
下面的示例允许每个人使用 GET,但只有 Kim 可以使用 POST。
<authorization>
<allow verb="GET" users="*"/>
<allow verb="POST" users="Kim"/>
<deny verb="POST" users="*"/>
</authorization>
使用下面的试探法应用规则:
位于较低目录级别的配置文件中包含的规则优先于位于较高目录级别的规则。系统通过构造一个 URL 的所有规则的合并列表,其中最近(层次结构中距离最近)的规则位于列表头,来确定哪条规则优先。
给定 URL 的一组合并的规则,系统从列表头开始,检查规则直到找到第一个匹配项为止。注意,ASP.NET 的默认配置包含向所有用户授权的 <allow users="*"> 元素。如果没有匹配的规则,则将允许请求,除非另外拒绝。如果找到匹配项并且匹配项是 <deny> 元素,则它将返回 401 状态代码。应用程序或站点可以方便地配置位于其站点或应用程序顶层的 <deny users="*"> 元素以防止此行为。
如果是 <allow> 匹配,则模块不执行任何操作,允许进一步处理请求。
还有 <location> 标记,您可以使用该标记来指定特定的文件或目录,由该标记环绕(即在 <location> 和 </location> 标记之间)的那些设置将应用到该文件或目录。
简单 Forms 身份验证
此示例展示 ASP.NET Forms 身份验证可能的最简单的实现。它旨在于阐释有关如何创建使用 Forms 身份验证的 ASP.NET 应用程序的基础知识。有关使用 XML 文件来保存用户名和密码的 Forms 身份验证的更复杂示例,请参见使用 XML 用户文件的 Forms 身份验证。
在此方案中,客户请求受保护的资源 Default.aspx。只有下面这一个用户可以获得对该受保护资源的访问权限:[email]jdoe@somewhere.com[/email],密码为 password。该用户名和密码已硬编码到 Logon.aspx 文件中。这里涉及三个文件:Web.config、Logon.aspx 和 Default.aspx。这些文件驻留在应用程序根目录下。这些文件的代码将在下面的讨论中进行分析。
Web.config
应设置 Web.config 配置文件以具有下列项,并将该配置文件放在应用程序根目录(Default.aspx 驻留的目录)中。
<configuration>
<system.web>
安装 Web.config 配置文件
将身份验证模式设置为 Forms。其他可能的值为 Windows、Passport 和 None(空字符串)。此示例中,必须为 Forms。
<authentication mode="Forms">
设置 Forms 身份验证属性。
<forms
将 loginUrl 属性设置为“logon.aspx”。如果 ASP.NET 没有找到针对请求的身份验证 Cookie,则 Logon.aspx 是用于重定向的 URL。
loginUrl = "logon.aspx"
设置该 Cookie 的名称后缀。
name = ".ASPXFORMSAUTH"/>
拒绝未经身份验证的用户访问此目录。
</authentication>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
Logon.aspx
Logon.aspx 是当 ASP.NET 没有找到针对请求的身份验证票时,该请求被重定向到的文件。此文件名是在 Web.config(即配置文件)中指定的。向客户端用户提供了一个窗体,该窗体包含两个文本框(“用户电子邮件名称”和“密码”)和一个“提交”按钮。用户输入电子邮件名称和密码,然后单击“提交”按钮。然后代码将此名称和密码与硬编码在 If 语句中的相应对进行比较。如果比较成功,则将用户连接到 Default.aspx;如果失败,则向用户显示错误信息。
实现登录功能
导入必需的命名空间。
<%@ Import Namespace="System.Web.Security" %>
设置脚本语言。
<html>
[Visual Basic]
<script language="VB" runat=server>
[C#]
<script language="C#" runat=server>
创建 Logon_Click 事件处理程序来处理提交事件。
[Visual Basic]
Sub Logon_Click(sender As Object, e As EventArgs)
[C#]
void Logon_Click(Object sender, EventArgs e)
{
通过对输入名称和密码与硬编码到代码中的名称和密码([email]jchen@contoso.com[/email] 和 password)进行比较来验证用户的身份。如果比较成功,则将请求重定向到受保护的资源 (Default.aspx)。如果比较失败,则显示错误信息。
[Visual Basic]
If ((UserEmail.Value = "[email]jchen@contoso.com[/email]") And _
(UserPass.Value = "password")) Then
FormsAuthentication.RedirectFromLoginPage _
(UserEmail.Value, Persist.Checked)
Else
Msg.Text = "Invalid Credentials: Please try again."
End If
End Sub
</script>
[C#]
if ((UserEmail.Value == "[email]jchen@contoso.com[/email]") &&
(UserPass.Value == "password"))
{
FormsAuthentication.RedirectFromLoginPage
(UserEmail.Value, Persist.Checked);
}
else
{
Msg.Text = "Invalid Credentials: Please try again.";
}
}
</script>
显示窗体以收集登录信息。
<body>
<form runat=server>
<h3><font face="Verdana">Logon Page</font></h3>
<table>
<tr>
创建“用户电子邮件名称”(User E-mail Name) 文本框。添加一个必填字段验证程序控件和一个检查电子邮件输入是否有效的正则表达式验证程序控件。
<td>Email:</td>
<td><input id="UserEmail" type="text" runat=server/></td>
<td><ASP:RequiredFieldValidator
ControlToValidate="UserEmail"
Display="Static"
ErrorMessage="Cannot be empty."
runat=server/>
</td>
<td><asp:RegularExpressionValidator id="RegexValidator"
ControlToValidate="UserEmail"
ValidationExpression="^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"
EnableClientScript="false"
Display="Static"
ErrorMessage="Invalid format for e-mail address."
runat="server"/>
</td>
</tr>
<tr>
创建“密码”(Password) 文本框。
<td>Password:</td>
<td><input id="UserPass" type=password runat=server/></td>
<td><ASP:RequiredFieldValidator
ControlToValidate="UserPass"
Display="Static"
ErrorMessage="Cannot be empty."
runat=server/>
</td>
</tr>
<tr>
创建一个“持久性 Cookie”(Persistent Cookie) 复选框。如果选中“持久性 Cookie”框,则该 Cookie 的有效期将跨浏览器会话。否则,在关闭浏览器时将销毁该 Cookie。
<td>Persistent Cookie:</td>
<td><ASP:CheckBox id=Persist runat="server"
autopostback="true"/>
</td>
<td></td>
</tr>
</table>
创建在回发时激发 Logon_Click 事件的“提交”按钮。
<input type="submit" OnServerClick="Logon_Click" Value="Logon"
runat="server"/>
<p><asp:Label id="Msg" ForeColor="red" Font-Name="Verdana"
Font-Size="10" runat=server/></p>
</form>
</body>
</html>
Default.aspx
Default.aspx 文件是被请求的、受保护的资源。它是仅显示字符串 Hello、记录的电子邮件名称和“注销”(Signout) 按钮的简单文件。
[Visual Basic]
<%@ Page LANGUAGE="VB" %>
<html>
<head>
<title>Forms Authentication</title>
<script runat=server>
Sub Page_Load(Src As Object, e As EventArgs)
Welcome.InnerHtml = "Hello, " + Context.User.Identity.Name
End Sub
Sub Signout_Click(sender As Object, e As EventArgs)
FormsAuthentication.SignOut()
Response.Redirect("logon.aspx")
End Sub
</script>
<body>
<h3><font face="Verdana">Using Forms Authentication</font></h3>
<span id="Welcome" runat=server/>
<form runat=server>
<input type="submit" OnServerClick="Signout_Click" Value="Signout"
runat="server"/><p>
</form>
</body>
</html>
[C#]
<%@ Page LANGUAGE="C#" %>
<html>
<head>
<title>Forms Authentication</title>
<script runat=server>
private void Page_Load(Object Src, EventArgs e )
{
Welcome.InnerHtml = "Hello, " + Context.User.Identity.Name;
}
private void Signout_Click(Object sender, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect("logon.aspx");
}
</script>
<body>
<h3><font face="Verdana">Using Forms Authentication</font></h3>
<span id="Welcome" runat=server/>
<form runat=server>
<input type="submit" OnServerClick="Signout_Click" Value="Signout"
runat="server"/><p>
</form>
</body>
</html>
部署 Web 解决方案
在本演练中,您将使用 Visual Basic 创建一个简单的 Web 窗体应用程序,然后创建一个安装程序将该应用程序部署到 Web 服务器上。
注意 在 Visual Basic .NET 标准版中不能使用 Web 安装项目。有关更多信息,请参见 Visual Basic 标准版功能。
创建 Web 窗体应用程序
在“文件”菜单上指向“新建”,然后选择“项目”。
在“新建项目”对话框中,选择“项目类型”窗格中的“Visual Basic 项目”,然后选择“模板”窗格中的“ASP.NET Web 应用程序”。在“位置”框中,键入 localhost/WebApp1。
单击“确定”关闭对话框。
项目被添加到解决方案资源管理器中,并且 Web 窗体设计器打开。
在“工具箱”中选择“Web 窗体”选项卡。
选择 Button 控件并将其拖到 Web 窗体中。
双击 Button 控件为该按钮添加事件处理程序。
在事件处理程序中添加下面的代码:
Button1.Text = "abc"
这将把单击按钮时按钮上显示的文本更改为“abc”。
在“生成”菜单上,选择“生成 WebApp1”以生成该应用程序。
创建部署项目
在“文件”菜单上指向“添加项目”,然后选择“新建项目”。
在“添加新项目”对话框中,选择“项目类型”窗格中的“安装和部署项目”,然后选择“模板”窗格中的“Web 安装项目”。在“名称”框中键入 WebDeploy。
单击“确定”关闭对话框。
项目被添加到解决方案资源管理器中,并且文件系统编辑器打开。
在“属性”窗口中,选择 ProductName 属性,并键入 WebApp1。
将 WebApp1 项目的输出添加到部署项目中
在“文件系统编辑器”中,选择“Web 应用程序”文件夹。在“操作”菜单上,指向“添加”,然后选择“项目输出”。
在“添加项目输出组”对话框中,选择“项目”下拉列表中的“WebApp1”。
单击“确定”关闭对话框。
从列表中选择“主输出”和“内容文件”组,然后单击“确定”。
设置安装程序的属性
选择 WebApplication 文件夹。在“属性”窗口中,将 VirtualDirectory 属性设置为 xyz。
在“属性”窗口中,将 DefaultDocument 属性设置为 WebForm1.aspx。
在“生成”菜单上选择“生成 WebDeploy”。
将应用程序部署到开发计算机上的 Web 服务器上
在解决方案资源管理器中,选择 WebDeploy 项目。在“项目”菜单上选择“安装”。
注意 必须在计算机上具有安装权限才能运行安装程序,而且必须具有 IIS(Internet 信息服务)权限才能安装到 Web 服务器。
若要访问已部署的 Web 应用程序,请启动 Internet Explorer,然后键入 URL localhost/xyz。注意,当在 Web 页上单击该按钮时,该按钮上的文本将更改为“abc”。
将应用程序部署到另一台计算机上的 Web 服务器
在 Windows 资源管理器中,定位到项目目录并查找所生成的安装程序。默认路径是 \documents and settings\yourloginname\ My Documents\Visual Studio Projects\WebApp1\WebDeploy\project configuration\WebDeploy.msi。默认“项目配置”是“调试”。
将该目录中的 WebDeploy.msi 文件以及其他所有文件和子目录复制到 Web 服务器计算机上。
在 Web 服务器计算机上,双击 Setup.exe 文件运行安装程序。
注意 必须在计算机上具有安装权限才能运行安装程序,而且必须具有 IIS 权限才能安装到 Web 服务器。
若要访问已部署的 Web 应用程序,请启动 Internet Explorer,并键入 URL [ComputerName]/xyz。注意,当在 Web 页上单击该按钮时,该按钮上的文本将更改为“abc”。
卸载应用程序
在 Windows“控制面板”中,双击“添加/删除程序”。
在“添加/删除程序”对话框中,选择“WebApp1”,并单击“删除”。
单击“确定”关闭对话框。
提示 若要从开发计算机上卸载,请在“项目”菜单上选择“卸载”。
使用业务对象
在业务组件中封装逻辑是实际应用程序的基本组成部分,不论是基于 Web 的应用程序还是任何其他应用程序。在 ASP.NET 中,业务对象是多层 Web 应用程序(如包含数据访问层或通用应用程序规则层的应用程序)的生成块。本节说明如何编写一些简单的组件并将它们包括在应用程序的 Web 窗体页中。
应用程序 /Bin 目录
使用 Web 应用程序组件的 COM 模型时遇到的问题是,这些组件必须注册(通常是使用 regsvr32 工具)后才能在传统的 ASP 应用程序中使用。对这些类型的应用程序进行远程管理通常是不可能的,因为注册工具必须在服务器上以本地方式运行。更为棘手的是,这些组件经应用程序加载后便在磁盘上保持锁定状态,必须使整个 Web 服务器停止运行后才能替换或移除这些组件。
为尝试解决这些问题,ASP.NET 允许将组件放置在已知目录中并在运行时自动找到它们。该已知目录总是命名为 /bin,并紧位于应用程序根目录(一个由 Internet 信息服务 (IIS) 定义的虚拟目录)下。这样做的好处是组件无需注册便可在 ASP.NET Framework 应用程序中使用——只需将组件复制到 /bin 目录或执行 FTP 文件传输便可部署组件。
除提供零注册方法部署复杂组件外,ASP.NET 不要求这些组件在运行时在磁盘上保持锁定状态。ASP.NET 在后台复制 /bin 中的程序集并加载这些 shadow 副本。原始组件即使在 Web 服务器仍在运行时也可以替换,而对 /bin 目录的更改则由运行时自动获取。当检测到更改时,ASP.NET 允许完成当前执行的请求,并将所有新传入的请求定向到使用新组件的应用程序。
导入业务对象
从最基本的层面看,业务组件只是一个类,可以从导入它的 Web 窗体页为其创建实例。下面的示例定义一个简单的 HelloWorld 类。该类有一个公共构造函数(当初次创建该类的实例时执行此构造函数)、一个称为 FirstName 的 String 属性和一个通过 FirstName 属性值打印问候语的 SayHello 方法。
using System;
using System.Text;
namespace HelloWorld {
public class HelloObj {
private String _name;
public HelloObj() {
_name = null;
}
public String FirstName {
get {
return _name;
}
set {
_name = value;
}
}
public String SayHello() {
StringBuilder sb = new StringBuilder("Hello ");
if (_name != null)
sb.Append(_name);
else
sb.Append("World");
sb.Append("!");
return sb.ToString();
}
}
}
Imports System
Imports System.Text
Namespace HelloWorld
Public Class HelloObj
Private _name As String
Public Sub New
MyBase.New()
_name = Nothing
End Sub
Public Property FirstName As String
Get
Return(_name)
End get
Set
_name = value
End Set
End Property
Public Function SayHello() As String
Dim sb As New StringBuilder("Hello ")
If (_name <> Nothing) Then
sb.Append(_name)
Else
sb.Append("World")
End If
sb.Append("!")
Return(sb.ToString())
End Function
End Class
End Namespace
import System;
import System.Text;
package HelloWorld {
public class HelloObj {
private var _name:String;
public function HelloObj() {
_name = null;
}
public function get FirstName() : String
{
return _name;
}
public function set FirstName(value:String) : void
{
_name = value;
}
public function SayHello() : String {
sb:StringBuilder = new StringBuilder("Hello ");
if (_name != null)
sb.Append(_name);
else
sb.Append("World");
sb.Append("!");
return sb.ToString();
}
}
}
C# VB JScript
若要编译该类,应从命令行运行 C# 编译器 (Csc.exe)。/t 选项通知编译器生成库 (DLL),/out 选项通知编译器将结果程序集放置到的位置。本例中,应用程序的 /bin 目录紧位于本教程的“aspplus”虚拟根目录下,并假设该命令从示例目录,即 ...\QuickStart\AspPlus\Samples\WebForms\Busobjs 运行。
csc /t:library /out:..\..\..\..\bin\HelloObj.dll HelloObj.cs
对于 Visual Basic,等效编译命令是:
vbc /t:library /out:..\..\..\..\bin\HelloObjVB.dll HelloObj.vb
对于 JScript,等效编译命令是:
jsc /out:..\..\..\..\bin\HelloObjJS.dll HelloObj.js
该组件现在可供应用程序中任何需要使用它的 Web 窗体页使用。下面的 HelloObj.aspx 示例阐释了此功能。
简单的两层 Web 窗体页
外部组件的典型用途是执行数据访问。它简化了页中的代码,增强了可读性并将用户界面 (UI) 逻辑与系统功能分开。下面的实例阐释了简单的两层 Web 窗体页,它使用数据访问组件检索产品信息。
<%@ Import Namespace="DataLayer" %>
<html>
<script language="C#" runat="server">
public void Page_Load(Object sender, EventArgs E)
{
if (!IsPostBack)
{
DataObj data = new DataObj("server=(local)\\NetSDK;database=grocertogo;Integrated Security=SSPI");
Categories.DataSource = data.GetCategories();
Categories.DataBind();
}
}
public void Submit_Click(Object sender, EventArgs E)
{
DataObj data = new DataObj("server=(local)\\NetSDK;database=grocertogo;Integrated Security=SSPI");
Products.DataSource = data.GetProductsForCategory(Categories.SelectedItem.Value);
Products.DataBind();
}
</script>
<body style="font: 10pt verdana" bgcolor="ffffcc">
<form runat="server">
<h3>A Simple Two-Tier WebForm</h3>
Select a Category:
<ASP:DropDownList id="Categories" DataValueField="CategoryName" runat="server"/>
<input type="Submit" OnServerClick="Submit_Click" Value="Get Products" runat="server"/><p>
<ASP:DataList id="Products" ShowHeader=false ShowFooter=false RepeatColumns="2" RepeatDirection="horizontal" BorderWidth=0 runat="server">
<ItemTemplate>
<table>
<tr>
<td width="150" style="text-align:center; font-size:8pt; vertical-align:top; height:200">
<ASP:ImageButton borderwidth=6 bordercolor="#ffffcc" command="Select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat="server"/>
<p>
<%# DataBinder.Eval(Container.DataItem, "ProductName") %> <br>
<%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}").ToString() %>
</td>
</tr>
</table>
</ItemTemplate>
</ASP:DataList>
</form>
</body>
</html>
using System.Reflection;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("A QuickStart Tutorial Assembly")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft QuickStart Tutorials")]
[assembly: AssemblyCopyright(" Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.1.*")]
namespace DataLayer
{
using System;
using System.Data;
using System.Data.SqlClient;
public class DataObj
{
private String _connStr;
public DataObj()
{
_connStr = null;
}
public DataObj(String connStr)
{
_connStr = connStr;
}
public String ConnectionString
{
get
{
return _connStr;
}
set
{
_connStr = value;
}
}
public DataView GetCategories()
{
SqlConnection myConnection = new SqlConnection(_connStr);
SqlDataAdapter myCommand = new SqlDataAdapter("select distinct CategoryName from Categories", myConnection);
DataSet ds = new DataSet();
try {
myCommand.Fill(ds, "Categories");
return ds.Tables["Categories"].DefaultView;
}
catch (Exception ex){
throw (ex);
}
finally{
myConnection.Close();
}
}
public DataView GetProductsForCategory(String category)
{
SqlConnection myConnection = new SqlConnection(_connStr);
SqlDataAdapter myCommand = new SqlDataAdapter("select ProductName, ImagePath, UnitPrice, c.CategoryId from Products p, Categories c where c.CategoryName='" + category + "' and p.CategoryId = c.CategoryId", myConnection);
DataSet ds = new DataSet();
try {
myCommand.Fill(ds, "Products");
return ds.Tables["Products"].DefaultView;
}
catch (Exception ex){
throw (ex);
}
finally{
myConnection.Close();
}
}
}
}
简单的三层 Web 窗体页
三层应用程序模型扩展了两层方案,在用户界面和数据访问逻辑之间包含了业务规则。此模型允许用户界面开发人员处理更高抽象化的级别,而不是通过低级数据访问组件 API 直接操作数据。中间业务组件通常强制业务规则并确保遵守数据库的关系和主键约束。下面的示例使用中间组件基于客户端输入的两位供应商 ID 计算折扣。
<%@ Page ClientTarget="DownLevel"%>
<%@ Import Namespace="BusinessLayer" %>
<html>
<script language="VB" runat="server">
Sub Page_Load(Sender As Object, E As EventArgs)
If Not (IsPostBack)
Dim Bus As BusObjVB
Bus = New BusObjVB()
Categories.DataSource = Bus.GetCategories()
Categories.DataBind()
End If
End Sub
Sub Submit_Click(Sender As Object, E As EventArgs)
If (Page.IsValid)
Dim Id As Integer = 0
If Not (CustomerId.Text = "")
Id = CInt(CustomerId.Text)
End If
Dim Bus As BusObjVB
Bus = new BusObjVB()
Products.DataSource = Bus.GetProductsForCategory(Categories.SelectedItem.Value,Id)
End If
Products.DataBind()
End Sub
</script>
<body style="font: 10pt verdana" bgcolor="ffffcc">
<form runat="server">
<h3>A Simple Three-Tier Web Form</h3>
Select a Category:
<ASP:DropDownList id="Categories" DataValueField="CategoryName" runat="server"/>
Preferred Customer ID:
<ASP:TextBox id="CustomerId" Width="35" runat="server"/>
<input type="Submit" OnServerClick="Submit_Click" Value="Get Products" runat="server"/>
<asp:RegularExpressionValidator id="RegExValidator"
ControlToValidate="CustomerId"
ValidationExpression="[0-9]{2}"
Display="Dynamic"
Font-Name="verdana" Font-Size="10"
runat=server>
Customer ID must be two numeric digits
</asp:RegularExpressionValidator>
<p>
<ASP:DataList id="Products" ShowHeader=false ShowFooter=false RepeatColumns="2" RepeatDirection="horizontal" BorderWidth=0 runat="server">
<ItemTemplate>
<table>
<tr>
<td width="150" style="text-align:center; font-size:8pt; vertical-align:top; height:200">
<ASP:ImageButton borderwidth=6 bordercolor="#ffffcc" command="Select" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImagePath") %>' runat="server"/>
<p>
<%# DataBinder.Eval(Container.DataItem, "ProductName") %> <br>
<%# DataBinder.Eval(Container.DataItem, "UnitPrice", "{0:C}").ToString() %>
</td>
</tr>
</table>
</ItemTemplate>
</ASP:DataList>
</form>
</body>
</html>
using System.Reflection;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("A QuickStart Tutorial Assembly")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft QuickStart Tutorials")]
[assembly: AssemblyCopyright(" Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.1.*")]
namespace BusinessLayer
{
using System;
using System.Data;
using System.Data.SqlClient;
using DataLayer;
public class BusObj
{
private DataObj data;
public BusObj()
{
data = new DataObj("server=(local)\\NetSDK;database=grocertogo;Integrated Security=SSPI");
}
public DataView GetCategories()
{
return data.GetCategories();
}
public DataView GetProductsForCategory(String category, int customerid)
{
DataView view = data.GetProductsForCategory(category);
double discount = 0;
if ((customerid >= 25)&&(customerid < 50))
{
discount = .50;
}
else if ((customerid >= 50)&&(customerid < 75))
{
discount = 1.00;
}
else if ((customerid >= 75)&&(customerid < 100))
{
discount = 1.50;
}
for (int i=0; i<view.Count; i++)
{
view["UnitPrice"] = Double.Parse(view["UnitPrice"].ToString()) - discount;
}
return view;
}
}
}
using System.Reflection;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("A QuickStart Tutorial Assembly")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft QuickStart Tutorials")]
[assembly: AssemblyCopyright(" Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.1.*")]
namespace DataLayer
{
using System;
using System.Data;
using System.Data.SqlClient;
public class DataObj
{
private String _connStr;
public DataObj()
{
_connStr = null;
}
public DataObj(String connStr)
{
_connStr = connStr;
}
public String ConnectionString
{
get
{
return _connStr;
}
set
{
_connStr = value;
}
}
public DataView GetCategories()
{
SqlConnection myConnection = new SqlConnection(_connStr);
SqlDataAdapter myCommand = new SqlDataAdapter("select distinct CategoryName from Categories", myConnection);
DataSet ds = new DataSet();
try {
myCommand.Fill(ds, "Categories");
return ds.Tables["Categories"].DefaultView;
}
catch (Exception ex){
throw (ex);
}
finally{
myConnection.Close();
}
}
public DataView GetProductsForCategory(String category)
{
SqlConnection myConnection = new SqlConnection(_connStr);
SqlDataAdapter myCommand = new SqlDataAdapter("select ProductName, ImagePath, UnitPrice, c.CategoryId from Products p, Categories c where c.CategoryName='" + category + "' and p.CategoryId = c.CategoryId", myConnection);
DataSet ds = new DataSet();
try {
myCommand.Fill(ds, "Products");
return ds.Tables["Products"].DefaultView;
}
catch (Exception ex){
throw (ex);
}
finally{
myConnection.Close();
}
}
}
}
向文件写入文本
using System;
using System.IO;
class Test
{
public static void Main()
{
// Create an instance of StreamWriter to write text to a file.
// The using statement also closes the StreamWriter.
using (StreamWriter sw = new StreamWriter("TestFile.txt"))
{
// Add some text to the file.
sw.Write("This is the ");
sw.WriteLine("header for the file.");
sw.WriteLine("-------------------");
// Arbitrary objects can also be written to the file.
sw.Write("The date is: ");
sw.WriteLine(DateTime.Now);
}
}
}
using System;
using System.IO;
public class TextToFile
{
private const string FILE_NAME = "MyFile.txt";
public static void Main(String[] args)
{
if (File.Exists(FILE_NAME))
{
Console.WriteLine("{0} already exists.", FILE_NAME);
return;
}
StreamWriter sr = File.CreateText(FILE_NAME);
sr.WriteLine ("This is my file.");
sr.WriteLine ("I can write ints {0} or floats {1}, and so on.",
1, 4.2);
sr.Close();
}
}
从文件读取文本
using System;
using System.IO;
class Test
{
public static void Main()
{
try
{
// Create an instance of StreamReader to read from a file.
// The using statement also closes the StreamReader.
using (StreamReader sr = new StreamReader("TestFile.txt"))
{
String line;
// Read and display lines from the file until the end of
// the file is reached.
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
catch (Exception e)
{
// Let the user know what went wrong.
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}