管道模型--Asp.Net MVC篇 Http请求

管道模型–Asp.Net MVC篇 Http请求

我们自己写的程序,是怎样进行处理的?一个完整的HTTP请求流程:

img

一. 请求大致流程

拿一个实例了解整个流程的步骤:

  1. 用户浏览器输入地址

    例如 http://www.csdn.net

  2. DNS解析(域名供应商)

    将输入的网址解析成IP+端口

  3. 请求到达服务器Server

    IP可以在互联网上唯一定位一台服务器,而端口是用来确定进程的,端口还可以带有协议信息,用于穿过防火墙

  4. HTTP.SYS服务接收HTTP请求

    我们可以自己用IIS部署一个网站,模拟HTTP请求。顺序是部署网站----指定一个端口监听----请求到服务器----带了端口信息和协议----被HTTP.SYS监听到。HTTP.SYS是安装IIS时自动装上去的

  5. IIS将请求转发给ISAPI

    IIS不能处理我们写的代码,不会做业务的处理,它只能将我们的代码转发到对应的程序进行处理。

    它里面有一个“处理映射程序”,这里配置的是IIS的处理方式,即请求是什么后缀名,就用哪种dll处理程序进行处理,其中*.cshtml、.aspx、.ashx都是由asp.net_isapi.dll来进行处理,如图:

    image-20220328194011637

    image-20220328194109777

    因为IIS只是将请求根据后缀转发到不同的处理程序,所以我们甚至可以给java和php指定处理程序。比如将php转发给php_ISAPI,java转发给java_ISAPI,只要配置好就可以实现。

    如果是js,css,html等静态文件,IIS是直接返回的。

    这里有个小问题,聪明的你可能会说,像mvc这样 Home/Index,没有后缀怎么办?

    IIS6和它之前都不支持mvc的,后来出现了mvc,没有后缀怎么写匹配?IIS会给没有后缀的加一个axd的后缀再处理,如图:

    image-20220328194415048

    到了IIS7.0,就不需要这么做了。

    IIS的应用程序池分为集成和经典,如图:

    image-20220328194724164

  6. HttpWorkerRequest

    在上一步,会将http请求包装成一个HttpWorkerRequest对象,通过pipeline传到HttpRuntime.ProcessRequest ()这里来,这里才是asp.net开发的入口,前面是系统帮我们做好的,我们程序员是从这里开始搞事情的!

    HttpWorkerRequest的定义,如图:

    image-20220328201522712

  7. HttpRuntime

    HttpRuntime:Http运行时,ProcessRequest 是它下面的一个方法,ProcessRequest 方法需要一个HttpWorkerRequest参数,也就是上一步得到的。

    image-20220328202147338

二、HttpRuntime.ProcessRequest()

至于ProcessRequest是怎么处理的,就要进入“管道处理模型”了。

什么是“管道处理模型”呢?

就是一个请求进入到HttpRunTime之后,也就是从第7步开始,要做的事情,就叫做“管道处理模型”,因为前6步都是系统做好的,我们管不着,从第7步才开始运行我们写的代码。

image-20220328210051028

System.Web.HttpRuntime 类下的ProcessRequest() 方法源码。

// System.Web.HttpRuntime
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr)	//传入参数是HttpWorkerRequest类型的
{
    //如果传入null,抛出异常;
	if (wr == null)	
	{
		throw new ArgumentNullException("wr");
	}
    //如果没有使用管道模型,也抛出异常
	if (HttpRuntime.UseIntegratedPipeline)
	{
		throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[]
		{
			"HttpRuntime.ProcessRequest"
		}));
	}
    //如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr)
	HttpRuntime.ProcessRequestNoDemand(wr);	
}

传入参数是HttpWorkerRequest类型的,如果传入null,抛出异常;如果没有使用管道模型,也抛出异常,如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr):

// System.Web.HttpRuntime
internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
{
	RequestQueue requestQueue = HttpRuntime._theRuntime._requestQueue;
	wr.UpdateInitialCounters();
	if (requestQueue != null)
	{
		wr = requestQueue.GetRequestToExecute(wr);
	}
	if (wr != null)
	{
		HttpRuntime.CalculateWaitTimeAndUpdatePerfCounter(wr);
		wr.ResetStartTime();
		HttpRuntime.ProcessRequestNow(wr);
	}
}

这里有个RequestQueue,因为Http请求也可以队列,如果队列不为空,就执行GetRequestToExecute(wr)方法进行处理,再看下面的ProcessRequestNow(wr)方法:

// System.Web.HttpRuntime
internal static void ProcessRequestNow(HttpWorkerRequest wr)
{
	HttpRuntime._theRuntime.ProcessRequestInternal(wr);
}

它调用了ProcessRequestInternal(wr)方法。

ProcessRequestInternal(wr)方法是怎么做的呢?如果被释放了(disposingHttpRuntime),那么就发送一个503的错误,“Server Too Busy”,并用一个html页面来显示。如果没有被释放,就往下走,初始化一个HttpContext,它是Http请求上下文,如果初始化HttpContext失败了,就用html页面给用户返回一个400错误;

如果HttpContext初始化成功了,就把它拿到HttpApplicationFactory.GetApplicationInstance方法里面创建了一个HttpApplication对象。

// System.Web.HttpRuntime
private void ProcessRequestInternal(HttpWorkerRequest wr)
{
	Interlocked.Increment(ref this._activeRequestCount);
	if (this._disposingHttpRuntime)
	{
		try
		{
			wr.SendStatus(503, "Server Too Busy");
			wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
			byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
			wr.SendResponseFromMemory(bytes, bytes.Length);
			wr.FlushResponse(true);
			wr.EndOfRequest();
		}
		finally
		{
			Interlocked.Decrement(ref this._activeRequestCount);
		}
		return;
	}
	HttpContext httpContext;
	try
	{
		httpContext = new HttpContext(wr, false);
	}
	catch
	{
		try
		{
			wr.SendStatus(400, "Bad Request");
			wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
			byte[] bytes2 = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
			wr.SendResponseFromMemory(bytes2, bytes2.Length);
			wr.FlushResponse(true);
			wr.EndOfRequest();
			return;
		}
		finally
		{
			Interlocked.Decrement(ref this._activeRequestCount);
		}
	}
	wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext);
	HostingEnvironment.IncrementBusyCount();
	try
	{
		try
		{
			this.EnsureFirstRequestInit(httpContext);
		}
		catch
		{
			if (!httpContext.Request.IsDebuggingRequest)
			{
				throw;
			}
		}
		httpContext.Response.InitResponseWriter();
        
        //创建了一个HttpApplication对象, 其本质是一个IHttpHandler类型对象
		IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
		if (applicationInstance == null)
		{
			throw new HttpException(SR.GetString("Unable_create_app_object"));
		}
		if (EtwTrace.IsTraceEnabled(5, 1))
		{
			EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start");
		}
		if (applicationInstance is IHttpAsyncHandler)
		{
			IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance;
			httpContext.AsyncAppHandler = httpAsyncHandler;
			httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);
		}
		else
		{
			applicationInstance.ProcessRequest(httpContext);
			this.FinishRequest(httpContext.WorkerRequest, httpContext, null);
		}
	}
	catch (Exception e)
	{
		httpContext.Response.InitResponseWriter();
		this.FinishRequest(wr, httpContext, e);
	}
}

每个请求都经过了上面的步骤,创建了一个HttpApplication对象,用这个HttpApplication对象来处理请求,HttpApplication是我们管道模型的核心。

通过上述步骤,终于执行到了我们写的代码,前面我们几乎没做什么,都是框架做的,我们也扩展不了。

三、HttpApplication

因为HttpApplication要处理各种不同的请求,每个请求也许要做相同的事情,也可能不同的事情,也可能要做的事情的顺序不同,我们要把共性的部分封装起来,所以就封装成了这么多的事件(event),这样做的好处就是,遇到不同的请求,我们可以把这些事件排列和组合起来,就能完成请求的处理。
img

HttpApplication对象是由Asp.net帮助我们创建的,它是asp.net中处理请求的重要对象。为了便于扩展,HttpApplication采用处理管道的方式进行处理,将处理的步骤分为多个步骤,每个步骤通过事件的形式暴露给程序员,这些事件按照固定的处理顺序依次触发,程序员通过编写事件处理方法就可以定义一个请求的扩展过程。

对于HttpApplication,到ASP.NET 4.0,提供了19个标准事件。

序号事件名称备注
1BeginRequestasp.net开始处理请求的第一个事件,表示处理的开始
2AuthenticateRequest验证请求,开始检查用户的身份,一般用来取得请求的用户信息
3PostAuthenticateRequest已经获取请求的用户信息,用户信息已经检查完成,检查完成后可以用 HttpContext的User属性获取到,类型为System.Security.Principal.IPrincipal
4AuthorizeRequest开始进行用户权限检查,如果用户没有通过上面的安全检查,一般情况下直接跳至 EndRequest 事件
5PostAuthorizeRequest用户请求已经获得授权
6ResolveRequestCache获取以前处理缓存的处理结果,如果以前缓存过,那么不用再进行请求的处理工作,直接返回缓存的结果
7PostResolveRequestCache已经完成缓存的处理工作,缓存检查结果
创建被请求的页面类对象(aspx/ashx)
8PostMapRequestHandler已经根据用户的请求,创建了请求的处理器对象(IHttpHandler)
9AcquireRequestState获取请求的状态,一般用于session
10PostAcquireRequestState已经获得了session
11PreRequestHandlerExecute准备执行处理程序,即调用 HttpContext 中的Handler 属性的 ProcessRequest 方法
12PostRequestHandlerExecute已经执行了处理程序
13ReleaseRequestState准备释放请求的状态
14PostReleaseRequestState已经释放了请求的状态
15UpdateRequestCache更新缓存
16PostUpdateRequestCache已经更新完了缓存
17LogRequest请求的日志操作
18PostLogRequest已经完成请求的日志操作
19EndRequest本次请求处理完成
20Error
21RequestCompleted
22PreSendRequestHeaders(.net 4.0 新增)可以根据发送的 Header 来动态设置一些参数,比如,如果通过 Content-Type 参数获知发送的内容是 text/html 网页,那么通过启用输出的压缩来提高网络的传输速度。这个操作可以通过设置一个特殊的 Header 来通知浏览器
23PreSendRequestContent(.net 4.0 新增)如果配置了输出到客户端的压缩,那么可以在这个事件中包装输出到浏览器的流以实现输出的压缩

其中BeginRequest 和 EndRequest 是方便我们做扩展的,可以在这两个方法里面加上我们要的触发动作。

PostMapRequestHandler 就是把我们的请求创建一个处理器对象,我们写的MVC,Webform,都在它里面,要让它来实际执行的。

执行HtttpRuntime.ProcessRequest进入程序入口,基于HttpWorkerRequest创建HttpRequset; 继续往后是有IhttpHandler来执行—HttpApplication来执行;
如果HttpApplication中就只是编写处理业务逻辑的代码;

HttpApplication内部肯定有固定的逻辑;也有千千万万的开发者需要扩展的地方;事件:观察者模式;

可以把不变的逻辑(公共逻辑)写在HttpApplication内部,可变的逻辑就可以通过定义事件暴露出去;让别人来注册动作到事件中;

在执行的时候,按照事件的顺序来执行;从而达到框架的可扩展!

在事件中的动作—谁来注册呢? 是HttpMoudule

四、HttpMoudule

先写一段代码,用反射的方式获取所有系统自带的HttpApplication 的 Event事件

public ViewResult Events()
{
    //获取当前上下文的HttpApplication实例
    HttpApplication app = base.HttpContext.ApplicationInstance;

    List<SysEvent> sysEventsList = new List<SysEvent>();
    int i = 1;
    foreach (EventInfo e in app.GetType().GetEvents())
    {
        sysEventsList.Add(new SysEvent()
                          {
                              Id = i++,
                              Name = e.Name,
                              TypeName = e.GetType().Name
                          });
    }
    return View(sysEventsList);
}

运行一下,25个Event事件:

img

先说一下,HttpModule是什么:对HttpApplication做的扩展事件(上面图上,都是HttpApplication原本有的事件),Init方法,参数是HttpApplication。

public class MyCustomModule : IHttpModule
{
    public void Dispose()
    {
        //此处放置清除代码。
    }

    public void Init(HttpApplication application)
    {
        application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AcquireRequestState        "));
        application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthenticateRequest        "));
        application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthorizeRequest           "));
        application.BeginRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "BeginRequest               "));
        application.Disposed += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Disposed                   "));
        application.EndRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "EndRequest                 "));
        application.Error += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Error                      "));
        application.LogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "LogRequest                 "));
        application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "MapRequestHandler          "));
        application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAcquireRequestState    "));
        application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest    "));
        application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest       "));
        application.PostLogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostLogRequest             "));
        application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostMapRequestHandler      "));
        application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostReleaseRequestState    "));
        application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute  "));
        application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostResolveRequestCache    "));
        application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache     "));
        application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute   "));
        application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestContent      "));
        application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders      "));
        application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ReleaseRequestState        "));
        application.RequestCompleted += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "RequestCompleted           "));
        application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ResolveRequestCache        "));
        application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "UpdateRequestCache         "));

    }
}

好了,自定义的MyCustomModule写好了,但是还不能使用,要去webconfig注册, 注册后,每个页面都会执行这个Module。

img

运行一下,发现注册后比以前多了一些东西:

image-20220329200036688

MyCustomModule被从头到尾执行了一遍,这些蓝色的文字,说明在这个module中执行了哪些事件以及执行的顺序,而以前通过反射找到HttpApplication中事件(ShowEvents方法),是在PreRequstHandlerExecute方法中执行的,执行完后,还进行了收尾操作,比如PostRequestHandlerExecute(已经执行了处理程序),ReleaseRequestState(释放请求的状态),PostReleaseRequestState(已经释放了请求的状态)等等。

好了,上面就是我们自定义的Module,现在看看框架自带的Module,打开 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config

<httpModules>
    <add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
    <add name="Session" type="System.Web.SessionState.SessionStateModule"/>
    <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule"/>
    <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule"/>
    <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule"/>
    <add name="RoleManager" type="System.Web.Security.RoleManagerModule"/>
    <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule"/>
    <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule"/>
    <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule"/>
    <add name="Profile" type="System.Web.Profile.ProfileModule"/>
    <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
    <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule"/>
    <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

看到没?有OutputCache,Session,WindowsAuthentication等,这些都是.net4.0框架自己注册好了的Module,是全局的,运行在这台服务器上的Web程序都要用到这个config,每个页面用到了这些Module。但是这里列的一些Module我们是用不上的,比如WindowsAuthentication,FormsAuthentication,PassportAuthentication,可以干掉的,不过不要在这个全局的webconfig中干掉,可以在每个项目的webconfig中干掉,比如刚才项目的webcongif中可以加这么一些代码干掉不要的Module

img

有没有干掉呢?

写一个方法,获取全部的Module,包括我们扩展的MyCustomModule,还有框架自带的,看效果:

public ViewResult Modules()
{
    HttpApplication app = base.HttpContext.ApplicationInstance; //获取当前上下文的HttpApplication实例
    List<SysModules> sysModulesList = new List<SysModules>();
    int i = 1;
    foreach (string name in app.Modules.AllKeys)
    {
        sysModulesList.Add(new SysModules()
                           {
                               Id = i++,
                               Name = name,
                               TypeName = app.Modules[name].ToString()
                           });
    }//1 我们自定义配置的config   2 来自于.Net框架的配置

    return View(sysModulesList);
}

运行一下,发现在webconfig中remove的Module,它们都不出现了:

img

总结一下,HttpModule要实现IHttpModule接口,在webconfig注册,里面给HttpApplication事件去添加动作,并且HttpModule是每个页面(每次请求)都要执行的。

五、HttpModule 应用

  1. 权限认证:每个请求都经过Module,所以做权限认证很好

  2. URL转发

    • 新创建一个BaseModule

      public class BaseModule : IHttpModule
      {
          /// <summary>
          /// Init方法仅用于给期望的事件注册方法
          /// </summary>
          /// <param name="httpApplication"></param>
          public void Init(HttpApplication httpApplication)
          {
              httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
              httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
          }
          
          // 处理BeginRequest 事件的实际代码
          private void context_BeginRequest(object sender, EventArgs e)
          {
              HttpApplication application = (HttpApplication)sender;
              HttpContext context = application.Context;
              string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
              if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
              {
                  context.Response.Write(string.Format("<h4 style='color:#00f'>来自BaseModule 的处理,{0}请求到达</h4><hr>", DateTime.Now.ToString()));
              }
      
              //处理地址重写
              if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
                  context.RewritePath("/Pipe/Handler");
          }
      	......
      }
      
      

      注意这两句,先获取到了HttpApplication,后来又获取到了HttpContext,有了HttpContext就有了全世界。

      HttpApplication application = (HttpApplication)sender;
      HttpContext context = application.Context;
      
    • 注册BaseModule

      <httpModules>
      	<add name="BaseModule" type="XXX.Web.Core.PipeLine.BaseModule,XXX.Web.Core"/>
      </httpModules>
      
    • 访问 “/Pipe/Some”

      如果访问 “/Pipe/Some”,Module会偷偷跳到"/Pipe/Handler",而且浏览器的地址栏不改变,还是"/Pipe/Some"。

      MVC其实就是Module的扩展,即上面说过的UrlRoutingModule, MVC框架的诞生对webform没有影响,只是在原来框架流程上增加了一个UrlRoutingModule,它会把我们的请求做一次映射处理,指向MvcHandler,MVC路由为什么能生效啊?就是利用了UrlRoutingModule,处理方式和上面说的Url转发是一样的。

      伪静态也可以这样做,先在IIS里面给后缀指定处理程序asp.net_isapi(上面有介绍),请求进到HttpApplication后,我们扩展一个Module,在里面建立一些跳转页面的规则。

  3. 反爬虫

    每个请求都要经过Module,所以可以记录每个IP,如果某个IP请求太频繁,那么就不让它访问原始页面,让它访问一个需要输入验证码的页面,验证通过了才能继续访问。

    但也有Module不适合的场景,比如对一些特殊请求的处理。Module是每个请求都会执行的,单独给某些请求服务的不合适。

    又总结一下,管道处理模型就是请求进入System.Web(HttpRuntime.processrequest)之后,那些处理的类、方法、过程,这些就叫管道处理模型,mvc只是其中很小的一部分。

六、Module的扩展

  1. 新建一个GlobalModule.cs

    public class GlobalModule : IHttpModule
    {
        public event EventHandler GlobalModuleEvent;
    
        /// <summary>
        /// Init方法仅用于给期望的事件注册方法
        /// </summary>
        /// <param name="httpApplication"></param>
        public void Init(HttpApplication httpApplication)
        {
            httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
    
            httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
        }
    
        // 处理BeginRequest 事件的实际代码
        void context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;
            string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
            if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
                context.Response.Write(string.Format("<h1 style='color:#00f'>来自GlobalModule 的处理,{0}请求到达</h1><hr>", DateTime.Now.ToString()));
    
            //处理地址重写
            if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
                context.RewritePath("/Pipe/Handler");
    
            if (GlobalModuleEvent != null)
                GlobalModuleEvent.Invoke(this, e);
        }
    
        // 处理EndRequest 事件的实际代码
        void context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;
            string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
            if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
                context.Response.Write(string.Format("<hr><h1 style='color:#f00'>来自GlobalModule的处理,{0}请求结束</h1>", DateTime.Now.ToString()));
        }
    
        public void Dispose()
        {
        }
    }
    

    注意看这里:

    public event EventHandler GlobalModuleEvent;
    

    本来Module是用来添加事件的,比如:

    httpApplication.BeginRequest += new EventHandler(context_BeginRequest);	//Asp.net处理的第一个事件,表示处理的开始
    

    这里结果又给Module配置了一个事件GlobalModuleEvent。那怎样让这个GlobalModuleEvent生效呢?=> 在global里面添加代码

  2. 注册GlobalModule

    <httpModules>
    	<add name="GlobalModule" type="XXX.Web.Core.PipeLine.GlobalModule,XXX.Web.Core"/>
    </httpModules>
    
  3. 在Global里面添加代码

    /// <summary>
    /// HttpModule注册名称_事件名称
    /// 约定的
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e)
    {
        Response.Write("<h3 style='color:#800800'>来自 Global.asax 的文字 GlobalModule_GlobalModuleEvent</h2>");
    }
    

    其中方法名 = webconfig中注册的Module名称 + ‘_’+ Module中的事件名称。这样,这个方法就会自动触发。

  4. 访问站点查看效果

    image-20220329202506180

有没有类似的?有的,大家应该见过这个,也是在Global里面。

例如Session:

protected void Session_Start(object sender, EventArgs e)
{
    // 在新会话启动时运行的代码
    Console.WriteLine("Session_Start 啥也不干");
    logger.Info("Session_Start");
}

protected void Session_End(object sender, EventArgs e)
{
    // 在会话结束时运行的代码。 
    // 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
    // InProc(默认内存里) 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer 
    // 或 SQLServer,则不会引发该事件。
    Console.WriteLine("Session_End 啥也不干");
    logger.Info("Session_End");
}

Start和End就是Session这个Module里面定义的Event。有证据吗?有的!先看看全局的webconfig,session注册过。

img

然后通过反编译工具,找到SessionState这个类,从它内部找到了Start和End。

image-20220329202806944

这样做的好处是:我们封装了Module之后,Module里面还有要扩展的,就可以做成Event,在global里面去实现,并且也只能在global里面实现,什么时候执行这个Event呢?根据自己的需求。使用套路如图:

img

七、其他

Application_Error是处理异常的,filter也是处理异常的,但前提是要进入了控制器,如果是cshtml出错了,或者页面不存在,filter就管不到了,但可以被Application_Error处理,它可以捕获整个网站的异常,不管是控件级事件、页面级事件还是请求级事件,都可以get到。

/// <summary>
/// 请求出现异常,都可以处理
/// 也可以完成全局异常处理
/// filter只能处理控制器里面的异常
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Application_Error(object sender, EventArgs e)
{
    logger.Info("Application_Error");
    Response.Write("出错");
    Server.ClearError();
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武功山上捡瓶子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值