Skip to content

3. Web开发入门 ASP.NET

3.1. Introduction

上一章介绍了与所用编程语言无关的Web开发原理。目前,有三项技术主导着Web开发市场:

  • J2EE,这是一个Java开发平台。该平台结合Struts技术,部署在各类应用服务器上,主要应用于大型项目。 由于采用Java语言,J2EE应用程序可在主要操作系统(Windows、Unix、Linux、Mac OS等)上运行
  • PHP 作为一种解释型语言,同样独立于操作系统。 与Java不同,它并非面向对象的语言。不过,PHP5版本预计将引入面向对象特性。由于易于使用,PHP被广泛应用于中小型项目。
  • ASP.NET 是一项仅在配备 .NET 平台(XP、2000、2003 等)的 Windows 机器上运行的技术。 所使用的开发语言可以是任何与 .NET、c.a.d 兼容的语言,共有十余种,包括微软的语言(C#、VB.NET、J#)、博兰德的 Delphi、Perl、Python 等。

上一章针对这三种技术分别给出了简短的示例。本文将重点探讨使用VB.NET语言进行ASP.NET Web开发。 我们假设读者已了解该语言。这一点非常重要。本文仅关注该语言在Web开发中的应用。让我们通过探讨MVC Web开发方法论来进一步阐明这一点。

遵循 MVC 模型的 Web 应用程序将采用以下架构:

Image

这种被称为三层架构的架构旨在遵循 MVC 模型(Model View Controller):

  • 用户界面是 V(视图)
  • 应用逻辑是 C(控制器)
  • 数据源是 M(模型)

用户界面通常是网页浏览器,但也可能是通过网络向Web服务发送请求并格式化其返回结果的独立应用程序。应用逻辑由处理用户请求的脚本构成。 数据源通常是数据库,但也可能是简单的平面文件、目录 LDAP、远程 Web 服务等。开发人员应确保这三个实体之间保持高度独立,以便其中一个发生变化时,另外两个无需或只需少量调整。

  • 应用程序的业务逻辑应放置在与控制请求-响应对话的类相分离的类中。因此,上文中的 [Logique applicative] 代码块可由以下部分组成:

Image

在 [Logique Applicative] 模块中,我们可以区分出

  • 作为应用程序入口的控制器类,
  • [Classes métier] 模块,其中包含应用程序逻辑所需的类。这些类与客户端无关。
  • [Classes d'accès aux données]模块,其中包含获取Servlet所需数据的类,通常是持久化数据(BD、文件、WEB服务等)
  • ASP 页面模块,构成应用程序的视图。

在简单情况下,应用逻辑通常可简化为两个类:

  • 负责客户端与服务器端交互的控制器类:处理请求、生成各种响应
  • 业务类,从控制器接收待处理的数据,并向其返回结果。该业务类随后自行管理对持久化数据的访问。

Web 开发的独特性在于控制器类和展示页面的编写。业务类和数据访问类是经典的 .NET 类,既可用于 Web 应用程序,也可用于 Windows 应用程序,甚至控制台应用程序。编写这些类需要具备扎实的面向对象编程知识。 本文档中,这些类将使用VB.NET语言编写,因此我们假设读者已掌握该语言。 基于此,无需在数据访问代码上过多赘述。几乎所有关于 ASP.NET 的书籍中,都有一章专门介绍 ADO.NET。 上图所示,数据访问是通过完全传统的 .NET 类实现的,这些类并不知道它们是在 Web 环境中被使用的。作为 Web 应用程序“团队负责人”的控制器,无需关心 ADO.NET。 它只需知道应向哪个类请求所需数据以及如何进行请求。仅此而已。将 ADO.NET 代码放入控制器中不符合上述 MVC 概念,因此我们不会这样做。

3.2. 工具

本文档面向学生,因此我们将使用可从互联网免费下载的工具:

  • .NET 平台(编译器、文档)
  • WebMatrix开发环境(自带Cassini Web服务器)
  • 各类 SGBD(MSDE、MySQL)

建议读者查阅附录“Web 工具”,其中说明了如何获取和安装这些工具。大多数情况下,我们只需要以下三种工具:

  • 一个文本编辑器,用于编写 Web 应用程序。
  • 一个开发工具 VB.NET,用于编写大量 VB 代码。此类工具通常提供代码输入辅助(自动代码补全),并在输入代码时或编译时提示语法错误。
  • 一个用于测试所编写Web应用程序的Web服务器。本文将采用Cassini。若读者拥有IIS服务器,可将Cassini替换为IIS。两者均兼容.NET。 不过,Cassini 仅限于响应本地请求(localhost),而 IIS 则可响应外部机器的请求。

Microsoft 的 Visual VB.NET 是开发 Studio.NET 的绝佳商业环境。 这款功能非常丰富的 IDE 允许管理各种文档(VB.NET 代码、HTML 文档、XML、样式表等)。 在编写代码时,它提供了宝贵的代码自动“补全”功能。话虽如此,这款显著提升开发者生产力的工具却因优点而产生了一定缺陷:它将开发者禁锢在一种虽然高效但未必总是合适的标准化开发模式中。

Cassini 服务器可以在 [WebMatrix] 之外使用,这也是我们经常会做的事情。服务器的可执行文件位于 <WebMatrix>\<version>\WebServer.exe,其中 <WebMatrix> 是 [WebMatrix] 的安装目录,<version> 是其版本号:

Image

打开一个 DOS 窗口,并进入 Cassini 服务器的文件夹:

E:\Program Files\Microsoft ASP.NET Web Matrix\v0.6.812>dir
...
29/05/2003  11:00               53 248 WebServer.exe
...

不带任何参数运行 [WebServer.exe]:

E:\Program Files\Microsoft ASP.NET Web Matrix\v0.6.812>webserver

Image

上方的面板显示,[WebServer/Cassini] 应用程序支持三个参数:

  • /port:Web 服务的端口号。可以是任意值。默认值为 80
  • /path:磁盘上某个文件夹的物理路径
  • /vpath:与上述物理文件夹关联的虚拟文件夹。

我们将示例文件放置在根目录 P 的文件树中,其中包含 chap1、chap2 等文件夹,分别对应本文档的不同章节。我们将虚拟路径 V 与该物理文件夹 P 关联。因此,我们将使用以下 DOS 命令启动 Cassini:

dos> WebServer /port:80 /path:P vpath:V

例如,若希望服务器的物理根目录为 [D:\data\devel\aspnet\poly],其虚拟根目录为 [aspnet],则启动 Web 服务器的 DOS 命令为:

dos> WebServer /port:80 /path:D:\data\devel\aspnet\poly vpath:/aspnet

可以将此命令设置为快捷方式。Cassini 启动后,会在任务栏中安装一个图标。双击该图标,即可访问服务器的“停止/启动”面板:

Image

该面板显示了启动时使用的三个参数。它提供两个启动/停止按钮,以及一个指向其Web树根目录的测试链接。我们点击该链接。浏览器随即打开,并请求了URL [http://localhost/aspnet]。 我们获取了上述[Physical Path]字段中指定的文件夹内容:

Image

在此示例中,请求的 URL 对应的是一个文件夹而非网页文档,因此服务器显示的是该文件夹的内容,而非特定的网页文档。 如果该文件夹中存在名为 [default.aspx] 的文件,则将显示该文件。例如,我们创建以下文件并将其放置在 Cassini Web 树结构的根目录下(此处为 d:\data\devel\aspnet\poly):

<html>
    <head>
        <title>Page d'entrée</title>
    </head>
    <body>
    Page d'index...
    </body>
</html>
dos>dir d:\data\devel\aspnet\poly\default.aspx
23/03/2004  18:21                  107 default.aspx

现在我们用浏览器请求 URL [http://localhost/aspnet]:

Image

可以看到,实际上显示的是 URL [http://localhost/aspnet/default.aspx]。 在本文后续部分,我们将说明如何通过 Cassini(path,vpath) 这种表示法配置 Cassini,其中 [path] 是服务器 Web 树结构的根目录名称,[vpath] 是与其关联的虚拟路径。 需注意,在 Cassini(path,vpath) 服务器中,URL [http://localhost/vpath/XX] 对应物理路径 [path\XX]。我们将所有文档放置于一个名为 <webroot> 的物理根目录下。 因此,我们可以这样描述文件:<webroot>\chap2\here1.aspx。对于每位用户而言,这个根目录<webroot>将位于其个人计算机上的一个文件夹中。此处的屏幕截图显示,该文件夹通常为[d:\data\devel\aspnet\poly]。但这并非绝对,因为测试是在不同的计算机上进行的。

3.3. 初步示例

我们将展示使用 VB.NET 创建的动态网页的简单示例。建议读者亲自测试这些示例,以确认其开发环境已正确安装。我们将发现构建 ASP.NET 页面有多种方法。我们将从中选择一种作为后续开发的基础。

3.3.1. 基础示例 - 方案 1

所需工具:文本编辑器、Cassini Web服务器

我们延续上一章的示例。构建如下 [heure1.aspx] 文件:

<html>
  <head>
      <title>Demo asp.net </title>
  </head>
  <body>
      Il est <% =Date.Now.ToString("T") %>
  </body>
</html>

该代码是 HTML 代码,其中包含一个特殊的 <% ... %> 标签。在这个标签内,可以放入 VB.NET 代码。此处的代码

Date.Now.ToString("T")

会生成一个表示当前时间的字符串 C。随后,<% ... %> 标签会被该字符串 C 替换。因此,如果 C 是字符串 18:11:01,则包含代码 VB.NET 的行 HTML 将变为:

      Il est 18:11:01

将上述代码放入文件 [<webroot>\chap2\heure1.aspx] 中。启动 Cassini(<webroot>,/aspnet),并通过浏览器请求 URL [http://localhost/aspnet/chap2/heure1.aspx]:

Image

一旦获得此结果,即表明开发环境已正确安装。页面 [heure1.aspx] 已被编译,因为它包含代码 VB.NET。其编译生成了一个 dll 文件,该文件被存储在系统文件夹中,随后由 Cassini 服务器执行。

3.3.2. 基础示例 - 变体 2

所需工具:文本编辑器、Cassini Web 服务器

文档 [heure1.aspx] 混合了 HTML 和 VB.NET 代码。 在如此简单的示例中,这不会造成问题。如果需要包含更多的 VB.NET 代码,则建议将 HTML 代码与 VB 代码进一步分离。 这可以通过将代码 VB 包裹在 <script> 标签中来实现:

<script runat="server">
     ' 通过代码计算要显示的数据HTML
...
</script>
<html>
....
' 显示由脚本部分计算出的值
</html>

示例 [heure2.aspx] 展示了此方法:

<script runat="server">
    Dim maintenant as String=Date.Now.ToString("T")
</script>
<html>
    <head>
        <title>Demo asp.net </title>
    </head>
    <body>
        Il est
        <% =maintenant %>
    </body>
</html>

我们将文档 [heure2.aspx] 放置在 Cassini 网页服务器(<webroot>,/aspnet)的 [<webroot>\chap2\heure2.aspx] 目录树中,并通过浏览器请求该文档:

Image

3.3.3. 基础示例 - 变体 3

所需工具:文本编辑器、Cassini Web 服务器

我们将代码VB与代码HTML进一步分离,将其分别存入两个独立的文件中。 代码 HTML 将位于文件 [heure3.aspx] 中,代码 VB 将位于文件 [heure3.aspx.vb] 中。文件 [heure3.aspx] 的内容如下:


<%@ Page Language="vb" src="heure3.aspx.vb" Inherits="heure3" %>
<html>
    <head>
        <title>Demo asp.net</title>
    </head>
    <body>
        Il est
        <% =maintenant %>
    </body>
</html>

有两个根本区别:

  • [Page]指令带有尚未知的属性
  • 在代码 HTML 中使用了变量 [maintenant],但该变量在任何地方都没有被初始化

指令 [Page] 在此用于指示将初始化页面的代码 VB 位于另一个文件中。正是属性 [src] 指明了该代码所在位置。 我们将发现,代码 VB 属于一个名为 [heure3] 的类。对开发人员而言,这一过程是透明的:一个 .aspx 文件会被转换为继承自名为 [Page] 的基类的派生类。 在此,我们的文档 HTML 必须继承自定义并计算其需显示数据的类。这里是定义在文件 [heure3.aspx.vb] 中的类 [heure3]。 因此,还需指定文档 VB、[heure3.aspx.vb] 与文档 HTML、[heure3.aspx] 之间的父子关系。 [inherits] 属性用于指定此关联。它必须指明由 [src] 属性指向的文件中定义的类名。

现在让我们研究该页面中的代码 VB:

Public Class heure3
    Inherits System.Web.UI.Page

     ' 待显示的网页数据
    Protected maintenant As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         '计算网页数据
        maintenant = Date.Now.ToString("T")
    End Sub
End Class

请注意以下几点:

  • 代码 VB 定义了一个从类 [System.Web.UI.Page] 派生的类 [heure3]。情况始终如此,因为网页必须始终从 [System.Web.UI.Page] 派生。
  • 该类声明了一个受保护(protected)的属性 [maintenant]。 众所周知,受保护的属性可在派生类中直接访问。正是这一点使得文档 HTML 和 [heure3.aspx] 能够在其代码中访问数据 [maintenant] 的值。
  • 属性 [maintenant] 的初始化是在过程 [Page_Load] 中进行的。我们稍后将看到,Web 服务器会向类型为 [Page] 的对象通知若干事件。 当对象 [Page] 及其组件创建完成时,会触发事件 [Load]。该事件的处理程序由指令 [Handles MyBase.Load] 指定
    Private Sub XX(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  • 事件处理程序的名称 [XX] 可以是任意名称。但其签名必须与上述所示一致。我们暂不对此进行详细说明。
  • 通常使用事件处理程序 [Page.Load] 来计算网页需要显示的动态数据值。

文档 [heure3.spx] 和 [heure3.aspx.vb] 被放置在 [<webroot>\chap2] 中。 然后,通过浏览器向 Web 服务器(<webroot>,/aspnet)请求 URL 和 [http://localhost/aspnet/chap2/heure3.aspx]:

Image

3.3.4. 基础示例 - 变体 4

所需工具:文本编辑器、Cassini Web 服务器

我们沿用之前的示例,但将所有代码重新整合到一个文件 [heure4.aspx] 中:

<script runat="server">
     ' 待显示的网页数据
    Private maintenant As String

     ' evt page_load
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         '计算网页数据
        maintenant = Date.Now.ToString("T")
    End Sub
</script>

<html>
    <head>
        <title>Demo asp.net</title>
    </head>
    <body>
        Il est
        <% =maintenant %>
    </body>
</html>

我们看到了示例 2 中的序列:

<script runat="server">
.... code VB
</script>

<html>
... code HTML
</html>

这次,代码 VB 被结构化为过程。其中包含了前一个示例中的过程 [Page_Load]。 此处旨在说明,单个 .aspx 页面(未与单独文件中的 VB 代码相关联)会被隐式转换为 [Page] 的派生类。 因此,我们可以使用该类的属性、方法和事件。本文中正是如此,我们使用了该类的 [Load] 事件。

测试方法与之前相同:

Image

3.3.5. 基础示例 - 变体 5

所需工具:文本编辑器、Cassini Web 服务器

与示例 3 一样,我们将代码 VB 和代码 HTML 分别存入两个独立的文件中。代码 VB 被放入 [heure5.aspx.vb] 中:

Public Class heure5
    Inherits System.Web.UI.Page

     ' 待显示的网页数据
    Protected maintenant As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         '计算网页数据
        maintenant = Date.Now.ToString("T")
    End Sub
End Class

代码 HTML 被放置在 [heure5.aspx] 中:


<%@ Page Inherits="heure5" %>
<html>
    <head>
        <title>Demo asp.net</title>
    </head>
    <body>
        Il est
        <% =maintenant %>
    </body>
</html>

这次,指令 [Page] 不再指示代码 HTML 与代码 VB 之间的关联。 Web 服务器无法再找到代码 VB 进行编译(缺少 src 属性)。因此,我们需要自行进行编译。在 DOS 窗口中,我们编译类 VB [heure5.aspx.vb]:

dos>dir
23/03/2004  18:34                  133 heure1.aspx
24/03/2004  09:47                  232 heure2.aspx
24/03/2004  10:16                  183 heure3.aspx
24/03/2004  10:16                  332 heure3.aspx.vb
24/03/2004  14:31                  440 heure4.aspx
24/03/2004  14:45                  332 heure5.aspx.vb
24/03/2004  14:56                  148 heure5.aspx
dos>vbc /r:system.dll /r:system.web.dll /t:library /out:heure5.dll heure5.aspx.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir heure5.dll
24/03/2004  14:51                3 072 heure5.dll

上文中,编译器的可执行文件 [vbc.exe] 位于 DOS 机器的 PATH 目录下。 如果不是这样,就需要提供 [vbc.exe] 的完整路径,该文件位于安装了 SDK.NET 的文件夹树中。 从 [Page] 派生的类需要 DLL 和 [system.dll, system.web.dll] 中存在的资源,因此通过编译器的 /r 选项引用了这些文件。 /t:library 选项用于指定生成 DLL 文件。/out 选项指定生成的文件名,此处为 [heure5.dll]。 该文件包含网页文档 [heure5.aspx] 所需要的 [heure5] 类。不过,Web 服务器会在特定位置查找其所需的 DLL 文件。 其中一个位置是位于其目录树根目录下的 [bin] 文件夹。该根目录即我们所说的 <webroot>。对于 IIS 服务器,通常为 <驱动器>:\inetpub\wwwroot,其中 <驱动器> 是安装了 IIS 的驱动器(C、D、 ...)。对于 Cassini 服务器,该根目录对应于您启动服务器时使用的 /path 参数。请注意,您可以通过双击任务栏中的服务器图标来获取该值:

Image

<webroot> 对应上文的 [Physical Path] 属性。因此,我们创建一个 <webroot>\bin 文件夹,并将 [heure5.dll] 放置其中:

Image

准备就绪。向 Cassini 服务器(<webroot>,/aspnet)请求 URL [http://localhost/aspnet/chap2/heure5.aspx]:

Image

3.3.6. 基础示例 - 变体 6

所需工具:文本编辑器、Cassini Web 服务器

到目前为止,我们已经说明了一个动态 Web 应用程序包含两个部分:

  1. 用于计算页面动态部分的代码 VB
  2. HTML代码(有时包含VB代码),用于在页面中显示这些值。这一部分代表发送给Web客户端的响应。

组件 1 被称为页面的控制器组件,而部分 2 则称为呈现组件呈现部分应尽可能少地包含 VB 代码,甚至完全不包含 VB 代码。 我们将看到这是可行的。在此,我们展示了一个仅包含控制器而没有呈现组件的示例。控制器会自行生成发给客户端的响应,无需呈现组件的协助。

呈现代码如下:

 <%@ Page src="heure6.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="heure6" %>

可以看到其中已不再包含任何 HTML 代码。响应直接在控制器中生成:

Public Class heure6
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' 生成响应
        Dim HTML As String
        HTML = "<html><head><title>heure6</title></head><body>Il est "
        HTML += Date.Now.ToString("T")
        HTML += "</body></html>"
         ' 发送响应
        Response.Write(HTML)
    End Sub
End Class

在此,控制器生成响应的全部内容,而非仅生成其动态部分。此外,它还会发送该响应。它通过 [Page] 类的 [Response] 属性(类型为 [HttpResponse])来完成此操作。 这是一个表示服务器向客户端返回响应的对象。[HttpResponse]类提供了一个[Write]方法,用于向将发送给客户端的HTML数据流中写入数据。 在此,我们将待发送的完整数据流 HTML 放入变量 [HTML] 中,并通过 [Response.Write(HTML)] 将其发送给客户端。

我们向 Cassini 服务器 (<webroot>,/aspnet) 请求 URL [http://localhost/aspnet/chap2/heure6.aspx]:

Image

3.3.7. 结论

接下来,我们将采用方法 3,该方法将动态网页文档中的代码 VB 和代码 HTML 分别放置在两个独立的文件中。该方法的优势在于将网页拆分为两个组件:

  1. 一个仅由代码 VB 组成的控制器组件,用于计算页面的动态部分
  2. 一个呈现组件,即发送给客户端的响应。它由 HTML 代码组成,有时还包含用于显示动态值的 VB 代码。 我们将始终致力于在呈现部分使用最少的 VB 代码,理想情况是完全不使用。

正如方法5所示,控制器可以独立于Web应用程序进行编译。这样做的好处是能够专注于代码本身,并且每次编译后都能获得完整的错误列表。 控制器编译完成后,即可测试Web应用程序。若未预先编译,则由Web服务器进行编译,此时错误将逐一报告。这可能会被认为比较繁琐。

对于接下来的示例,只需以下工具即可:

  • 一个文本编辑器,用于构建应用程序中简单的 HTML 和 VB 文档
  • 一个开发工具 IDE(如 .NET),用于构建类 VB.NET,以便利用此类工具在编写代码时提供的辅助功能。 此类工具的一个示例是 CSharpDevelop(http://www.icsharpcode.net)。附录 [Les outils du développement web] 中展示了使用示例。
  • 使用工具 WebMatrix 构建应用程序的展示页面(参见附件 [Les outils du développement web])。
  • Cassini 服务器

所有这些工具均免费提供。