xieex's blog


从事软件开发的交流平台

我的人生信念:态度决定一切
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ASP.NET架构分析

Posted on 2007-10-29 13:12  xieex  阅读(5822)  评论(17编辑  收藏  举报

最近听了微软讲师邵志东的讲座“深入解析ASP.NET架构”的视频讲座,我觉得对ASP.NET架构有一定的认识,现在对讲座做一点总结,以及发表我对ASP.NET架构相关知识的自身理解,如有不妥之处,希望各位同仁不吝指出!
一、ASP.NET工作原理:
首先通过一个图来了解一下ASP.NET工作原理,
  
 
                                     图一 ASP.NET工作原理
通过该图的顺序我们可以看到ASP.NET是如何工作的,可以看到客户端和服务器端是如何交互的。看到客户端是如何请求服务器,服务器接受到客户端请求后,是如何处理请求的,并将处理结果返回给客户端的。
需要指出的是图中的aspnet_isapi.dll是用来处理.aspx文件的,其实IIS服务器是只能识别.html文件的,当浏览器对服务器进行http请求时,IIS服务器识别到.aspx文件时,IIS服务器将其交给aspnet_isapi.dll来处理,aspnet_isapi.dll将.aspx文件发送给.NET FrameWork中CLR(公共语言运行时)进行编译,编译后将HTML流返回给浏览器。
这在IIS服务器上是可以来配置的,配置中默认.aspx文件是由aspnet_isapi.dll来处理的,其实也可以去添加某种特定类型的文件由特定的可执行文件来处理,如:可以添加一个.apx文件来由aspnet_isapi.dll来处理。
具体配置如下图:

                                图二 IIS配置

趁此说一下IIS服务的一些知识:
IIS本事是不支持动态页面的,也就是说它仅仅支持静态html页面的内容,对于如.aspx,.php,.cs等,IIS并不会处理这些标记,它就会把它当作文本,丝毫不做处理发送到客户端。为了解决这个问题。IIS有一种机制,叫做ISAPI的筛选器,它是一个标准组件(COM组件。)  ASP.NET服务在注册到IIS的时候,会把每个扩展可以处理的文件扩展名注册到IIS里面(如:*.aspx,*.php等),扩展启动后,就根据定义好的方式来处理IIS所不能处理的文件,然后把控制权跳转到专门处理代码的进程中(如*.aspx由aspnet_isapi.dll来处理)。让这个进程开始处理代码,生成标准的HTML代码,生成后把这些代码加入到原有的HTML代码中,最后把完整的HTML返回到IIS,IIS再把内容发送给客户端。


通过下图可以看到更具体的ASP.NET页面的执行过程:


                                 图三 ASP.NET页面的执行过程


二、ASP.NET请求的处理过程:

上面我们已经了解了ASP.NET的工作原理,知道了客户端是如何和服务器端进行交互的。现在我们可以来看看服务器端到底是怎样来处理客户端的请求。
Web服务器提供了很多处理请求的功能,但是为了满足开发者的需求,开发者需要扩充或扩展Web服务器的功能,就是向Web服务器插入某些组件来增强Web服务器的功能,微软公司提供了ISAPI(Internet服务器API)。就比如说aspnet_isapi.dll可以来处理.aspx等文件,我们也可以开发组件(如xxx.dll)来处理某种类型的文件或者去做一些其他增强服务器能力的事情。

简单的说如何去增强Web服务器的能力呢,我们就可以去开发一些组件,开发组件需要一种技术叫ISAPI。
(ISAPI是一种重要的技术,它允许我们增强与ISAPI兼容的Web服务器的能力,其中IIS就是一种与ISAPI兼容的Web服务器。)

增强Web服务器的能力的组件的类型主要有两种:
1、ISAPI过滤器:客户端每次向服务器发出请求的时候,请求要经过过滤器。客户端不需要在请求中指定过滤器,只需要简单地把请求发送给Web服务器,接着Web服务器把请求传递给相关的过滤器。接下来过滤器可能修改请求,执行某些登录操作等。也就是说,客户端每次请求服务器时,必须要经过“过滤”,如需要检查用户名和密码,符合条件时才让通过请求,“检查用户名和密码”的过程就是ISAPI过滤器所要做的操作。过滤器可以有很多(web.config中指定,后面将讲到),并且请求都必须要经过所有的过滤器进行“过滤”。

2、ISAPI扩展:ISAPI扩展是使用Win32动态链接库(xxx.dll)来实现的,也可以把ISAPI扩展看做一个普通的应用程序。它的处理目标是http请求。也就是说Web服务器可以来处理http请求,但是你可以去扩展服务器,去自定义对http的请求,达到更好的效果。

基于上面的内容,我们可以将ASP.NET请求的处理过程总结如下:

ASP.NET请求处理过程是基于“管道模型”的,客户端向服务器发送http请求时,在模型中ASP.NET把http请求传递给管道中的所有模块(ISAPI过滤器),每个模块都接受http请求并且有完全控制权限,模块可以用任何自认为的方式(通过开发人员来开发,如校验用户名密码等)来处理请求。一旦请求经过了所有Http模块(如用户名、密码符合),就最终被Http处理程序(ISAPI扩展)处理,http处理程序对请求进行一些处理,并且结果将再次经过管道中的http模块。

                图四  管道模型

我自己举例来说吧,我建立了一个.apx文件,要求客户端访问该页面,并在客户端返回"我是apx文件,我是被“ISAPI过滤器”过滤过的,并被"ISAPI扩展"处理过的"。在访问页面时必须需要用户名和密码。
此时我就需要开发组件来扩充Web服务器,因为Web服务器没有校验指定的用户名和密码的能力也没有处理.apx文件的能力。我就需要开发“ISAPI过滤器”组件(用来校验用户名和密码)和ISAPI扩展组件(处理.apx文件)。

那我们怎么开发“ISAPI过滤器”组件和“ISAPI扩展”组件呢?

1、开发“ISAPI过滤器”
   HttpModule(Http模块)实现了ISAPI 过滤器的功能,是通过对IhttpModule接口的继承来处理。HttpModule是实现了System.Web.IhttpModule接口的.NET组件,这些组件通过在某些事件中注册自身,把自己插入到ASP.NET请求处理管道中(如上图中的Module1,Module2)。当这些事件发生的时候,ASP.NET调用对请求有兴趣的HTTP模块,这样该模块就能处理请求了。

   HttpModule的实现:
   1、编写一个类,实现IhttpModule接口,要添加引用System.Web
   2、实现Init方法,并且要注册需要的方法。如:AuthenticateRequest等
   3、实现注册的方法
   4、实现Dispose方法,如果需要手工为类做一些清除工作,可以添加Dispose方法的实现,但这不是必需的,通常可以
     不为Dispose方法添加任何代码
   5、在Web.config文件中,注册您编写的类。
  
   下面通过一个实例来实现HttpModule(具体见附件代码)

1、我编写了SecurityModules类实现IHttpModule接口
2、实现了Init方法,并且向Application对象注册事件处理程序(myAuthenticateRequest),当http请求执行到AuthenticateRequest(建立用户标   识时)时,就执行注册的方法myAuthenticateRequest
3、实现注册的方法myAuthenticateRequest,主要来校验userid和password
4、实现Dispose方法

 1using System;
 2using
 System.Collections.Generic;
 3using
 System.Text;
 4using
 System.Web;
 5using
 System.Security.Principal;
 6

 7namespace
 MyModule1
 8
{
 9    public class
 SecurityModules : IHttpModule
10    
{
11        public void
 Init(HttpApplication objApplication)
12        
{
13            // 向Application 对象的事件AuthenticateRequest注册处理程序

14            objApplication.AuthenticateRequest += new EventHandler(this.myAuthenticateRequest);
15        }

16
17        private void myAuthenticateRequest(object
 objSender, EventArgs objEventArgs)
18        
{
19            // 鉴别用户的凭证,并找出用户角色

20            HttpApplication objApp = (HttpApplication)objSender;
21            HttpContext objContext =
 (HttpContext)objApp.Context;
22            if ((objContext.Request["userid"== null&& (objContext.Request["password"== null
))
23            
{
24                objContext.Response.Write("用户名和密码不允许为空!"
);
25
                objContext.Response.End();
26            }

27
28            string sUserId = ""
;
29            sUserId = objContext.Request["userid"
].ToString();
30            string sPassword = ""
;
31            sPassword = objContext.Request["password"
].ToString();
32            string
[] strRoles;
33            strRoles =
 AuthenticateAndGetRoles(sUserId,sPassword);
34            if ((strRoles == null|| (strRoles.GetLength(0== 0
))
35            
{
36                objContext.Response.Write("用户名和密码错误!"
);
37                objApp.CompleteRequest();//终止一个http请求

38            }

39            //GenericIdentity类表示具有指定名称和身份验证类型的用户
40            GenericIdentity objIdentity = new GenericIdentity(sUserId, "CustomAuthentication");
41            objContext.User = new
 GenericPrincipal(objIdentity,strRoles);
42        }

43
44        
/// <summary>
45        /// 根据http请求的userid和password来验证和获取角色。
46        /// </summary>

47        private string[] AuthenticateAndGetRoles(string sUserId, string sPassword)
48        
{
49            string[] strRoles = null
;
50            if ((sUserId.Equals("xieex")) && (sPassword.Equals("111"
)))
51            
{
52                strRoles = new String[1
];
53                strRoles[0= "Administrator"
;
54            }

55            else if ((sUserId.Equals("zhangsan")) && (sPassword.Equals("222")))
56            
{
57                strRoles = new String[1
];
58                strRoles[0= "User"
;
59            }

60            return strRoles;
61        }

62
63        public void
 Dispose()
64        
{
65
 
66        }

67    }

68}

 

5、新建一个项目,在Web.config文件中注册该类,当访问该项目的页面时,都要校验userid和password才能访问,并且在该项目的引用中添加MyModule1.dll

在Web.config文件中添加:

<!--HttpModule通过对HttpApplication对象的一系列事件的处理来对HTTP处理管道施加影响,这些事件在HttpModule的Init方法中进行注册包括BeginRequest等。-->
      
<httpModules>
        
<add name="myModule1" type="MyModule1.SecurityModules,MyModule1"/>
      
</httpModules>
  
<!--add将HttpModule类添加到应用程序-->
  
<!--add格式:<add name="modulename(随便起)" type="命名空间。类名(该类继承IHttpModule接口), (assemblyname)dll文件名"/>  -->

  
<!--remove从应用程序移除HttpModule类-->
  
<!--remove格式<remove name="modulename">-->

  
<!--clear从应用程序移除所有HttpModule映射-->
  
<!--clear/-->

此时访问该项目中的页面时,就要校验用户名和密码了。

此时输入在URL上增加“?userid=xieex@password=111”时就可以进入页面了。

这样我们就开发完了ISAPI过滤器,注意过滤器可以有多个,只要在web.config中的httpModules节点中Add即可,它们都会起作用的不会覆盖的。

HttpModule通过对HttpAplication对象的一系列事件的处理来对HTTP处理管道施加影响,这些事件在HttpModule的Init方法中进行注册。具体的事件发生顺序如下:


                         图五  事件发生顺序

关于事件发生顺序可以参考例子:MultiHttpModule.csproj,这个例子也反应了过滤器可以有多个,只要在web.config中的httpModules节点中Add即可,它们都会起作用的不会覆盖的。

2、开发“ISAPI扩展”组件

还是拿下面的例子来说:
“我自己举例来说吧,我建立了一个.apx文件,要求客户端访问该页面,并在客户端返回"我是apx文件,我是被“ISAPI过滤器”过滤过的,并被"ISAPI扩展"处理过的"。在访问页面时必须需要用户名和密码。
此时我就需要开发组件来扩充Web服务器,因为Web服务器没有校验指定的用户名和密码的能力也没有处理.apx文件的能力。我就需要开发“ISAPI过滤器”组件(用来校验用户名和密码)和ISAPI扩展组件(处理.apx文件)。”
上面的ISAPI过滤器可以解决“校验用户名和密码”问题,下面我们利用ISAPI扩展来处理.apx文件。

HttpHandler实现了ISAPI扩展的功能,它处理请求(Request)的信息和发送响应(Response)。HttpHandler功能的实现要继承IHttpHandler接口。
HTTP处理程序是实现了System.Web.IHttpHandler接口的.NET组件,任何实现了IHttpHandler接口的类都可以用于处理输入的HTTP请求。

HttpHandler的实现:
1、编写一个实现IHttpHandler接口的类;
2、在Web.config或machine.config文件中注册这个处理程序;
3、在Internet服务管理器把文件扩展(你想要的文件扩展名)映射到ASP.NET ISAPI扩展DLL(aspnet_isapi.dll)上。(该步也可不用做)

同样通过一个实例来实现HttpHandler(具体见附件代码)
1、编写了HandlerAPX类,该类继承了IHttpHandler接口,该类实现了ProcessRequest方法和IsReusable属性。注意要引用System.Web

 1using System;
 2using
 System.Collections.Generic;
 3using
 System.Text;
 4using
 System.Web;
 5

 6namespace
 MyHandler
 7
{
 8    public class
 HandlerAPX : IHttpHandler
 9    
{
10        Implementation of IHttpHandler

35    }

36}

 

2、新建一个项目,在Web.config文件中注册该类,并且在该项目的引用中添加MyHandler.dll

在Web.config文件中添加:

      <httpHandlers>
        
<add verb="*" path="*.apx" 
               type
="MyHandler.HandlerAPX,MyHandler" />

      
</httpHandlers>
      
<!--add格式:<add verb="*" path="要处理文件"  type="命名空间.类名(该类继承IHttpHandler接口), (assemblyname)dll文件名"/>          -->

 

说明当IIS识别到.apx文件时,就调用HandlerAPX类来处理。

http请求通过httpHandler来处理,本来系统就对其有默认处理,那和自定义的处理是如何协调的呢?看下图就可以明白,其实两者是选择其一的:


                                     图六  HttpHandler之间的关系

在“图五 事件发生顺序”中,我们看到HttpHandler建立后,此后Session就可以用了,下面来看看在HttpHandler中如何访问Session.
1、不能直接通过HttpContext访问
2、必须实现IRequiresSessionState接口
3、IRequriresSessionState接口指定目标HTTP处理程序接口具有对会话状态值的读写访问权限,这是一个标记接口,没
   有任何方法。

同样通过一个实例来实现在HttpHandler访问Session(具体见附件代码)
1、编写类HandlerSession,该类实现了接口IHttpHandler,IRequiresSessionState

 

using System;
using
 System.Collections.Generic;
using
 System.Text;
using
 System.Web;
using
 System.Web.SessionState;

namespace
 MyHandlerSession
{
    
public class
 HandlerSession : IHttpHandler,IRequiresSessionState
    
{
        
Implementation of IHttpHandler

    }

}

 

2、新建一个项目,在Web.config文件中注册该类,并且在该项目的引用中添加MyHandlerSession.dll

在Web.config文件中添加:

      <httpHandlers>
        
<add verb="*" path="*.apx"
               type
="MyHandler.HandlerAPX,MyHandler" />

        
<add verb="*" path="HttpModule.aspx"
       type
="MyHandlerSession.HandlerSession,MyHandlerSession" />


      
</httpHandlers>

 

说明当IIS识别到HttpModule.aspx文件时,就调用HandlerSession类来处理。但是如果是.apx文件时,就会调用HandlerAPX类来处理。

也就是说注册多个HttpHandler会覆盖的,这与HttpModule是不一样的。如果是对同一类文件注册了不同的HttpHandler,会执行最后一个。

即:如果Web.config配置为

      <httpHandlers>
        
<add verb="*" path="*.apx"
               type
="MyHandler.HandlerAPX,MyHandler" />

        
<add verb="*" path="*.apx"
       type
="MyHandlerSession.HandlerSession,MyHandlerSession" />


      
</httpHandlers>

说明当IIS识别到*.apx文件时,就调用HandlerSession类来处理,而不是调用HandlerAPX类来处理。

三、ASP.NET事件模型机制
1、ASP.NET之所以对于以前的ASP是一个革命性的巨变,在很大程度上是由于ASP.NET技术是一种基于事件驱动的全新技术。
2、在ASP.NET中时间的触发和处理是在客户端和服务端进行的。
3、ASP.NET中,如果频繁和服务器进行事件信息传递,会大大降低服务器的处理效率和性能,因而有些事件如OnMouseOver没有提供;
4、但提供了Change事件,为了提高效率它们被缓存在客户端,等到再一次事件信息被发送到服务器端时一同发送回去。
如文本框的change事件,下拉框的change事件,
如两个控件的change事件中:

 

        protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
        
{
            Response.Write(
"DropDownList控件选择改变!<br>"
);
        }


        
protected void TextBox1_TextChanged(object sender, EventArgs e)
        
{
            Response.Write(
"TextBox文本改变!<br>"
);
        }

如果控件本身的AutoPostBack设置为false(默认是false)时,文本框和下拉框发生改变时,不会执行change事件的,而是将事件信息缓存在客户端,当在页面上点击一个服务器端控件Button,

        protected void Button1_Click(object sender, System.EventArgs e)
        
{
           Response.Write(
"点击了Button按钮!<br>"
);
        }

此时将客户端中的事件信息发送到服务器端,执行所有的事件,返回到客户端的信息为:
TextBox文本改变!
DropDownList控件选择改变!
点击了Button按钮!

具体例子代码见附件。
HttpModuelandHttpHandler代码