Click here to Skip to main content
Click here to Skip to main content

Adding a ProcessContext to ASP.NET

, 11 Apr 2005
Rate this:
Please Sign up or sign in to vote.
A simple and reliable way to build context into a multi stage process in ASP.NET.

Introduction

ASP.NET is a fantastic platform for building web applications. One of the main services it provides is the ability to store/cache collected or useful data in memory.

ASP.NET supports scoping your cached data in many ways: at the Application level using Cache and Application objects, at the User level using the Session, for the duration of a specific user interaction with a Page using View State and finally for the duration of the request inside the Context.

Conceptually this looks a little like this:

Background

All of these are useful. However there does appear to be one conspicuous oversight. How do you store information between the chain of requests needed for a user to complete a multistage process?

There are lots of examples of multistage processes, for example:

  • A call center application in which a helpdesk employee (Dave B.) enters user issues when they phone the help desk. The whole issue capture process is done in three steps: on the first page Dave captures the user’s details, on the second he captures the details of the bug, and on the final page he captures any miscellaneous comments made by the user.
  • A Health and Safety application in which a health officer (Sally), captures information about workplace hazards. On the first screen Sally captures details for the hazard, on the second she captures her ideas and thoughts about how to mitigate the hazard and finally on the third screen she captures her action plan.

You might be thinking that the Session is a good candidate for storing the information captured in each step. However the Session is scoped to a User not to a specific User Process. What is wrong with that? Well, if you have a power user like Dave, who fancies himself as a bit of a PC expert, you’ll find you have users who like to multitask by entering issues simultaneously into multiple windows or tabs. If you used the Session for capturing this information, you could easily lose data as the windows corrupt each other’s data.

So what about ViewState? Well, ViewState has some significant limitations. Firstly, by default, your data must be serialized so it can be sent by the server with each response and returned by the browser with each request. Not all objects are serializable, so this puts limitations on what you can put in the ViewState. Also as the ViewState is sent with each request and response, if it gets too big, perceived performance will suffer. Dave and Sally would get annoyed really quickly if their application takes 5 seconds to load each page! Sure these limitations can be overcome if you hook into and modify the default behavior of ASP.NET ViewState; however this is not for the lighthearted.

A more unavoidable limitation of ViewState is that it is bound to a single page or control. Multistage processes often involve multiple pages, and unfortunately information can’t easily be transferred from the ViewState of one page to the ViewState of another.

So it seems we are lacking an appropriate place to store our information during a multi stage process. I think the solution is to create something called a ‘ProcessContext’. A ProcessContext is a framework you can use to easily create applications that keep users like Dave and Sally happy.

The key requirements of a ProcessContext are:

  1. Data put into the ProcessContext is stored on the server.
  2. ProcessContext can be transferred between pages.
  3. ProcessContexts are isolated from each other. For example, if a single user initiates two processes, data in one process will not corrupt the other.

Conceptually, this is how things would look if we had a ProcessContext:

Implementing a ProcessContext

The first step to create a ProcessContext is to create a way of uniquely identifying it. Let’s call this the ProcessID. Once we have a ProcessID we can store information for a particular ProcessContext keyed against that ProcessID. Where the ProcessContexts themselves are stored is not really important, all that is important is that we can get back to the right one in the future.

So let's define an interface called IProcessParticipant which can then be implemented by pages, controls or anything else that you can think of in the future.

The IProcessParticipant interface looks like this:

public interface IProcessParticipant
{
    string AttachProcessToUrl(string url);
    string ProcessID {get;set;}
    Hashtable ProcessContext{get;}
}

To actually implement a ProcessContext, I subclass System.Web.UI.Page and implement the interface in a class called ProcessParticipantPage. To do this we first override OnInit checking to see if a ProcessID has been handed to us via the QueryString.

protected override void OnInit(EventArgs e)
{
    ProcessID = Request.QueryString["ProcessID"];
    base.OnInit (e);
}

This allows a ProcessContext to be easily transferred between pages using the QueryString. In fact AttachProcessToUrl is used to take a URL and decorate its QueryString with the current ProcessID.

public string AttachProcessToUrl(string url)
{
    if (url.IndexOf("?") == -1) 
        url = url + "?";
    else
        url = url + "&";
    return url + string.Format("ProcessID={0}",ProcessID);
}

The ProcessID getter and setter use the ProcessID key in the ViewState of the page. If no ProcessID is found in the ViewState, then a new one is generated. This ensures a ProcessID is always available when required.

public string ProcessID
{
    get
    {
                
        if (ViewState["ProcessID"] == null)
            ViewState["ProcessID"] = Guid.NewGuid().ToString();
        return ViewState["ProcessID"] as string;
    }
    set
    {
        if (value == null) return;
        else ViewState["ProcessID"] = value;
    }
}

Notice that this implementation uses the ViewState to store the ProcessID as it is the simplest option available. The ProcessID is simply a GUID, which is of course not a lot to serialize between client and server, so it adds almost no overhead. An alternative would be to include a hidden field in the web form, i.e. in a similar way to the way ViewState works. A hidden field would be guaranteed to work in all situations, not just when ViewState is enabled. However for simplicity we are using ViewState, so we need to make sure ViewState is always available to our page.

public override bool EnableViewState
{
    get
    {
        return true;
    }
    set
    {
        if (value == false) throw new Exception("ViewState is Required");
    }
}

Now for clarity we store the information associated with a ProcessContext under the ProcessID key of the Session object, but you could easily change this to use the Cache object or even a database if you wanted. To ensure flexibility, we use a Hashtable to actually store information associated with a particular ProcessContext.

public Hashtable ProcessContext
{
    get
    {
        Hashtable results = Session[ProcessID] as Hashtable;
        if (results == null)
        {
            results = new Hashtable();
            Session[ProcessID] = results;
        }
        return results;
    }
}

We now have a complete working ASP.NET page class that can be used as a base class for all pages that participate in a process and need to share information with other pages in the process.

Using the ProcessContext

All you need do to enable the ProcessContext in your web page code is to simply change this line in your code:

public class YourPage: System.Web.UI.Page

to this:

public class YourPage: ProcessParticipantPage:

Once the ProcessContext is available, you simply use it like any hashtable, for example:

ProcessContext["YourKey"] = data;
string stored = ProcessContext["YourKey"] as string;
ProcessContext.Clear();

As you can see this is very simple, hopefully you will find it useful too.

To show you just how simple it is, I have created a simple ASP.NET application for Sally to capture her Health and Safety hazards, sorry Dave get your own programmer!

The application for Sally has three pages:

  • HazardDetails.aspx
  • Analysis.aspx
  • ActionPlan.aspx

    On HazardDetails.aspx in the btnNext event handler, we grab what is in the form and put it in the ProcessContext under the appropriate key. Then we attach the ProcessContext to the next page in the process (Analysis.aspx) and redirect to that URL.

    private void btnNext_Click(object sender, System.EventArgs e)
    {
        string title = this.txtTitle.Text;
        string severity = this.txtSeverity.Text;
        string description = this.txtDescription.Text;
        ProcessContext["title"] = title;
        ProcessContext["severity"] = severity;
        ProcessContext["description"] = description;
        Response.Redirect(this.AttachProcessToUrl("Analysis.aspx"));
    }

    We do much the same in the btnNext event handler of the Analysis page, capturing Sally’s ideas and thoughts. Finally on the last page, ActionPlan.aspx, when they hit btnSubmit, we capture the new plan and schedule from that page and grab what has already been put into the ProcessContext to submit the completed Hazard and Analysis data to the database in one step.

    private void btnSubmit_Click(object sender, System.EventArgs e)
    {
        string title = ProcessContext["title"] as string;
        string severity = ProcessContext["severity"] as string;
        string description = ProcessContext["description"] as string;
        string ideas = ProcessContext["ideas"] as string;
        string analysis = ProcessContext["analysis"] as string;
        string plan = this.txtPlan.Text;
        DateTime duedate = DateTime.Parse(this.txtDue.Text);
        InsertInDB(title,severity,description,ideas,analysis,plan,due);
    }

    Conclusion

    This application is trivial but it shows the power of the ProcessContext. If you play around with opening HazardDetails.aspx in two windows, you will see how what you enter into one window is isolated from the other. So should Sally ever become a power user, this application will be ready!

    In this article, I have demonstrated a simple way to add a ProcessContext to your ASP.NET applications; this can be useful in many situations, especially in Wizard like processes that can be run in multiple windows simultaneously by the same user. I hope you find this example helpful.

    Remember it’s all about Context!

    Update

    ASP.NET 2.0 will provide a feature called Cross Page Posting which allows you to post your web form to the next page in a sequence, this next page then has access to the PreviousPage from which you can access information from the previous form. This can help with Wizards but is still not as powerful as a full fledged ProcessContext.

    Update #2

    With Thomas Eyde and Hzi's suggestions I have created a standalone version of the ProcessContext that exposes a static property ProcessContext.Current from which you can assess the ProcessContext data.

    It works by looking for a ProcessID first on the QueryString, then on the Form for a hidden field called __PROCESSID and finally if nothing is found, it creates a new ProcessID and registers the hidden field to store the ProcessID between Postbacks if you haven't yet got the QueryString to contain the ProcessID.

    You can still ProcessContext.Current.AttachProcessToUrl as before, there is also a convenient ProcessContext.Current.Redirect method that does an AttachProcessToUrl and then a Response.Redirect.

    Additionally, if you are concerned about the size of what you are storing in the Session you can use ProcessContext.Current.Discard to free your memory.

    Now with this version, all you need to do is this:

    ProcessContext.Current["MyData"] = value; //Store info
    string myData = ProcessContext.Current["MyData"] as string; //Access
    ProcessContext.Current.Redirect("Page2.aspx");//Redirect the ProcessContext

    Perhaps a new article is in order?

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here

    Share

    About the Author

    Alex James
    Web Developer
    New Zealand New Zealand
    My name is Alex James, a software architect and developer from Auckland, New Zealand. I have 10 years of business experience working on and managing IT projects. My team and I recently decided to open source Base4.NET: a very exciting tool for creating integrated business applications.

    Comments and Discussions

     
    GeneralBug report Pinmemberzlyaksa29-Oct-08 23:17 
    GeneralNice article Pinmemberzlyaksa29-Oct-08 20:13 
    GeneralI can see the benefits but... Pinmemberworldspawn19-Apr-05 18:17 
    GeneralRe: I can see the benefits but... PinmemberAlex James19-Apr-05 18:56 
    GeneralRe: I can see the benefits but... Pinmemberworldspawn19-Apr-05 19:25 
    (Hey I meant to post this before u replied but my net connection failed at the critical moment, read as if i havent already read ur reply)
    Sorry my post sounds a bit more critical than I intended. Your code does solve some tough issues, I just find it hard to get past the expiring data issue.
     
    Ignoring the serialization issue, what if you had something that stored the data in mem for the usual period of time (20 minutes say) and then demoted it to the file system (or db) and then, after the process hit about 8 hours of age (or a day maybe) the process is completely erased.
     
    You could complexitise the process id generation system so it generated a key that indicated the time the process was started. Essentially it would be 2 parts, one part a unique id, the second part is the time the process was last requested. That way, when the process manager receives a request for a process (by its id) it can say, is this id more than 20 minutes old? If no return the process from the session state, if yes then retrieve it from the secondary store (file, db etc) and promote it back to mem.
     
    You could use the asp.net cache system to manage the demotion of process data.
     
    So what does this solve? Well your shiznit still expires true, but it lasts a lot longer than before and without dedicating memory to data that is not being used. At most your storing an id in the cache.
     
    [worldspawn]
    GeneralRe: I can see the benefits but... PinmemberDave Bacher21-Apr-05 4:00 
    GeneralRe: I can see the benefits but... Pinmemberworldspawn21-Apr-05 13:13 
    GeneralAnother approach PinmemberThomas Eyde12-Apr-05 12:31 
    GeneralRe: Another approach PinmemberAlex James12-Apr-05 12:49 
    GeneralRe: Another approach PinmemberThomas Eyde12-Apr-05 13:04 
    GeneralRe: Another approach PinmemberAlex James12-Apr-05 13:25 
    GeneralRe: Another approach PinmemberThomas Eyde12-Apr-05 14:02 
    GeneralRe: Another approach PinmemberAlex James12-Apr-05 16:00 
    GeneralRe: Another approach PinmemberAlex James12-Apr-05 16:32 
    GeneralData cleaning PinmemberHzi12-Apr-05 1:34 
    GeneralRe: Data cleaning PinmemberAlex James12-Apr-05 11:41 

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

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

    | Advertise | Privacy | Mobile
    Web02 | 2.8.140821.2 | Last Updated 11 Apr 2005
    Article Copyright 2005 by Alex James
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid