Asp.net MVC中的Http管道事件為什麼要以Application_開頭?

jikhww發表於2024-12-02

今天遇到一個問題,需要在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[handlers.Count];
    for (int i = 0; i < _eventHandlerMethods.Length; i++)
        _eventHandlerMethods[i] = (MethodInfo)handlers[i];
  }
  
  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[0].ParameterType != typeof(System.Object))
                return false;
            // param 1 must be eventargs
            if (parameters[1].ParameterType != typeof(System.EventArgs) &&
                !parameters[1].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.cs
public 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[i];
          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[targetName];

          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[0].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[0].ParameterType, this, appMethodName);
              }
              catch {
                  // some type mismatch
                  continue;
              }
          }

          // Call the AddXXX() to hook up the delegate

          try {
              addMethod.Invoke(target, new Object[1]{handlerDelegate});
          }
          catch {
              if (HttpRuntime.UseIntegratedPipeline) {
                  throw;
              }
          }

          if (eventName != null) {
              if (_pipelineEventMasks.ContainsKey(eventName)) {
                  if (!StringUtil.StringStartsWith(eventName, "Post")) {
                      _appRequestNotifications |= _pipelineEventMasks[eventName];
                  }
                  else {
                      _appPostNotifications |= _pipelineEventMasks[eventName];
                  }
              }
          }
      }
  }
}


核心方法:HookupEventHandlersForApplicationAndModules,其作用就是將前面獲取到的method與HttpApplication的Event進行繫結(前提是方法名是以Application_開頭的),

後面就是向IIS註冊事件通知了,由於看不到IIS原始碼,具體怎麼做的就不知道了。

最後安利一下,還是用net core吧,更加清晰、直觀,誰用誰知道。

相關文章