我们还是简单的来复习一下Session吧:Session的数据时保存在服务器端,并且每个客户端对应不同Session。那么Session究竟是如何保存,如何区分客服端的了?我们还是沿用以前的方法来讲吧,以一个demo开始:
protected void Page_Load(object sender, EventArgs e) { string name = this.Request["Name"]; object sessionName = Session["Name"]; if (string.IsNullOrEmpty(name) && sessionName==null) { Response.Write("Please Enter your name!"); } else { if (sessionName == null) { Session.Add("Name", name); Response.Write("Set Session name and Session ID:"+Session.SessionID); } else { Response.Write("Get Session Name and Session ID:"+ Session.SessionID); } Response.Write(" Name:" + name); } }
假设我们的请求路径为http://localhost:18385/WebForm1.aspx?name=majiang
第一次请求数据如下:
第二次请求数据了:
这里我们看见在第一次请求的返回头里面有一个ASP.NET_SessionId,在第二次请求过程中这个请求头里面也含有ASP.NET_SessionId,并且它的值刚好是Session.SessionID(我这里用的是asp.net4.5),我们可以猜测这个ASP.NET_SessionId就是用来区分不同的客户端请求。那么这个值是什么时候生成的又是什么时候输出的了?
首先我们需要知道我们用到的那个Session具体在什么地方定义的,其实它是定义于HttpContext的Session属性中,我们一般的page也只是调用这个属性而已。
public HttpSessionState Session
{
get
{
if (this.HasWebSocketRequestTransitionCompleted)
{
return null;
}
if (this._sessionStateModule != null)
{
lock (this)
{
if (this._sessionStateModule != null)
{
this._sessionStateModule.InitStateStoreItem(true);
this._sessionStateModule = null;
}
}
}
return (HttpSessionState) this.Items["AspSession"];
}
}
这里用到一个_sessionStateModule的变量,那么究竟在什么地方操作它们的了?在HttpContext中有两个操作sessionStateModule方法如下:
internal void AddDelayedHttpSessionState(SessionStateModule module)
{
if (this._sessionStateModule != null)
{
throw new HttpException(SR.GetString("Cant_have_multiple_session_module"));
}
this._sessionStateModule = module;
}
internal void RemoveDelayedHttpSessionState()
{
this._sessionStateModule = null;
}
这两个方法干什么的我就不说了,它们是在什么地方调用的了。如果你开发过asp.net,那么你应该知道在SessionStateModule 类,它是一个IHttpModule的实现者专门用来管理Session的,在这个类中有一个InitModuleFromConfig方法,该方法主要 是在该类的Init中调用,如丧我们来看看它的具体实现吧:
private void InitModuleFromConfig(HttpApplication app, SessionStateSection config) { if (config.Mode != SessionStateMode.Off) { app.AddOnAcquireRequestStateAsync(new BeginEventHandler(this.BeginAcquireState), new EndEventHandler(this.EndAcquireState)); app.ReleaseRequestState += new EventHandler(this.OnReleaseState); app.EndRequest += new EventHandler(this.OnEndRequest); this._partitionResolver = this.InitPartitionResolver(config); switch (config.Mode) { case SessionStateMode.InProc: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new InProcSessionStateStore(); this._store.Initialize(null, null); break; case SessionStateMode.StateServer: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new OutOfProcSessionStateStore(); ((OutOfProcSessionStateStore) this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.SQLServer: this._store = new SqlSessionStateStore(); ((SqlSessionStateStore) this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.Custom: this._store = this.InitCustomStore(config); break; } this._idManager = this.InitSessionIDManager(config); if (((config.Mode == SessionStateMode.InProc) || (config.Mode == SessionStateMode.StateServer)) && this._usingAspnetSessionIdManager) { this._ignoreImpersonation = true; } } }
这里主要是设置 this._store和 this._idManager 它们两个变量,其中 this._store的设置根据Session的存储类型不同设置为不同的实例,这里的存储方式有以下四种
public enum SessionStateMode
{
Off,
InProc,
StateServer,
SQLServer,
Custom
}
默认的是SessionStateMode.InProc,所以默认的this._store是一个InProcSessionStateStore实
例,而this._idManager默认是一个SessionIDManager实例。这个方法结束后我们的 this._store和
this._idManager这两个变量就已经有值了。在SessionStateModule类中还有一个很重要的方法
BeginAcquireState:
private IAsyncResult BeginAcquireState(object source, EventArgs e, AsyncCallback cb, object extraData) { IAsyncResult result; bool sessionStateItem = true; bool flag3 = false; this._acquireCalled = true; this._releaseCalled = false; this.ResetPerRequestFields(); this._rqContext = ((HttpApplication) source).Context; this._rqAr = new HttpAsyncResult(cb, extraData); this.ChangeImpersonation(this._rqContext, false); try { if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_BEGIN, this._rqContext.WorkerRequest); } this._store.InitializeRequest(this._rqContext); bool requiresSessionState = this._rqContext.RequiresSessionState; if (this._idManager.InitializeRequest(this._rqContext, false, out this._rqSupportSessionIdReissue)) { this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } if ((s_allowInProcOptimization && !s_sessionEverSet) && (!requiresSessionState || !((SessionIDManager) this._idManager).UseCookieless(this._rqContext))) { flag3 = true; } else { this._rqId = this._idManager.GetSessionID(this._rqContext); } if (!requiresSessionState) { if (this._rqId != null) { this._store.ResetItemTimeout(this._rqContext, this._rqId); } this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } this._rqExecutionTimeout = this._rqContext.Timeout; if (this._rqExecutionTimeout == DEFAULT_DBG_EXECUTION_TIMEOUT) { this._rqExecutionTimeout = s_configExecutionTimeout; } this._rqReadonly = this._rqContext.ReadOnlySessionState; if (this._rqId != null) { sessionStateItem = this.GetSessionStateItem(); } else if (!flag3) { bool flag4 = this.CreateSessionId(); this._rqIdNew = true; if (flag4) { if (s_configRegenerateExpiredSessionId) { this.CreateUninitializedSessionState(); } this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } } if (sessionStateItem) { this.CompleteAcquireState(); this._rqAr.Complete(true, null, null); } result = this._rqAr; } finally { this.RestoreImpersonation(); } return result; }
在这个方法中有以下3句比较重要
this._rqId = this._idManager.GetSessionID(this._rqContext);
sessionStateItem = this.GetSessionStateItem();
this.CompleteAcquireState();
第一句获取SessionID,第二句货物SessionStateItem,第三句主要是调用一个CompleteAcquireState方法,而这个方法里面有一句 SessionStateUtility.AddDelayedHttpSessionStateToContext(this._rqContext, this);或则this.InitStateStoreItem(true); 这个方法主要对应一句
SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);,在这个类中还有一个方法OnReleaseState里面有这么一句
SessionStateUtility.RemoveHttpSessionStateFromContext(this._rqContext, delayed);
我们首先来可看看SessionStateUtility的AddHttpSessionStateToContext、RemoveHttpSessionStateFromContext方法的实现吧。
internal static void AddDelayedHttpSessionStateToContext(HttpContext context, SessionStateModule module) { context.AddDelayedHttpSessionState(module); } internal void AddDelayedHttpSessionState(SessionStateModule module) { if (this._sessionStateModule != null) { throw new HttpException(SR.GetString("Cant_have_multiple_session_module")); } this._sessionStateModule = module; } public static void AddHttpSessionStateToContext(HttpContext context, IHttpSessionState container) { HttpSessionState state = new HttpSessionState(container); try { context.Items.Add("AspSession", state); } catch (ArgumentException) { throw new HttpException(SR.GetString("Cant_have_multiple_session_module")); } } internal static void RemoveHttpSessionStateFromContext(HttpContext context, bool delayed) { if (delayed) { context.RemoveDelayedHttpSessionState(); } else { context.Items.Remove("AspSession"); } }
其中HttpContext的RemoveDelayedHttpSessionState就一句 this._sessionStateModule = null;我想对于SessionStateUtility里面的这几个方法我就不多说吧,很简单。
我们还是回头看看前面那2句吧,
public string GetSessionID(HttpContext context) { string id = null; this.CheckInitializeRequestCalled(context); if (this.UseCookieless(context)) { return (string) context.Items["AspCookielessSession"]; } HttpCookie cookie = context.Request.Cookies[Config.CookieName]; if ((cookie != null) && (cookie.Value != null)) { id = this.Decode(cookie.Value); if ((id != null) && !this.ValidateInternal(id, false)) { id = null; } } return id; }
默认情况下我们的cookie是可用的,这里的Config.CookieName实际上就是SessionStateSection的CookieName属性
服务器保存的id就是cookie value过后的Decode,其实现code 如下:
public virtual String Decode(String id) { // Need to do UrlDecode if the session id could be custom created. if (_isInherited) { Debug.Trace("SessionIDManager", "Decode is doing UrlDecode "); return HttpUtility.UrlDecode(id); } else { Debug.Trace("SessionIDManager", "Decode is doing nothing"); return id.ToLower(CultureInfo.InvariantCulture); } }
其中 _isInherited = !(this.GetType() == typeof(SessionIDManager));的取值。SessionIDManager的实例代码如下:
ISessionIDManager InitSessionIDManager(SessionStateSection config) { string sessionIDManagerType = config.SessionIDManagerType; ISessionIDManager iManager; if (String.IsNullOrEmpty(sessionIDManagerType)) { iManager = new SessionIDManager(); _usingAspnetSessionIdManager = true; } else { Type managerType; managerType = ConfigUtil.GetType(sessionIDManagerType, "sessionIDManagerType", config); ConfigUtil.CheckAssignableType(typeof(ISessionIDManager), managerType, config, "sessionIDManagerType"); iManager = (ISessionIDManager)HttpRuntime.CreatePublicInstance(managerType); } iManager.Initialize(); return iManager; }
[ConfigurationProperty("cookieName", DefaultValue="ASP.NET_SessionId")] public string CookieName { get { return (string) base[_propCookieName]; } set { base[_propCookieName] = value; } }
到这里大家应该知道为什么Http请求和返回关于Session对应Cookie的id是ASP.NET_SessionId了吧。不过大家要注意一点这里的SessionIDManager 在操作cookie做了一些数据验证处理,如果在特殊情况需要自定义验证规则我们可以自己来实现ISessionIDManager接口。这里我们可以看到第一次请求是没有sessionid的,所以sessionStateItem = this.GetSessionStateItem();这句代码不会执行,sessionStateItem默认为true,但是第二次请求时有sessionid这句代码就会执行。GetSessionStateItem()的实现这里我们就忽略了吧,这个方法设置一个SessionStateStoreData的实例 this._rqItem ,如果 this._rqItem为null则返回false。一般我们的Session都是可读写的。GetSessionStateItem方法主要是调用 this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
现在我们回到CompleteAcquireState方法中来:
if (flag)
{
SessionStateUtility.AddDelayedHttpSessionStateToContext(this._rqContext, this);
this._rqSessionState = s_delayedSessionState;
}
else
{
this.InitStateStoreItem(true); //SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);
}
这里是flag默认是false,里面具体判断就不说,InitStateStoreItem方法主要代码:
if (this._rqItem == null)
{
this._rqItem = this._store.CreateNewStoreData(this._rqContext, s_timeout);
}
this._rqSessionItems = this._rqItem.Items;
this._rqSessionState = new HttpSessionStateContainer(this, this._rqId, this._rqSessionItems, this._rqStaticObjects, this._rqItem.Timeout, this._rqIsNewSession, s_configCookieless, s_configMode, this._rqReadonly);
SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);
这里InProcSessionStateStore 的CreateNewStoreData方法实际就是调用SessionStateUtility.CreateLegitStoreData:
internal static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout) { if (sessionItems == null) { sessionItems = new SessionStateItemCollection(); } if ((staticObjects == null) && (context != null)) { staticObjects = GetSessionStaticObjects(context); } return new SessionStateStoreData(sessionItems, staticObjects, timeout); }
其中SessionStateItemCollection的定义如下:
public sealed class SessionStateItemCollection : NameObjectCollectionBase, ISessionStateItemCollection, ICollection, IEnumerable
这里创建了一个 HttpSessionStateContainer实例。我想大家到这里就应该明白我们的Session实际上就是一个HttpSessionStateContainer实例。
好现在我来看 Session.SessionID这个是怎么实现的
public string SessionID
{
get
{
if (this._id == null)
{
this._id = this._stateModule.DelayedGetSessionId();
}
return this._id;
}
}
而SessionStateModule的DelayedGetSessionId方法实现如下:
internal string DelayedGetSessionId()
{
this.ChangeImpersonation(this._rqContext, false);
try
{
this._rqId = this._idManager.GetSessionID(this._rqContext);
if (this._rqId == null)
{
this.CreateSessionId();
}
}
finally
{
this.RestoreImpersonation();
}
return this._rqId;
}
这里的CreateSessionId具体是怎么创建我就不说了吧,知道它是真正创建sessionid的就可以。而session的实际操作都是在ISessionStateItemCollection里面如HttpSessionStateContainer的Add方法:
public void Add(string name, object value)
{
this._sessionItems[name] = value;
}
而这里的_sessionItems实际上是this._rqItem.Items,本来想忽略_rqItem的创建,看来这个实例比较强啊。
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
if ((((this._rqItem == null) && !flag2) && (this._rqId != null)) && ((s_configCookieless != HttpCookieMode.UseUri) || !s_configRegenerateExpiredSessionId))
{
this.CreateUninitializedSessionState();
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
}
这里的CreateUninitializedSessionState方法实际就是调用this._store.CreateUninitializedItem(this._rqContext, this._rqId, s_timeout);
我们前面知道this._store这个可以取很多实例的,是SessionStateStoreProviderBase类型,这里我们也已默认的 InProcSessionStateStore(继承SessionStateStoreProviderBase)来说说吧,相关方法:
private SessionStateStoreData DoGet(HttpContext context, string id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { bool flag; string key = this.CreateSessionStateCacheKey(id); InProcSessionState state = (InProcSessionState) HttpRuntime.CacheInternal.Get(key); if (state == null) { return null; } ...... return SessionStateUtility.CreateLegitStoreData(context, state._sessionItems, state._staticObjects, state._timeout); } public override void CreateUninitializedItem(HttpContext context, string id, int timeout) { string key = this.CreateSessionStateCacheKey(id); SessionIDManager.CheckIdLength(id, true); InProcSessionState state = new InProcSessionState(null, null, timeout, false, DateTime.MinValue, NewLockCookie, 1); try { } finally { if (HttpRuntime.CacheInternal.UtcAdd(key, state, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, timeout, 0), CacheItemPriority.NotRemovable, this._callback) == null) { PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL); PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE); } } }
现在我们终于明白一个Sessionid对应一个SessionStateStoreData,所以它能区分不同的用户请求,这里的id就是我们前面的this._rqId了。
现在我们也总结一下吧,我们的HttpContext的Session属性实际上是一个HttpSessionStateContainer实例(HttpSessionStateContainer继承IHttpSessionState),而它数据成员都是保存在ISessionStateItemCollection实例中,每一次http请求我们都会去获取它的Sessionid,第一次请求sessionid问null,我们没有对应的SessionStateStoreData数据,这时我们在SessionStateModule的 InitStateStoreItem方法调用SessionStateStoreProviderBase的CreateNewStoreData方法来创建一个SessionStateStoreData实例,其中该实例有一个成员变量类型是ISessionStateItemCollection用来保存用户session的数据。同一个用户第二次请求我们能获取到它的sessionid,默认也能获取到SessionStateStoreData实例(session过期则取不到)。一个用户对应一个SessionStateStoreData,每个SessionStateStoreData里面有一个ISessionStateItemCollection实例用来保存用户数据,至于sessionid也就是用户身份的区别依赖于ISessionIDManager的实现。
前几天有人问我session过期处理流程是怎么样的。这里以InProcSessionStateStore为列来简单说明一下:
InProcSessionStateStore中有CreateUninitializedItem方法和SetAndReleaseItemExclusive方法,分别有HttpRuntime.CacheInternal.UtcAdd(key, state, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, timeout, 0), CacheItemPriority.NotRemovable, this._callback)
和 cacheInternal.UtcInsert(key, state2, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, state2._timeout, 0), CacheItemPriority.NotRemovable, this._callback);的方法调用
其中this._callback的赋值语句在Initialize方法中
public override void Initialize(string name, NameValueCollection config)
{
if (string.IsNullOrEmpty(name))
{
name = "InProc Session State Provider";
}
base.Initialize(name, config);
this._callback = new CacheItemRemovedCallback(this.OnCacheItemRemoved);
}
public void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason)
{
PerfCounters.DecrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
InProcSessionState state = (InProcSessionState) value;
if (((state._flags & 2) == 0) && ((state._flags & 1) == 0))
{
switch (reason)
{
case CacheItemRemovedReason.Removed:
PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ABANDONED);
break;
case CacheItemRemovedReason.Expired:
PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TIMED_OUT);
break;
}
if (this._expireCallback != null)
{
string id = key.Substring(CACHEKEYPREFIXLENGTH);
this._expireCallback(id, SessionStateUtility.CreateLegitStoreData(null, state._sessionItems, state._staticObjects, state._timeout));
}
}
}
现在我们来看看 this._expireCallback是什么东东
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
this._expireCallback = expireCallback;
return true;
}
而SetItemExpireCallback则在SessionStateModule类中调用
public event EventHandler End {
add {
lock(_onEndTarget) {
if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
_supportSessionExpiry = _store.SetItemExpireCallback(
new SessionStateItemExpireCallback(_onEndTarget.RaiseSessionOnEnd));
}
++_onEndTarget.SessionEndEventHandlerCount;
}
}
remove {
lock(_onEndTarget) {
--_onEndTarget.SessionEndEventHandlerCount;
//
if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
_store.SetItemExpireCallback(null);
_supportSessionExpiry = false;
}
}
}
}
其中SessionOnEndTarget的定义如下:
class SessionOnEndTarget { internal int _sessionEndEventHandlerCount; internal SessionOnEndTarget() { } internal int SessionEndEventHandlerCount { get { return _sessionEndEventHandlerCount; } set { _sessionEndEventHandlerCount = value; } } internal void RaiseOnEnd(HttpSessionState sessionState) { Debug.Trace("SessionOnEnd", "Firing OnSessionEnd for " + sessionState.SessionID); if (_sessionEndEventHandlerCount > 0) { HttpApplicationFactory.EndSession(sessionState, this, EventArgs.Empty); } } internal void RaiseSessionOnEnd(String id, SessionStateStoreData item) { HttpSessionStateContainer sessionStateContainer = new HttpSessionStateContainer( id, item.Items, item.StaticObjects, item.Timeout, false, SessionStateModule.s_configCookieless, SessionStateModule.s_configMode, true); HttpSessionState sessionState = new HttpSessionState(sessionStateContainer); if (HttpRuntime.ShutdownInProgress) { // call directly when shutting down RaiseOnEnd(sessionState); } else { // post via thread pool SessionOnEndTargetWorkItem workItem = new SessionOnEndTargetWorkItem(this, sessionState); WorkItem.PostInternal(new WorkItemCallback(workItem.RaiseOnEndCallback)); } } }
主要调用 HttpApplicationFactory.EndSession(sessionState, this, EventArgs.Empty);
其中主要的调用过程如下:
internal static void EndSession(HttpSessionState session, object eventSource, EventArgs eventArgs) { _theApplicationFactory.FireSessionOnEnd(session, eventSource, eventArgs); } private void FireSessionOnEnd(HttpSessionState session, object eventSource, EventArgs eventArgs) { if (this._sessionOnEndMethod != null) { HttpApplication specialApplicationInstance = this.GetSpecialApplicationInstance(); if (AspCompatApplicationStep.AnyStaObjectsInSessionState(session) || HttpRuntime.ApartmentThreading) { AspCompatSessionOnEndHelper source = new AspCompatSessionOnEndHelper(specialApplicationInstance, session, eventSource, eventArgs); AspCompatApplicationStep.RaiseAspCompatEvent(null, specialApplicationInstance, session.SessionID, this._sessionOnEndEventHandlerAspCompatHelper, source, EventArgs.Empty); } else { specialApplicationInstance.ProcessSpecialRequest(null, this._sessionOnEndMethod, this._sessionOnEndParamCount, eventSource, eventArgs, session); } this.RecycleSpecialApplicationInstance(specialApplicationInstance); } } internal HttpApplicationFactory() { this._sessionOnEndEventHandlerAspCompatHelper = new EventHandler(this.SessionOnEndEventHandlerAspCompatHelper); } private void SessionOnEndEventHandlerAspCompatHelper(object eventSource, EventArgs eventArgs) { AspCompatSessionOnEndHelper helper = (AspCompatSessionOnEndHelper) eventSource; helper.Application.ProcessSpecialRequest(null, this._sessionOnEndMethod, this._sessionOnEndParamCount, helper.Source, helper.Args, helper.Session); } 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;
}
那么这里的 public event EventHandler End 事件又是在哪里触发的了:
System.Web.Hosting.PipelineRuntime: InitializeApplication(System.IntPtr appContext) { ................ if (!HttpRuntime.HostingInitFailed) { // // On IIS7, application initialization does not provide an http context. Theoretically, // no one should be using the context during application initialization, but people do. // Create a dummy context that is used during application initialization // to prevent breakage (ISAPI mode always provides a context) // HttpWorkerRequest initWorkerRequest = new SimpleWorkerRequest("" /*page*/, "" /*query*/, new StringWriter(CultureInfo.InvariantCulture)); MimeMapping.SetIntegratedApplicationContext(appContext); HttpContext initHttpContext = new HttpContext(initWorkerRequest); app = HttpApplicationFactory.GetPipelineApplicationInstance(appContext, initHttpContext); } .......... } HttpApplicationFactory: internal static HttpApplication GetPipelineApplicationInstance(IntPtr appContext, HttpContext context) { _theApplicationFactory.EnsureInited(); return _theApplicationFactory.GetSpecialApplicationInstance(appContext, context); } private HttpApplication GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) { HttpApplication app = null; lock (_specialFreeList) { if (_numFreeSpecialAppInstances > 0) { app = (HttpApplication)_specialFreeList.Pop(); _numFreeSpecialAppInstances--; } } if (app == null) { // // Put the context on the thread, to make it available to anyone calling // HttpContext.Current from the HttpApplication constructor or module Init // using (new DisposableHttpContextWrapper(context)) { // If ran out of instances, create a new one app = (HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType); using (new ApplicationImpersonationContext()) { app.InitSpecial(_state, _eventHandlerMethods, appContext, context); } } } return app; } HttpApplication: internal void InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) { ....... Debug.Trace("PipelineRuntime", "InitSpecial for " + appContext.ToString() + "\n"); RegisterEventSubscriptionsWithIIS(appContext, context, handlers); ...... } private void RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) { RequestNotification requestNotifications; RequestNotification postRequestNotifications; ......... if (handlers != null) { HookupEventHandlersForApplicationAndModules(handlers); } // 1643363: Breaking Change: ASP.Net v2.0: Application_OnStart is called after Module.Init (Integarted mode) HttpApplicationFactory.EnsureAppStartCalledForIntegratedMode(context, this); // Call Init on HttpApplication derived class ("global.asax") // and process event subscriptions before processing other modules. // Doing this now prevents clearing any events that may // have been added to event handlers during instantiation of this instance. // NOTE: If "global.asax" has a constructor which hooks up event handlers, // then they were added to the event handler lists but have not been registered with IIS yet, // so we MUST call ProcessEventSubscriptions on it first, before the other modules. _currentModuleCollectionKey = HttpApplicationFactory.applicationFileName; .......... } private void HookupEventHandlersForApplicationAndModules(MethodInfo[] handlers) { ........ try { addMethod.Invoke(target, new Object[1]{handlerDelegate}); } catch { if (HttpRuntime.UseIntegratedPipeline) { throw; } } ....... } 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]; }
我想大家看到这里就知道调用End事件是HttpApplication类HookupEventHandlersForApplicationAndModules方法中的 addMethod.Invoke(target, new Object[1]{handlerDelegate}); 这句实现的,触发来源于PipelineRuntime的InitializeApplication方法,至于PipelineRuntime.InitializeApplication 我就不多说了,不过HookupEventHandlersForApplicationAndModules 中的handlers是在ReflectOnApplicationType中赋值,说白就是HTTApplication中自定义事件。
同样这里我们也说说Session start方法:
在SessionStateModule的Init-〉InitModuleFromConfig-〉 app.AddOnAcquireRequestStateAsync(new BeginEventHandler(this.BeginAcquireState), new EndEventHandler(this.EndAcquireState)); 中的BeginAcquireState-〉
if (sessionStateItem)
{
this.CompleteAcquireState();
this._rqAr.Complete(true, null, null);
}
在CompleteAcquireState()有
if (this._rqIsNewSession)
{
this.OnStart(EventArgs.Empty);
}
private void OnStart(EventArgs e) { this.RaiseOnStart(e); } private void RaiseOnStart(EventArgs e) { if (this._sessionStartEventHandler != null) { if (HttpRuntime.ApartmentThreading || this._rqContext.InAspCompatMode) { AspCompatApplicationStep.RaiseAspCompatEvent(this._rqContext, this._rqContext.ApplicationInstance, null, this._sessionStartEventHandler, this, e); } else { if (HttpContext.Current == null) { DisposableHttpContextWrapper.SwitchContext(this._rqContext); } this._sessionStartEventHandler(this, e); } } }
这里的_sessionStartEventHandler默认就是我们的Global中的Session_Start方法。
-----------------2017-2-11 最加
这里说一下Session_Start的方法调用的确与这里的this._rqIsNewSession 有关 但是该值的赋值实在CompleteAcquireState方法里面:
void CompleteAcquireState() { Debug.Trace("SessionStateModuleOnAcquireState", "Item retrieved=" + (_rqItem != null).ToString(CultureInfo.InvariantCulture)); bool delayInitStateStoreItem = false; Debug.Assert(!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId), "!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId)"); try { if (_rqItem != null) { _rqSessionStateNotFound = false; if ((_rqActionFlags & SessionStateActions.InitializeItem) != 0) { Debug.Trace("SessionStateModuleOnAcquireState", "Initialize an uninit item"); _rqIsNewSession = true; } else { _rqIsNewSession = false; } } else { _rqIsNewSession = true; _rqSessionStateNotFound = true; if (s_allowDelayedStateStoreItemCreation) { Debug.Trace("SessionStateModuleOnAcquireState", "Delay creating new session state"); delayInitStateStoreItem = true; } // We couldn't find the session state. if (!_rqIdNew && // If the request has a session id, that means the session state has expired s_configRegenerateExpiredSessionId && // And we're asked to regenerate expired session _rqSupportSessionIdReissue) { // And this request support session id reissue // We will generate a new session id for this expired session state bool redirected = CreateSessionId(); Debug.Trace("SessionStateModuleOnAcquireState", "Complete re-creating new id; redirected=" + redirected); if (redirected) { Debug.Trace("SessionStateModuleOnAcquireState", "Will redirect because we've reissued a new id and it's cookieless"); CreateUninitializedSessionState(); return; } } } if (delayInitStateStoreItem) { _rqSessionState = s_delayedSessionState; } else { InitStateStoreItem(true); } // Set session state module SessionStateUtility.AddHttpSessionStateModuleToContext(_rqContext, this, delayInitStateStoreItem); if (_rqIsNewSession) { Debug.Trace("SessionStateModuleOnAcquireState", "Calling OnStart"); OnStart(EventArgs.Empty); } } finally { if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest); } #if DBG if (_rqIsNewSession) { if (_rqId == null) { Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization"); Debug.Trace("SessionStateModuleOnAcquireState", "New session: session id reading is delayed"+ "\nReturning from SessionStateModule::OnAcquireState"); } else { Debug.Trace("SessionStateModuleOnAcquireState", "New session: SessionId= " + _rqId + "\nReturning from SessionStateModule::OnAcquireState"); } } else { Debug.Trace("SessionStateModuleOnAcquireState", "Retrieved old session, SessionId= " + _rqId + "\nReturning from SessionStateModule::OnAcquireState"); } #endif }
_rqItem 对象就是我们的SessionStateStoreData 实例,一般浏览器第一次请求这个值为null (客服端没有对应的ASP.NET_SessionId) 所以 _rqIsNewSession为true。但是最近遇到这样一个问题,访问一个web站点,该站点配置了2个端口,比如192.168.1.100:8081 和192.168.1.100:8082,先访问8081 然后在访问8082,在第一次访问8081的时候_rqIsNewSession为true,那么访问8081后第一次访问8082的时候发现_rqIsNewSession还是true。 于是就查看源码哦。 发现与_rqActionFlags参数有关。该参数在调用SessionStateStoreProviderBase的GetItemExclusive或GetItem方法被赋值。 在InProcSessionStateStore里面退码实际上调用:
SessionStateStoreData DoGet(HttpContext context, String id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { string key = CreateSessionStateCacheKey(id); // Set default return values locked = false; lockId = null; lockAge = TimeSpan.Zero; actionFlags = 0; // Not technically necessary for InProc, but we do it to be consistent // with SQL provider SessionIDManager.CheckIdLength(id, true /* throwOnFail */); InProcSessionState state = (InProcSessionState) HttpRuntime.CacheInternal.Get(key); if (state != null) { bool lockedByOther; // True if the state is locked by another session int initialFlags; initialFlags = (int)state._flags; if ((initialFlags & (int)SessionStateItemFlags.Uninitialized) != 0) { // It is an uninitialized item. We have to remove that flag. // We only allow one request to do that. // For details, see inline doc for SessionStateItemFlags.Uninitialized flag. // If initialFlags != return value of CompareExchange, it means another request has // removed the flag. Debug.Trace("SessionStateClientSet", "Removing the Uninit flag for item; key = " + key); if (initialFlags == Interlocked.CompareExchange( ref state._flags, initialFlags & (~((int)SessionStateItemFlags.Uninitialized)), initialFlags)) { actionFlags = SessionStateActions.InitializeItem; } } if (exclusive) { lockedByOther = true; // If unlocked, use a spinlock to test and lock the state. if (!state._locked) { state._spinLock.AcquireWriterLock(); try { if (!state._locked) { lockedByOther = false; state._locked = true; state._utcLockDate = DateTime.UtcNow; state._lockCookie++; } lockId = state._lockCookie; } finally { state._spinLock.ReleaseWriterLock(); } } else { // It's already locked by another request. Return the lockCookie to caller. lockId = state._lockCookie; } } else { state._spinLock.AcquireReaderLock(); try { lockedByOther = state._locked; lockId = state._lockCookie; } finally { state._spinLock.ReleaseReaderLock(); } } if (lockedByOther) { // Item found, but locked locked = true; lockAge = DateTime.UtcNow - state._utcLockDate; return null; } else { return SessionStateUtility.CreateLegitStoreData(context, state._sessionItems, state._staticObjects, state._timeout); } } // Not found return null; }
而我真是的codey用的是 RedisSessionStateProvider ,默认取值 actions = SessionStateActions.None;也就是说session 对应的cookieID 不存在或者session 过期后,会触发我们的Session_Start方法。