A couple of people seemed to like my attribute driven session management bits, so here's the final and ultimate solution for all of your session management desires in ASP.NET. Cross-roundtrip, cross-page, cross-session, cross-site-visit, cross-page-visit. The XML code comments are the docs, take it, use it, improve it, keep me out of trouble, give credit and otherwise: write less code.  (Just copy & paste from the browser window)

Update 2002-8-1, three quick notes: (a) yesterday's post didn't compile with copy & paste, because Radio isn't built for source code and did eat backslashes; (b) I added an explicit call to the event handler SessionStore() in Redirect & Transfer, so that the cross-page, cross-session and session state gets stored in these cases, too (that was a bug); (c) if you want to use this with user controls, you should be able to just flip the base class around and get control scope instead of page scope; you will get in trouble with sharing named items (cross-session, cross-page) across components of the same page by just using declarations at two places. On each page (even if it is a composite) there can only be exactly one place where a particular item can be stored. If you need to share fields across a composite, make an interface with accessor properties wrapping the state variables and share that interface. Also, using HTML frames with the persistent (cookie-based) things will be a problem, because the cookies have site scope; only use "PersistentPageState" on your main content frame and don't use this attribute, at all, on your navigation frames. 

Attributes rock!

   public class MyPage : StateManagingPage
    {
         [PersistentPageState] public int pageVisitsEver;
         [PersistentPageState("Visits")] public int siteVisitsEver;
         [SessionPageState] public int pageVisitsThisSession;
         [SessionPageState("Visits")] public int siteVisitsThisSession;
         [TransientPageState] public int roundtripsThisPage;
     
         // omissions...
         
         private void Page_Load(object sender, System.EventArgs e)
         {
                pageVisitsEver++;
                siteVisitsEver++;
                pageVisitsThisSession++;
                siteVisitsThisSession++;
                roundtripsThisPage++;            
         }    

-----------8<---------cut here ---------8<-------------- 

// Copyright (c) 2002, newtelligence AG. (http://www.newtelligence.com)
// All rights reserved.
// Author: Clemens F. Vasters (clemensv@newtelligence.com)
//
// Revision 1: Added cross-page, cross-session state


using System;
using System.Reflection
;
using System.Web
;
using System.Web.UI
;
using System.Runtime.Serialization
;
using System.Globalization;

namespace newtelligence.Web.UI
{
    /// <summary>
    /// Use this attribute to mark all protected or public
    /// fields that your want to auto-serialize into
    /// session state and which are scoped to the current
    /// conversation only.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    public class TransientPageStateAttribute : Attribute
    {
        /// <summary>
        /// Enables auto-serialization of this field into
        /// conversation (page scoped session) state. The
        /// data type must be serializable.
        /// </summary>
        public TransientPageStateAttribute()
        {
        }
    }

    /// <summary>
    /// Use this attribute to mark all protected or public
    /// fields that your want to auto-serialize into
    /// session state and which are scoped to the session.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    public class SessionPageStateAttribute : Attribute
    {
        internal string keyName = null;

        /// <summary>
        /// Enables auto-serialization of this field into
        /// session state (session scope). The data type
        /// must be serializable.
        /// </summary>
        public SessionPageStateAttribute()
        {
        }

        /// <summary>
        /// Enables auto-serialization of this field into
        /// session state (session scope). The data type
        /// must be serializable.
        /// </summary>
        /// <param name="KeyName">A site-unique key that enables
        /// sharing of this state element value across pages</param>
        /// <remarks>
        /// This variant of this attribute enables declarative
        /// sharing of session state between pages. Works with
        /// Redirects and Transfers.
        /// </remarks>
        public SessionPageStateAttribute(string KeyName)
        {
            keyName = KeyName;
        }
    }

    /// <summary>
    /// Use this attribute to mark all protected or public
    /// fields that your want to auto-serialize into
    /// a permanent cookie and which are scope across
    /// sessions.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    public class PersistentPageStateAttribute : Attribute
    {
        internal string keyName = null;

        /// <summary>
        /// Enables auto-serialization of this field into
        /// a persistent state cookie (user, page scope).
        /// The data type must be serializable.
        /// </summary>
        public PersistentPageStateAttribute()
        {
        }

        /// <summary>
        /// Enables auto-serialization of this field into
        /// a persistent state cookie (user scope). The data type
        /// must be serializable.
        /// </summary>
        /// <param name="KeyName">A site-unique key that enables
        /// sharing of this state element value across pages
        /// and sessions</param>
        /// <remarks>
        /// This variant of this attribute enables declarative
        /// sharing of session state between pages and across visits.
        /// Works with Redirects and Transfers.
        /// </remarks>
        public PersistentPageStateAttribute(string KeyName)
        {
            keyName = KeyName;
        }
    }

    /// <summary>
    /// This is a base class for your own Pages which implements
    /// automatic serialization of public and protected fields
    /// into session state or into cookies.
    /// </summary>
    /// <example>
    /// All you need to do is this:
    ///
    /// using newtelligence.Web.UI
    ///
    /// public class MyPage : StateManagingPage
    /// {
    /// ;     [PersistentPageState] public int pageVisitsEver;
    /// ;     [PersistentPageState("Visits")] public int siteVisitsEver;
    /// ;     [SessionPageState] public int pageVisitsThisSession;
    /// ;     [SessionPageState("Visits")] public int siteVisitsThisSession;
    /// ;     [TransientPageState] public int roundtripsThisPage;
    /// 
    /// ;     // omissions...
    /// ;     
    /// ;     private void Page_Load(object sender, System.EventArgs e)
    /// ;        {
    /// ;            pageVisitsEver++;
    /// ;            siteVisitsEver++;
    /// ;            pageVisitsThisSession++;
    /// ;            siteVisitsThisSession++;
    /// ;            roundtripsThisPage++;            
    /// ;        }    
    /// ;     
    /// }
    /// </example>
    /// <remarks>
    /// This should really be:
    ///
    /// template <class T>
    /// public class StateManagingPage : T
    ///
    /// but we don't have generics, yet. Sigh.
    /// </remarks>
    public class StateManagingPage : System.Web.UI.Page
    {
        private EventHandler PreRenderHandler = null;
        private const string keyPrefix = "__$stateManagingPages";

        /// <summary>
        /// Public constructor. Sets up events.
        /// </summary>
        public StateManagingPage()
        {
            Init += new EventHandler(this.SessionLoad);
            PreRender += PreRenderHandler = new EventHandler(this.SessionStore);
        }

        /// <summary>
        /// Event handler attached to "Page.Init" that recovers
        /// marked fields from session state
        /// </summary>
        /// <param name="o">Object firing the event</param>
        /// <param name="e">Event arguments</param>
        private void SessionLoad(object o, EventArgs e)
        {

            FieldInfo[] fields = GetType().GetFields(BindingFlags.Public|
                                                     BindingFlags.NonPublic|
                                                     BindingFlags.Instance);

            // Persistent, page scope values
            HttpCookie pageCookie = Request.Cookies[GetType().FullName];
            if ( pageCookie != null )
            {
                foreach( FieldInfo field in fields )
                {
                    if ( field.IsDefined(typeof(PersistentPageStateAttribute),
                                         true ) &&
                        field.FieldType.IsPrimitive )
                    {
                        PersistentPageStateAttribute ppsa =
                            (PersistentPageStateAttribute)
                            field.GetCustomAttributes(
                                typeof(PersistentPageStateAttribute),true)[0];
                        if ( ppsa.keyName == null && pageCookie[field.Name] != null )
                        {
                            field.SetValue(this,
                                Convert.ChangeType(pageCookie[field.Name],
                                field.FieldType,
                                CultureInfo.InvariantCulture));
                        }
                    }
                }
            }

            // Persistent, user scope values
            HttpCookie siteCookie = Request.Cookies[keyPrefix];
            if ( siteCookie != null )
            {
                foreach( FieldInfo field in fields )
                {
                    if ( field.IsDefined(typeof(PersistentPageStateAttribute),
                        true ) &&
                        field.FieldType.IsPrimitive )
                    {
                        PersistentPageStateAttribute ppsa =
                            (PersistentPageStateAttribute)
                            field.GetCustomAttributes(
                            typeof(PersistentPageStateAttribute),true)[0];

                        if ( ppsa.keyName != null && siteCookie[ppsa.keyName] != null )
                        {
                            field.SetValue(this,
                                Convert.ChangeType(siteCookie[ppsa.keyName],
                                field.FieldType,
                                CultureInfo.InvariantCulture));
                        }
                    }
                }
            }

            // Session scope values
            foreach( FieldInfo field in fields )
            {
                if ( field.IsDefined(typeof(SessionPageStateAttribute),true ) &&
                    field.FieldType.IsSerializable )
                {
                    SessionPageStateAttribute spsa =
                        (SessionPageStateAttribute)
                        field.GetCustomAttributes(
                        typeof(SessionPageStateAttribute),true)[0];

                    if ( spsa.keyName == null )
                    {
                        field.SetValue(this,
                            Session[field.DeclaringType.FullName+"."+field.Name]);
                    }
                    else
                    {
                        field.SetValue(this,
                            Session[keyPrefix+spsa.keyName]);
                    }
                }
            }

            if ( IsPostBack )
            {
                // Conversation scope values
                foreach( FieldInfo field in fields)
                {
                    if ( field.IsDefined(typeof(TransientPageStateAttribute),
                                         true ) &&
                        field.FieldType.IsSerializable )
                    {
                        field.SetValue(this,
                                       Session[field.DeclaringType.FullName+"."+field.Name]);
                    }
                }
            }
        }

        /// <summary>
        /// Event handler attached to "Page.Unload" that sticks marked fields
        /// into session state
        /// </summary>
        /// <param name="o">Object firing the event</param>
        /// <param name="e">Event arguments</param>
        private void SessionStore(object o, EventArgs e)
        {

            FieldInfo[] fields = GetType().GetFields(BindingFlags.Public|
                                                     BindingFlags.NonPublic|
                                                     BindingFlags.Instance);

            // Persistent state
            HttpCookie pageCookie = new HttpCookie(GetType().FullName);
            HttpCookie siteCookie = Request.Cookies[keyPrefix];

            if ( siteCookie == null )
            {
                siteCookie = new HttpCookie(keyPrefix);
            }
            siteCookie.Expires = pageCookie.Expires = DateTime.Now.AddYears(2);

            foreach( FieldInfo field in fields )
            {
                if ( field.IsDefined(typeof(PersistentPageStateAttribute),true ))
                {
                    if ( field.FieldType.IsPrimitive )
                    {
                        PersistentPageStateAttribute ppsa =
                            (PersistentPageStateAttribute)
                            field.GetCustomAttributes(
                            typeof(PersistentPageStateAttribute),true)[0];
                        if ( ppsa.keyName == null )
                        {
                            pageCookie[field.Name] =
                                (string)Convert.ChangeType(field.GetValue(this),
                                typeof(string),
                                CultureInfo.InvariantCulture);
                        }
                        else
                        {
                            siteCookie[ppsa.keyName] =
                                (string)Convert.ChangeType(field.GetValue(this),
                                typeof(string),
                                CultureInfo.InvariantCulture);
                        }
                    }
                    else
                    {
                        throw new SerializationException(
                            String.Format("Field '{0}' is not a primitive type",field.Name));
                    }
                }
            }

            if ( pageCookie.Values.Count > 0 )
            {
                Response.AppendCookie(pageCookie);
            }
            if ( siteCookie.Values.Count > 0 )
            {
                Response.AppendCookie(siteCookie);
            }

            // Transient & Session scope state
            foreach( FieldInfo field in fields)
            {
                if ( field.IsDefined(typeof(TransientPageStateAttribute),true ) ||
                    field.IsDefined(typeof(SessionPageStateAttribute),true ) )
                {
                    if (  field.FieldType.IsSerializable )
                    {
                        string fieldName;

                        fieldName = field.DeclaringType.FullName+"."+field.Name;
                        if ( field.IsDefined(typeof(SessionPageStateAttribute),true) )
                        {
                            SessionPageStateAttribute spsa =
                                (SessionPageStateAttribute)
                                field.GetCustomAttributes(
                                typeof(SessionPageStateAttribute),true)[0];
                            if ( spsa.keyName != null )
                            {
                                fieldName = keyPrefix+spsa.keyName;
                            }
                        }

                        Session[fieldName] = field.GetValue(this);
                    }
                    else
                    {
                        throw new SerializationException(
                            String.Format("Type {0} of field '{0}' is not serializable",
                                          field.FieldType.FullName,field.Name));
                    }
                }
            }
        }

        /// <summary>
        /// Private utility function invoked by Transfer and Redirect.
        /// Clears out all transient state of the current page.
        /// </summary>
        private void SessionDiscard()
        {
            // Discard transient page state only
            foreach( FieldInfo field in GetType().GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance))
            {
                if ( field.IsDefined(typeof(TransientPageStateAttribute),true ) &&
                    field.FieldType.IsSerializable )
                {
                    Session.Remove(field.DeclaringType.FullName+"."+field.Name);
                }
            }
        }

        /// <summary>
        /// Use this as a replacement for Server.Transfer. This
        /// method will discard the transient page state and
        /// call Server.Transfer.
        /// </summary>
        /// <param name="target"></param> 
        public virtual void Transfer( string target )
        {
            // ;store persistent session aspects, since PreRender isn't fired now
            SessionStore(null, null); 
            // ;and then discard the transient state
        SessionDiscard();
             Server.Transfer(target);
        }

        /// <summary>
        /// Use this as a replacement for Response.Redirect. This
        /// method will discard the transient page state and
        /// call Response.Redirect.
        /// </summary>
        /// <param name="target"></param>
        public virtual void Redirect( string target )
        {
            // ;store persistent session aspects, since PreRender isn't fired now
       SessionStore(null, null); 
       // ;and then discard the transient state
       SessionDiscard();

           Response.Redirect(target);
        }
    }
}

// Copyright (c) 2002, newtelligence AG. (http://www.newtelligence.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted provided
// that the following conditions are met:
// 
// (1) Redistributions of source code must retain the above copyright notice, this list
// of conditions and the following disclaimer.
// (2) Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior
// written permission.
// ;     
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


May 2003
Sun Mon Tue Wed Thu Fri Sat
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Apr   Jun

Navigation