Asp.net Session认识加强-Session究竟是如何存储你知道吗?

我们还是简单的来复习一下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事件是HttpApplicationHookupEventHandlersForApplicationAndModules方法中的 addMethod.Invoke(target, new Object[1]{handlerDelegate});这句实现的,触发来源于PipelineRuntimeInitializeApplication方法,至于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;
        }
 而我真是的code用的是 RedisSessionStateProvider ,默认取值  actions =  SessionStateActions.None ; 也就是说session 对应的cookieID 不存在或者session 过期后,会触发我们的Session_Start方法。  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值