Click here to Skip to main content
15,891,184 members
Articles / Web Development / ASP.NET
Article

Using Attributes for encapsulating ASP.NET Session and ViewState variables

Rate me:
Please Sign up or sign in to vote.
4.67/5 (23 votes)
13 Apr 2005CPOL2 min read 95.1K   412   63   14
A generic and type-safe way to encapsulate ASP.NET Session and ViewState variables.

Introduction

In my first CodeProject article. I'll show how to encapsulate the access to objects stored in the Session, ViewState or Application collections.

Background

ASP.NET development is great. Using code-behind classes, user controls, datasets, and encapsulating the application business logic, ASP.NET development (almost) turned into pleasure.

But sometimes you find yourself in need to write code that reads and writes to the Session, ViewState or even the Application collection. I don't know about other developers, but I hate using non-typesafe collections like these.

That's when attributes and reflection come in handy - so you don't need to type all those casts.

This sample code relies on tagging fields of a WebForm using an attribute, and then subclassing the Page class to override the LoadViewState, SaveViewState, OnInit and OnUnload behavior.

Declaring the Metadata

We need to create an attribute, so we can mark the WebForm fields we want to be persisted.

C#
[AttributeUsage(AttributeTargets.Field)]
public class PersistFieldAttribute : Attribute
{
    PersistLocation loc;
    string key;

    public PersistFieldAttribute()
        : this(PersistLocation.Nowhere, null)
    {
    }
    
    public PersistFieldAttribute(PersistLocation location)
        : this(location, null)
    {
    }

    public PersistFieldAttribute(PersistLocation location, string key)
    {
        Location = location;
        Key = key;
    }

    public string GetKeyFor(MemberInfo mi)
    {
        return (Key != null ? Key + "_" + mi.Name : mi.Name);
    }
    
    public string Key
    {
        get { return key; }
        set { key = value; }
    }

    public PersistLocation Location
    {
        get { return loc; }
        set { loc = value; }
    }

    public static PersistFieldAttribute GetAttribute(MemberInfo mi)
    {
        return (PersistFieldAttribute) Attribute.GetCustomAttribute(mi, 
                            typeof(PersistFieldAttribute));
    }

    public static PersistFieldAttribute GetAttribute(MemberInfo mi, 
                            PersistLocation forLocation)
    {
        PersistFieldAttribute attr = GetAttribute(mi);
        return (attr != null && attr.Location == forLocation ? attr : null);
    }
}

I also added some helper (and type-safe) static GetAttribute methods.

Of course, we also need to declare the PersistLocation enumeration:

C#
public enum PersistLocation
{
    Nowhere     = 0x00,
    Context     = 0x01,
    ViewState   = 0x02,
    Session     = 0x04,
    Application = 0x08,
}

(YES, I like to explicitly declare the enumeration values)

Overriding System.Web.UI.Page

We'll need to override these four methods:

C#
using System;
using System.Reflection;
using System.Web.UI;

public class PageEx : Page
{
    const BindingFlags FieldBindingFlags = 
         BindingFlags.Instance|BindingFlags.NonPublic;

    protected override void LoadViewState(object savedState)
    {
        base.LoadViewState(savedState);
        
        foreach (FieldInfo fi in GetType().GetFields(FieldBindingFlags))
        {
            PersistFieldAttribute attr = 
                 PersistFieldAttribute.GetAttribute(fi, PersistLocation.ViewState);
            if (attr != null)
            {
                TrySetValue(fi, ViewState[attr.GetKeyFor(fi)]);
            }
        }
    }

    protected override object SaveViewState()
    {
        foreach (FieldInfo fi in GetType().GetFields(FieldBindingFlags))
        {
            PersistFieldAttribute attr = 
                  PersistFieldAttribute.GetAttribute(fi, PersistLocation.ViewState);
            if (attr != null)
                ViewState[attr.GetKeyFor(fi)] = TryGetValue(fi);
        }
        return base.SaveViewState();
    }

    protected override void OnInit(EventArgs e)
    {
        foreach (FieldInfo fi in GetType().GetFields(FieldBindingFlags))
        {
            PersistFieldAttribute attr = PersistFieldAttribute.GetAttribute(fi);
            if (attr != null)
            {
                switch (attr.Location)
                {
                    case PersistLocation.Application:
                        TrySetValue(fi, Application[attr.GetKeyFor(fi)]);
                        break;
                    case PersistLocation.Context:
                        TrySetValue(fi, Context.Items[attr.GetKeyFor(fi)]);
                        break;
                    case PersistLocation.Session:
                        TrySetValue(fi, Session[attr.GetKeyFor(fi)]);
                        break;
                }
            }
        }

        base.OnInit(e);
    }

    protected override void OnUnload(EventArgs e)
    {
        base.OnUnload(e);

        foreach (FieldInfo fi in GetType().GetFields(FieldBindingFlags))
        {
            PersistFieldAttribute attr = PersistFieldAttribute.GetAttribute(fi);
            if (attr != null)
            {
                switch (attr.Location)
                {
                    case PersistLocation.Application:
                        Application[attr.GetKeyFor(fi)] = TryGetValue(fi);
                        break;
                    case PersistLocation.Context:
                        Context.Items[attr.GetKeyFor(fi)] = TryGetValue(fi);
                        break;
                    case PersistLocation.Session:
                        Session[attr.GetKeyFor(fi)] = TryGetValue(fi);
                        break;
                }
            }
        }
    }
}

Using the attributes

That's the simplest part:

C#
public class MyCustomPage : PageEx
{
    [PersistField(Key = "custompage", Location = PersistLocation.Session)]
    protected DataSet ds;

    [PersistField(Location = PersistLocation.ViewState)]
    protected bool isLoaded = false;
    
    .
    .
    .
    
    private void Page_Load(object sender, EventArgs e)
    {
        if (ds == null)
            ds = new DataSet();
    }
}

How does it work?

When a new instance of your Page Handler (the class directly derived from your code-behind class) is created, its constructor is called. Just before it runs, the inline assignments you wrote are executed.

Our OnInit override runs just after the constructor and inline assignments. Then our LoadViewState override is called.

If your attribute isn't in the persistent collection (such as when the page is first run), it's simply not assigned. That's when your inline assignments come in handy: to specify a default value for the field.

After the execution of our loading overrides, you're ready to perform your work. Use the fields just like they were usual fields.

When the page unloads, the other overrides will store the field value in the persistent collection. And you're done!

Some Warnings

  • Try not to create objects in inline assignments. You can end up wasting an initialization, as the instance you created will eventually be replaced by the stored one.
  • Make sure you supply a key name when persisting fields to the Session or Application, so that you avoid annoying conflicts.
  • The fields are saved in the media in the format Key - underscore - Member Name.

Conclusion

I hope this article can be as useful for other developers as it's for me. Thank you.

History

  • April 04, 2005

    First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Brazil Brazil
Fábio Batista started programming in BASIC at 1989, using a CP400 machine. Currently he is running a company located in Porto Alegre, Brazil, called Suprifattus; and developing in C#, Java, C++ and any other thing that has a grammar.

Comments and Discussions

 
GeneralVery nice solution, makes viewstate bigger though Pin
Member 464839218-Dec-09 0:05
Member 464839218-Dec-09 0:05 
Generalsession problem Pin
naveen attri13-Jul-09 1:34
naveen attri13-Jul-09 1:34 
QuestionThe attribute is not working for my class?!? Pin
vatri9-Sep-08 4:28
vatri9-Sep-08 4:28 
AnswerRe: The attribute is not working for my class?!? Pin
vatri9-Sep-08 4:33
vatri9-Sep-08 4:33 
QuestionGreat solution. Where i can ... Pin
Daniel Junges12-Apr-08 3:28
Daniel Junges12-Apr-08 3:28 
GeneralRe: Great solution. Where i can ... Pin
cristianbg21-Apr-08 18:11
cristianbg21-Apr-08 18:11 
They are at the end of one of the downloded files (PageEx.cs):

void TrySetValue(FieldInfo fi, object val)
{
if (val != null)
fi.SetValue(this, val);
}

object TryGetValue(FieldInfo fi)
{
return fi.GetValue(this);
}
GeneralGreat concept Pin
Steven Berkovitz26-Nov-07 15:34
Steven Berkovitz26-Nov-07 15:34 
GeneralFor portuguese readers Pin
Fábio Batista24-Apr-05 9:09
Fábio Batista24-Apr-05 9:09 
GeneralI like! Pin
KiwiPiet20-Apr-05 18:14
KiwiPiet20-Apr-05 18:14 
GeneralRe: I like! Pin
Fábio Batista21-Apr-05 5:26
Fábio Batista21-Apr-05 5:26 
GeneralRe: I like! Pin
KiwiPiet21-Apr-05 20:20
KiwiPiet21-Apr-05 20:20 
GeneralRe: I like! Pin
Fábio Batista24-Apr-05 9:10
Fábio Batista24-Apr-05 9:10 
GeneralRe: I like! Pin
KiwiPiet24-Apr-05 12:01
KiwiPiet24-Apr-05 12:01 
GeneralRe: I like! Pin
monkeyboy2417-May-05 3:35
monkeyboy2417-May-05 3:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.