asp.net HttpModule和HttpApplication 用法

作者:袖梨 2022-06-25

 ihttpmodule是个什么东西呢?对我们web开发有什么用呢?
    先从名字来看他是一个接口,接口就是让人来继承的,我们要用它就得继承他,并实现他的方法。module的意思是模块、组件的意思。如果说我们实现了这个接口,并配置了web.config,让iis的知道我们的web程序使用了这个组件;那么我们的程序是不是就比默认的web程序多了个组件?!显然,而且在必要的时候会调用我们组件里定义的方法,这就是httpmodule的用处。说白了,就是我们给iis写扩展,但该扩展仅仅是针对于使用了(配置config)的web程序。其实每个web应用程序都是一个iis进程,而这个进程的配置文件就是web.config。
    弄明白了他的意义,我们就开始!我们创建一个web应用程序,并创建一个类,继承ihttpmodule,并实现他的方法,在config的modules节点里,ok!

namespace webapplication1
{
    public class myhttpmodule : ihttpmodule
    {
        public void dispose()
        {
        }

        public void init(httpapplication context)
        {
            context.context.response.write(1);
        }
    }
}




 
   
   
     
   

 

 
    requests="true">
     
   

 

web.config的配置有2个,上面的那个是给非iis7用的,下面的显然就是给iis7用的。启动程序,what happend?! 是不是页的头部多了个1,有木有!!我们打开任何页面都会有个1,说明我们的模块起到作用了,也说明每个请求都会执行httpmodule的init方法?是不是呢?
我们把代码改一下:

 

public void init(httpapplication context)
 {
     context.context.response.write(1);
     context.beginrequest += onbeginrequest;
 }

 public void onbeginrequest(object sender, eventargs e)
 {
     var app = (httpapplication)sender;
     var url = app.context.request.rawurl;
     app.context.response.write(url);
 }

分别给init和onbeginrequest 两个方法加断点,重新编译下,然后f5看看。init只走1次,而onbeginrequest却走了3次,ur的值l分别是  default.asp教程x   style.css教程 和 favorite.ico;可以看出任何url请求,包括静态文件,都会经过执行我们定义的事件方法!看来这要比只处理aspx慢不少!
init的必须走一次啊,要不然事件不被订阅3次了?,但为什么只走1次呢?这到底是为什么呢? 呵呵,其实很简单,myhttpmodule就实例化一次哦,实例化后执行init初始化,然后就驻留在应用程序池了,直到应用程序池被回收,或他被各种原因搞崩溃;而onbeginrequest是被httpapplication类的beginrequest事件订阅的。事件订阅是个什么概念?事件是个特殊的委托,委托是个什么概念?委托是个方法指针。所以,只要委托被执行,就会执行他指向的方法体,也就是onbeginrequest,可见onbeginrequest的执行,是和httpapplication的beginrequest有关系的,和myhttpmodule本身已经没关系了。
    走了3次说明3个request都执行了beginrequest,难道每个请求都实例化一个httpapplication?从名字我就能看出不会的,因为application(应用程序)嘛,我们目前运行的就一个,怎么会不断的实例化!想刨根问题,彻底整明白,就得翻出framework的源码,调试!
(------------声明,下面的源码可以不用完全理解,也可以跳过,只要知道跟request有关就行了------------)
下面来调查下httpapplication的初始化过程!
用reflector查阅system.web名字空间下的类,可以看到httpapplicationfactory类,他负责httpapplication的创建。当我们启动站点后,第一次的时候比较慢,为什么呢? 因为初始化的构建工作。
system.web.complilation名字空间下有一堆的构建类,其中就有构建global.asax的,也就是我们的httpapplication类,然后缓存到factory的堆栈里,我们需要的时候pop出来。 (你可能有疑问,pop了不就没了吗? 其实app在执行的时候还会push回去,详见httpapplication.releaseappinstance方法) 

 
httpapplicationfactory有个getapplicationinstance方法,就是用来获取httpapplication的:

internal static ihttphandler getapplicationinstance(httpcontext context)
{
    if (_customapplication != null)
    {
        return _customapplication;
    }
    if (context.request.isdebuggingrequest)
    {
        return new httpdebughandler();
    }
    _theapplicationfactory.ensureinited();
    _theapplicationfactory.ensureapps教程tartcalled(context);
    return _theapplicationfactory.getnormalapplicationinstance(context);
}

private httpapplication getnormalapplicationinstance(httpcontext context)
{
    httpapplication application = null;
    lock (this._freelist)
    {
        if (this._numfreeappinstances > 0)
        {
            application = (httpapplication) this._freelist.pop();//如果_freelist里有,就直接获取,只有第一次构建的时候没有
            this._numfreeappinstances--;
            if (this._numfreeappinstances < this._minfreeappinstances)
            {
                this._minfreeappinstances = this._numfreeappinstances;
            }
        }
    }
    if (application == null)
    {
        application = (httpapplication) httpruntime.createnonpublicinstance(this._theapplicationtype);
        using (new applicationimpersonationcontext())
        {
            application.initinternal(context, this._state, this._eventhandlermethods);//这里是初始化application的,并且会经过复杂的一坨代码后push到_freelist里
        }
    }
    return application;
}


跟踪这个方法,我们可以断定,application是被缓存起来的,不是每次都是实例化的。
通过reflector的分析,我们能发现这个getapplicationinstance方法是被httpruntime.processrequestnow调用的,终于回到我们的request来了。

private void processrequestnow(httpworkerrequest wr)
{
    httpcontext context;
    try
    {
        context = new httpcontext(wr, false);//实例化上下文
    }
    catch
    {
        wr.sendstatus(400, "bad request");
        wr.sendknownresponseheader(12, "text/html; charset=utf-8");
        byte[] bytes = encoding.ascii.getbytes("bad request");
        wr.sendresponsefrommemory(bytes, bytes.length);
        wr.flushresponse(true);
        wr.endofrequest();
        return;
    }
    wr.setendofsendnotification(this._asyncendofsendcallback, context);
    interlocked.increment(ref this._activerequestcount);
    hostingenvironment.incrementbusycount();
    try
    {
        try
        {
            this.ensurefirstrequestinit(context);
        }
        catch
        {
            if (!context.request.isdebuggingrequest)
            {
                throw;
            }
        }
        context.response.initresponsewriter();//实例化httpwriter,输出用的,我们的控件输出全靠他
        ihttphandler applicationinstance = httpapplicationfactory.getapplicationinstance(context);//获取handler也就是httpapplication
        if (applicationinstance == null)
        {
            throw new httpexception(sr.getstring("unable_create_app_object"));
        }
        if (etwtrace.istraceenabled(5, 1))
        {
            etwtrace.trace(etwtracetype.etw_type_start_handler, context.workerrequest, applicationinstance.gettype().fullname, "start");
        }
        if (applicationinstance is ihttpasynchandler)//是否异步的,显然我们的httpapplication是继承这个接口的,所以走这个if
        {
            ihttpasynchandler handler2 = (ihttpasynchandler) applicationinstance;
            context.asyncapphandler = handler2;
            handler2.beginprocessrequest(context, this._handlercompletioncallback, context);//从beginrequest就开始执行application的step
        }
        else
        {
            applicationinstance.processrequest(context);//直接执行
            this.finishrequest(context.workerrequest, context, null);
        }
    }
    catch (exception exception)
    {
        context.response.initresponsewriter();
        this.finishrequest(wr, context, exception);
    }
}

 
很好,上面的代码让我们看清了一个request的执行过程。而每个request都会走这个方法!httpruntime有个requestqueue(请求队列),会依次执行所有的request。终于知道为什么走3次了吧:) 就是application被用了3次。感兴趣的同学可以再去跟踪requestqueue,我就不贴了。

另外,httpapplication,意味着他是整个站点的boss,我们定义的myhttpmodule不过是他众多modules里的其中之一。而且我们也可以定义多个module,config里面add多个就可以了。
在application初始化的过程中,有初始化module的一段,我贴出来大家看看:

private void initmodules()
{
    this._modulecollection = runtimeconfig.getappconfig().httpmodules.createmodules();
    this.initmodulescommon();
}

最后,初始化所有的module,包括系统的一些module。

private void initmodulescommon()
{
    int count = this._modulecollection.count;
    for (int i = 0; i < count; i++)
    {
        this._currentmodulecollectionkey = this._modulecollection.getkey(i);
        this._modulecollection[i].init(this);//初始化每个httpmodule
    }
    this._currentmodulecollectionkey = null;
    this.initapplevelculture();
}


 httpapplication类公开了很多事件。上面的示例程序用到了beginrequest,这个事件是最先开始执行的事件;其作用很明白,就是request开始执行时,我们要准备点什么?或许你可能需要urlrewriter:)。下面插播“事件广告”,广告之后马上飞来。
上面我只是简单的提了一句“事件是特殊的委托”,并没有详细说为什么特殊。不知道同学你是否理解事件的意义呢?事件的意义是什么?
我是这么理解的。“事件”,代表着一件事情的发生。我们打一个比方,我把每天的生活设计成一个类。那么,一天的生活包含什么?包含从早到晚,包含很多事情要去做,甚至包含一些固定的事情。
细品这3个包含:
从早到晚,意味着这是一个过程,从头到尾,从始到终;很多事情要去做,说明在这个过程中要执行很多事;而一些固定的事情,比如吃饭,睡觉。
我们可以把早和晚,看作是构造函数和析构函数;把很多事情要做看作是事件;把固定的事情看作是方法。
因为每个人一天的生活都不一定是相同的,所以每天要去做的事我们没法写成方法!我们最多只能定义一些固有的模式的方法抽象,比如起床后做什么,午饭后做什么,睡觉前做什么。这不就是事件么?
我们在设计类到时候,如果类的使用有时候也涉及到一些执行过程的问题,而在这个过程中会发生一些未知的事情(未知意味着由外部类来提供,自己提供就是已知了),我们便把这些未知设计成抽象的方法。
由于过程的顺序是固定的,比如午饭后做什么就必须实在午饭后,所以午饭后做什么事件不能被别人在早上使用(你就是上帝不能把午饭的事情给我挪到早饭,挪了就叫早饭后了)。
同样的道理,事件的执行不能由外部来决定,这就是事件有别于委托的地方(委托没有使用限制,随时随地都可以用),这也是事件的意义。 整个过程也就是所谓的“生命周期”。
代码和现实就是这么的一致,耐人寻味。

广告回来~~ 继续看httpapplication的事件,我把他们按执行的顺序贴了出来;从名字就能看出大概的作用。有些我从来没用过:)
beginrequest        //请求开始
authenticaterequest   
postauthenticaterequest   
authorizerequest       
postauthorizerequest
resolverequestcache   
postresolverequestcache
postmaprequesthandler
acquirerequeststate    //获得请求状态,这时候已经有session了
postacquirerequeststate
prerequesthandlerexecute    //准备交给httphandler处理
error            //请求出现了异常!!!
postrequesthandlerexecute
releaserequeststate    //发布请求的状态
postreleaserequeststate
updaterequestcache
postupdaterequestcache
endrequest        //结束请求
presendrequestheaders    //准备发送请求头信息,在这我们还能修改内容
presendrequestcontent    //准备发送请求内容,这里就改不了了

这才是真正的整个生命周期,是不是!而面试题一般考的是page类的生命周期,这已经过时了,web开发又不光webform,所以考page类,没技术含量:)
在httpapplication里,把这些事件作为step,step by step的执行下去,下面是httpapplication构建step的代码:

internal override void buildsteps(waitcallback stepcallback)
{
    arraylist steps = new arraylist();
    httpapplication app = base._application;
    bool flag = false;
    urlmappingssection urlmappings = runtimeconfig.getconfig().urlmappings;
    flag = urlmappings.isenabled && (urlmappings.urlmappings.count > 0);
    steps.add(new httpapplication.validatepathexecutionstep(app));
    if (flag)
    {
        steps.add(new httpapplication.urlmappingsexecutionstep(app));
    }
    app.createeventexecutionsteps(httpapplication.eventbeginrequest, steps);
    app.createeventexecutionsteps(httpapplication.eventauthenticaterequest, steps);
    app.createeventexecutionsteps(httpapplication.eventdefaultauthentication, steps);
    app.createeventexecutionsteps(httpapplication.eventpostauthenticaterequest, steps);
    app.createeventexecutionsteps(httpapplication.eventauthorizerequest, steps);
    app.createeventexecutionsteps(httpapplication.eventpostauthorizerequest, steps);
    app.createeventexecutionsteps(httpapplication.eventresolverequestcache, steps);
    app.createeventexecutionsteps(httpapplication.eventpostresolverequestcache, steps);
    steps.add(new httpapplication.maphandlerexecutionstep(app));
    app.createeventexecutionsteps(httpapplication.eventpostmaprequesthandler, steps);
    app.createeventexecutionsteps(httpapplication.eventacquirerequeststate, steps);
    app.createeventexecutionsteps(httpapplication.eventpostacquirerequeststate, steps);
    app.createeventexecutionsteps(httpapplication.eventprerequesthandlerexecute, steps);
    steps.add(new httpapplication.callhandlerexecutionstep(app));
    app.createeventexecutionsteps(httpapplication.eventpostrequesthandlerexecute, steps);
    app.createeventexecutionsteps(httpapplication.eventreleaserequeststate, steps);
    app.createeventexecutionsteps(httpapplication.eventpostreleaserequeststate, steps);
    steps.add(new httpapplication.callfilterexecutionstep(app));
    app.createeventexecutionsteps(httpapplication.eventupdaterequestcache, steps);
    app.createeventexecutionsteps(httpapplication.eventpostupdaterequestcache, steps);
    this._endrequeststepindex = steps.count;
    app.createeventexecutionsteps(httpapplication.eventendrequest, steps);
    steps.add(new httpapplication.noopexecutionstep());
    this._execsteps = new httpapplication.iexecutionstep[steps.count];
    steps.copyto(this._execsteps);
    this._resumestepswaitcallback = stepcallback;
}

从构建的顺序我们也能看出执行的顺序,每个step都有一个execute的方法,挨个执行下去,如果程序出现异常,则直接跳出。而我们的page执行是在callhandlerexecutionstep这个step里。

相关文章

精彩推荐