ASP.NET框架代表着在IIS和ISAPI编程模型之上的一个重要的生产力层. 如果你熟悉ASP.NET开发的话, 你就会知道它为你的应用程序逻辑编写托管代码提供了便利, 比如说C#, VB.NET, 并且允许你在由Microsoft Visual Studio提供的面向生产力的可视化编辑器中工作. ASP.NET框架还提供了许多其他有价值的抽象, 来帮助开发人员进行状态管理, 数据绑定, 导航 以及数据缓存.
ASP.NET框架是由一个叫做aspnet_isapi.dll的ISAPI extension实现的. ASP.NET的基础配置涉及: 在IIS站点或虚拟目录的层次, 为ASP.NET的文件后缀注册映射的应用程序, ASP.NET的文件后缀包括.aspx, .ascx, 和.asmx. 当IIS看到一个指向这些文件类型的请求的时候, 他会把这个请求送给aspnet_isapi.dll, 让它来处理, 这个dll会高效地将控制转移给ASP.NET框架. 所以说, ASP.NET框架处理一个请求的方式很大程度上取决于目标文件的文件类型(扩展名).
ASP.NET框架像一个 独立的ASP.NET应用程序一样地执行指向IIS站点和虚拟目录的请求. 在每一个ASP.NET应用程序的身后, 都有一个包括一堆文件的根目录. 这种架构保证了ASP.NET应用程序的部署是一种非常简单的x-copy风格. 你可以简单地在Web服务器上创建一个新的虚拟目录, 然后拷贝你的ASP.NET应用程序的文件到根目录下. 当然, 有一点小麻烦的是在Web场环境中创建虚拟目录, 然后将同样的文件部署到场中的所有前端服务器上.
每个ASP.NET应用程序都可以在它的根目录下被独立地配置 web.config文件. web.config文件是一个基于xml的文件, 包含许多元素, 用于控制ASP.NET框架中的各种各样的feature的行为, 比如说编译啦, 页面渲染啦, 状态控制了什么的. 一个简单的web.config文件看起来像下面这样:
<? xml version = "1.0 " encoding = "utf-8 " ?>
< configuration >
< system.web >
< customErrors mode = "On " />
< httpRuntime maxRequestLength = "51200 " />
< authentication mode = "Windows " />
< identity impersonate = "true " />
< authorization >
< allow users = "* " />
</ authorization >
</ system.web >
</ configuration >
注意到ASP.NET框架在运行每一个ASP.NET应用程序的时候, 都有一定程度的隔离性. 即使是在你已经配置了多个ASP.NET应用程序运行在同一个IIS Application Pool中的时候, 也有隔离性. ASP.NET框架提供了运行在同一个IIS工作者进程中的对多个ASP.NET应用程序之间的隔离, 隔离方式是把它们分别加载到分开的.NET Framework AppDomain中.[学友注: 关于AppDomain, 可以参考文章走近.NET AppDomain ]
ASP.NET页面
===========
页面(page)是ASP.NET框架中最有价值的抽象之一. 开发者构建ASP.NET应用程序, 最典型的方式是在Visual Studio的可视化设计界面上拖拽服务器控件, 和通过标准的属性单来修改页面和控件的属性. ASP.NET框架和Visual Studio还使得向页面中添加处理逻辑相对更加简单了, 达到这个目的的方式是在页面级上的响应事件中和在控件级的事件中通过编写托管代码.
在物理层面, ASP.NET应用程序中的一个页面是一个以.aspx后缀结尾的, 存储在Web服务器上的文件, 在它被客户端请求时, 它会被编译为一个dll文件. 考虑下面的aspx页面定义, 它包含一个服务器端控件和一个简单的事件处理程序.
<% @ Page Language ="C#" %>
< script runat ="server">
protected override void OnLoad (EventArgs e ) {
lblDisplay .Text = "Hello, ASP.NET" ;
}
</ script >
< html >
< body >
< form id ="frmMain" runat ="server">
< asp : Label runat ="server" ID ="lblDisplay" />
</ form >
</ body >
在后台, ASP.NET框架做了不少工作来把.aspx文件编译成为一个dll文件. 首先, 它必须先将aspx文件解析并生成一个C#(或VB.NET)的源文件, 源文件中包含一个继承自Page类的公有类, Page类是定义在System.Web.UI命名空间下的, 存在于system.web.dll这个程序集中. 当ASP.NET页面解析器生成这个继承自Page的类时, 它建立一个控件的树形结构, 包括页面中定义的所有的服务器端控件. 页面解析器同时添加需要的代码来勾住页面中定义的任何的事件处理程序.
一旦ASP.NET页面解析器建造好了.aspx的源文件, 它就开始编译这个源文件成为一个DLL. 这个编译自动发生在这个.aspx页面第一次被请求的时候. 一旦ASP.NET运行时编译好了aspx文件的DLL, DLL的一份拷贝会被用来为所有后来的指向同样aspx文件的请求服务. 然而, ASP.NET运行时会监视DLL的时间戳, 如果它发现与DLL相关联的aspx文件被更新了, 它就会重新编译来重建那个DLL.
ASP.NET框架如此流行的原因之一, 就是因为它方便的服务器端控件. 使用与ASP.NET框架一同release的开箱即用(out-of-box)的控件创建页面非常容易.控件有很多, 比如validation控件, calendar控件, 还有支持数据绑定的控件, 比如GridView控件还有Repeater控件. 还有呢, 开发者创作自定义的控件并把它用在页面里也是相对容易的.
母版页
================
ASP.NET 2.0引入了母版页(master page), 它提供了一种非常高效的创建页模板的方式. 特别地, 一个母版页定义了可以在许多不同页面间使用的通用元素(比如说顶部横幅top banner), 还有站点导航控件. 在母版页中定义的布局可以被很多跟这个母版页链接到一起的页面使用. 在ASP.NET技术中, 与一个母版页链接到一起的页面叫做content page(内容页). 母版页与内容页的基本关系在下面可以看到图示:
举例, 假设你希望创建一个母版页, 用来定义一个HTML布局, 这个布局包括一个横幅在页的顶部. 你应该创建一个后缀名为.master的文件作为开始, 就叫default.master吧. 下一步你应该在页面的顶部添加一个@Master指令. 然后, 在下面你定义页面的HTML布局, 添加并命名placeholders, 就如下面的例子一样:
<% @ Master %>
< html >
< body >
< form id ="frmMain" runat ="server">
< table width ="100%">
< tr >
< td > <!-- Display Litware Banner -->
< h1 > Litware Inc.</ h1 >< hr />
</ td >
</ tr >
< tr >
< td > <!-- Display Main Body of Page -->
< asp : contentplaceholder id ="PlaceHolderMain" runat ="server" />
</ td >
</ tr >
</ table >
</ form >
</ body >
</ html >
当你想创建一个内容页时, 你应该创建一个.aspx文件, 然后添加一个@Page指令, 其中包括MasterPageFile属性. 一旦你决定你想要取代母版页中的哪一个命名的placeholder, 你就为这个取代操作定义一个Content元素. 接下来是一个简单的内容页链接起一个母版页的例子, 代码中取代了母版页中的名为PlaceHolderMain的placeholder.
<% @ Page Language ="C#" MasterPageFile ="~/default.master" %>
< script runat ="server">
protected override void OnLoad (EventArgs e )
{
lblDisplay .Text = "Hello World" ;
}
</ script >
< asp : Content ID ="main" Runat ="Server" ContentPlaceHolderID ="PlaceHolderMain" >
< asp : Label ID ="lblDisplay" runat ="server" />
</ asp : Content >
注意当你创建了一个内容页并指向了一个母版页了的时候, 所有你想添加的的HTML必须写在指向某个名字的placeholder的Content元素中. 如果你在Content元素之外写了HTML代码或者是服务器端控件, 那么这个页面就不会被编译. 但是, 如同你在之前的例子中看到的, 你可以在Content元素之外添加一个脚本块, 并在脚本块中添加任何你想添加的代码.
当一个母版页定义了一个命名了的placeholder, 你并不需要一定总是在你的内容页中取代它. 因为, 母版页创建带有的默认内容的placeholder. 任何指向那个母版页的内容页, 只要不包括那个命名了的placeholder, 就会得到母版页中定义的默认内容. 另一个连接到同样母版页的内容页, 并且其中包含了那个命名的placeholder, 会用自己的自定义内容取代默认内容.
最后, 注意不管是谁, 只要他创建了母版页, 那他就得决定placeholder的名字, 还要决定哪个包含什么样的默认内容. 这对于设计WSS站点的页面时很重要, 因为你创建的内容页将会指向由WSS团队创建的母版页. 在这种情况下, 你必须学会WSS团队定义了什么placeholder, 还有哪些种类的内容是可以取代的.
HTTP请求的pipeline
================
在面向高产的页面和服务器端控件架构之下, ASP.NET框架为想要在更深一个层次的开发者暴露了HTTP请求Pipeline. 他给开发者提供了可以跟ISAPI编程模型在某种程度相比的控制. 然而, 当你为HTTP请求pipeline创建组件的时候, 你却可以使用如C#或VB.NET一样的托管代码. 你还可以使用由ASP.NET提供的API, 这可比直接使用ISAPI编程模型容易多了.
下图展现了HTTP request pipeline和它的三个可以替代的组件类型: HttpHandler, HttpApplication, 和HttpModule. 当请求进来的时候, 他们被排入队列, 并被赋给一个工作者线程, 然后通过与这三个组件类型的交互来处理这个请求.
任何请求的最后的终点, 你可以在图中看到, 是HttpHandler类, 它继承了IHttpHandler接口. 作为一个开发者, 你可以创建一个自定义的HttpHandler组件, 然后通过在web.config中添加设置元素来把你的HttpHandler插入到HTTP request pipeline中.
Http Request Pipeline在HttpHandler之前放置了一个HttpApplication组件. 在一个应用程序范围的基础上, 进来的请求在到达HttpHandler之前, 总是通过HttpApplication来路由的. 因此, 给了HttpApplication一个不管最后被路由到哪个HttpHandler, 它总能预处理任何请求的能力. 这个预处理阶段是通过一系列的在HttpApplication中定义的事件来控制的, 比如说BeginRequest, AuthenticateRequest, 和AuthorizeRequest.
在你不想使用自定义的HttpApplication组件的情形下, ASP.NET使用一个标准的HttpApplication对象来初始化Http Request Pipeline. 然而, 你可以通过创建一个叫做global.asax的文件, 把它放到ASP.NET应用程序的根目录下来取代这个标准的HttpApplication组件. 举个例子, 你可以创建一个看起来像下这样的global.asax:
<% @ Application Language ="C#" %>
< script runat ="server">
protected void Application_AuthenticateRequest (object sender , EventArgs e ) {
// your code goes here for request authentication
}
protected void Application_AuthorizeRequest (object sender , EventArgs e ) {
// your code goes here for request authorization
}
</ script >
HTTP Request Pipeline中可以替换的第三个组件类型是HttpModule. HttpModule跟HttpApplication组件相似, 它被设计用来处理HttpApplication类定义的事件, 并且这些事件的处理发生在请求被传送给任何一个HttpHandler类型之前. 比方说, 你可以创建一个自定义的HttpModule组件来处理请求层次的时间, 比如说BeginRequest, AuthenticateRequest, 和AuthorizeRequest. 跟HttpHandler一样, HttpModule类是通过一个接口来定义的. 你可以创建一个实现IHttpModule接口的类, 然后通过在web.config文件中添加配置元素来把你的HttpModule插入到Http Request Pipeline中.
鉴于HttpApplication组件可以被定义为像带着.asax后缀的文本文件一样的简单, 自定义的HttpModule组件总是会被程序集DLL中的类一样被编译. 要添加一个自定义的HttpModule组件到Http Request Pipeline中, 你就得在web.config文件中添加入口.
尽管HttpApplication和HttpModule组件在所做的事情上非常相似, HttpModule包含一些值得注意的不同. 首先, 不像HttpApplication组件那样必须在一个应用程序中只添加一个HttpApplication组件, 对于HttpModule组件, 你的添加没有数量上的限制. ASP.NET的web.config文件可以添加多个不同的HttpModule组件. 第二, HttpModule组件可以被设置在机器的水平上. 事实上ASP.NET框架附带了好几个不同的HttpModule组件, 他们被自动的配置在机器机上运作, 来为ASP.NET提供诸如Windows authentication, Forms Authentication和输出缓存等功能.
对于HTTP Request Pipeline 我们要讨论的最后一个组件是HttpContext. 随着ASP.NEt初始化一个请求并发送给HTTP Request Pipeline, 它还用HttpContext类创建一个对象, 并使用重要的上下文相关的信息来初始化这个对象.
从时间的角度来说, 在ASP.NET在HTTP Request Pipeline中的任何自定义的代码有机会执行之前, HttpContext对象就已经被创建出来了, 认识到这一点很重要. 这意味着你永远可以使用HttpContext对象和它的子对象进行编程, 比如说Request, User, 和Response. 无论何时你开发一个用来在Http Request Pipeline中运行的组件, 你都可以写像下面这样的代码:
HttpContext currentContext = HttpContext .Current ;
string incomingUrl = currentContext .Request .Url ;
string currentUser = currentContext .User .Identity .Name ;
currentContext .Response .Write ("Hello world" );
<Inside Microsoft Windows SharePoint Services 3.0>