mmmm123 发表于 2025-2-7 02:06:40

Asp.net MVC中的Http管道事件为什么要以Application_开头?

今天遇到一个问题,需要在API请求结束时,释放数据库链接,避免连接池被爆掉。
按照以往的经验,需要实现IHttpModule,具体不展开了。
但是实现了IHttpModule后,还得去web.config中增加配置,这有点麻烦了,就想有没有简单的办法。
其实是有的,就是在Global.asax.cs里面定义并实现 Application_EndRequest 方法,在这个方法里面去释放数据库连接即可,经过测试,确实能达到效果。
但是,为什么方法名必须是Application_EndRequest ?在这之前真不知道为什么,只知道baidu上是这么说的,也能达到效果。
还好我有一点好奇心,想搞清楚是怎么回事情,就把net framework的源码拉下来(其实源代码在电脑里面已经躺了N年了) 分析了一下,以下是分析结果。
省略掉前面N个调用
第一个需要关注的是 HttpApplicationFactory.cs
从名字就知道,这是HttpApplication的工厂类,大家看看Gloabal.asax.cs 里面,是不是这样定义的
public class MvcApplication : System.Web.HttpApplication{.....}两者结合起来看,可以推测,HttpApplicationFactory 是用来获取 MvcApplication 实例的,实际情况也是如此 上代码(来自HttpApplicationFactory)
internal class HttpApplicationFactory{internal const string applicationFileName = "global.asax"; //看到这里,就知道为什么入口文件是global.asax了,因为这里定义死了...private void EnsureInited() {      if (!_inited) {          lock (this) {            if (!_inited) {                  Init();                  _inited = true;            }          }      }}private void Init() {      if (_customApplication != null)          return;      try {          try {            _appFilename = GetApplicationFile();            CompileApplication();          }          finally {            // Always set up global.asax file change notification, even if compilation            // failed.This way, if the problem is fixed, the appdomain will be restarted.            SetupChangesMonitor();          }      }      catch { // Protect against exception filters          throw;      }}private void CompileApplication() {    // Get the Application Type and AppState from the global file    _theApplicationType = BuildManager.GetGlobalAsaxType();    BuildResultCompiledGlobalAsaxType result = BuildManager.GetGlobalAsaxBuildResult();    if (result != null) {      // Even if global.asax was already compiled, we need to get the collections      // of application and session objects, since they are not persisted when      // global.asax is compiled.Ideally, they would be, but since <object> tags      // are only there for ASP compat, it's not worth the trouble.      // Note that we only do this is the rare case where we know global.asax contains      // <object> tags, to avoid always paying the price (VSWhidbey 453101)      if (result.HasAppOrSessionObjects) {            GetAppStateByParsingGlobalAsax();      }      // Remember file dependencies      _fileDependencies = result.VirtualPathDependencies;    }    if (_state == null) {      _state = new HttpApplicationState();    }    // Prepare to hookup event handlers via reflection    ReflectOnApplicationType();}   private void ReflectOnApplicationType() {    ArrayList handlers = new ArrayList();    MethodInfo[] methods;    Debug.Trace("PipelineRuntime", "ReflectOnApplicationType");    // get this class methods    methods = _theApplicationType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);    foreach (MethodInfo m in methods) {      if (ReflectOnMethodInfoIfItLooksLikeEventHandler(m))            handlers.Add(m);    }      // get base class private methods (GetMethods would not return those)    Type baseType = _theApplicationType.BaseType;    if (baseType != null && baseType != typeof(HttpApplication)) {      methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);      foreach (MethodInfo m in methods) {            if (m.IsPrivate && ReflectOnMethodInfoIfItLooksLikeEventHandler(m))                handlers.Add(m);      }    }    // remember as an array    _eventHandlerMethods = new MethodInfo;    for (int i = 0; i < _eventHandlerMethods.Length; i++)      _eventHandlerMethods = (MethodInfo)handlers;}    private bool ReflectOnMethodInfoIfItLooksLikeEventHandler(MethodInfo m) {    if (m.ReturnType != typeof(void))      return false;    // has to have either no args or two args (object, eventargs)    ParameterInfo[] parameters = m.GetParameters();    switch (parameters.Length) {      case 0:            // ok            break;      case 2:            // param 0 must be object            if (parameters.ParameterType != typeof(System.Object))                return false;            // param 1 must be eventargs            if (parameters.ParameterType != typeof(System.EventArgs) &&                !parameters.ParameterType.IsSubclassOf(typeof(System.EventArgs)))                return false;            // ok            break;      default:            return false;    }    // check the name (has to have _ not as first or last char)    String name = m.Name;    int j = name.IndexOf('_');    if (j <= 0 || j > name.Length-1)      return false;    // special pseudo-events    if (StringUtil.EqualsIgnoreCase(name, "Application_OnStart") ||      StringUtil.EqualsIgnoreCase(name, "Application_Start")) {      _onStartMethod = m;      _onStartParamCount = parameters.Length;    }    else if (StringUtil.EqualsIgnoreCase(name, "Application_OnEnd") ||             StringUtil.EqualsIgnoreCase(name, "Application_End")) {      _onEndMethod = m;      _onEndParamCount = parameters.Length;    }    else if (StringUtil.EqualsIgnoreCase(name, "Session_OnEnd") ||             StringUtil.EqualsIgnoreCase(name, "Session_End")) {      _sessionOnEndMethod = m;      _sessionOnEndParamCount = parameters.Length;    }    return true;}}上面代码调用链路是EnsureInited->Init->CompileApplication->ReflectOnApplicationType->ReflectOnMethodInfoIfItLooksLikeEventHandler ,核心作用是:将MvcApplication中,方法名包含下划线、方法参数为空或者有2个参数(第一个参数的类型是Object,第二个参数的类型是EventArgs) 的方法加入到_eventHandlerMethods 中
那么事件是怎么绑定的呢?继续上代码
internal class HttpApplicationFactory{......using (new ApplicationImpersonationContext()) {      app.InitInternal(context, _state, _eventHandlerMethods);}............using (new ApplicationImpersonationContext()) {      app.InitInternal(context, _state, _eventHandlerMethods);}......}// HttpApplication.cspublic class HttpApplication{internal void InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) {    .....          if (handlers != null) {          HookupEventHandlersForApplicationAndModules(handlers);      }    .....}   internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) {    .....      if (handlers != null)                HookupEventHandlersForApplicationAndModules(handlers);    .....}private void HookupEventHandlersForApplicationAndModules(MethodInfo[] handlers) {      _currentModuleCollectionKey = HttpApplicationFactory.applicationFileName;      if(null == _pipelineEventMasks) {          Dictionary<string, RequestNotification> dict = new Dictionary<string, RequestNotification>();          BuildEventMaskDictionary(dict);          if(null == _pipelineEventMasks) {            _pipelineEventMasks = dict;          }      }      for (int i = 0; i < handlers.Length; i++) {          MethodInfo appMethod = handlers;          String appMethodName = appMethod.Name;          int namePosIndex = appMethodName.IndexOf('_');          String targetName = appMethodName.Substring(0, namePosIndex);          // Find target for method          Object target = null;          if (StringUtil.EqualsIgnoreCase(targetName, "Application"))            target = this;          else if (_moduleCollection != null)            target = _moduleCollection;          if (target == null)            continue;          // Find event on the module type          Type targetType = target.GetType();          EventDescriptorCollection events = TypeDescriptor.GetEvents(targetType);          string eventName = appMethodName.Substring(namePosIndex+1);          EventDescriptor foundEvent = events.Find(eventName, true);          if (foundEvent == null            && StringUtil.EqualsIgnoreCase(eventName.Substring(0, 2), "on")) {            eventName = eventName.Substring(2);            foundEvent = events.Find(eventName, true);          }          MethodInfo addMethod = null;          if (foundEvent != null) {            EventInfo reflectionEvent = targetType.GetEvent(foundEvent.Name);            Debug.Assert(reflectionEvent != null);            if (reflectionEvent != null) {                  addMethod = reflectionEvent.GetAddMethod();            }          }          if (addMethod == null)            continue;          ParameterInfo[] addMethodParams = addMethod.GetParameters();          if (addMethodParams.Length != 1)            continue;          // Create the delegate from app method to pass to AddXXX(handler) method          Delegate handlerDelegate = null;          ParameterInfo[] appMethodParams = appMethod.GetParameters();          if (appMethodParams.Length == 0) {            // If the app method doesn't have arguments --            // -- hookup via intermidiate handler            // only can do it for EventHandler, not strongly typed            if (addMethodParams.ParameterType != typeof(System.EventHandler))                  continue;            ArglessEventHandlerProxy proxy = new ArglessEventHandlerProxy(this, appMethod);            handlerDelegate = proxy.Handler;          }          else {            // Hookup directly to the app methods hoping all types match            try {                  handlerDelegate = Delegate.CreateDelegate(addMethodParams.ParameterType, this, appMethodName);            }            catch {                  // some type mismatch                  continue;            }          }          // Call the AddXXX() to hook up the delegate          try {            addMethod.Invoke(target, new Object{handlerDelegate});          }          catch {            if (HttpRuntime.UseIntegratedPipeline) {                  throw;            }          }          if (eventName != null) {            if (_pipelineEventMasks.ContainsKey(eventName)) {                  if (!StringUtil.StringStartsWith(eventName, "Post")) {                      _appRequestNotifications |= _pipelineEventMasks;                  }                  else {                      _appPostNotifications |= _pipelineEventMasks;                  }            }          }      }}}核心方法:HookupEventHandlersForApplicationAndModules,其作用就是将前面获取到的method与HttpApplication的Event进行绑定(前提是方法名是以Application_开头的),
后面就是向IIS注册事件通知了,由于看不到IIS源码,具体怎么做的就不知道了。
最后安利一下,还是用net core吧,更加清晰、直观,谁用谁知道。
页: [1]
查看完整版本: Asp.net MVC中的Http管道事件为什么要以Application_开头?