今天遇到一個問題,需要在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吧,更加清晰、直觀,誰用誰知道。