• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 5396
  • Last Modified:

HttpContext.Current.Session Object Reference Exception

I have an web application that I have inherited and I am trying to update.

When I try to use the application from <i>certain pages </i> (aspx.cs) and when the app tries to set a session variable in a class (.cs file as oposed to a aspx.cs page) I get an object reference not set to an instance of an object exception. This only happens from certain pages.

I.e.
<code>
HttpContext.Current.Session.Contents[ "mySessionVariable" ] = "hello";  // => returns object reference not set to an instance of an object
</code>
However if I am using some other pages that use the same .cs class I do not get this exception.

Can someone please explain why trying to create a session variable like this (above) will sometime throw an exception if being called by some pages and not when called by others?

I hope this is not too vague, I am trying to be percise without overloading you with unnecessary details.

I will be glad to explain more if necessary.


0
Sling_Blade
Asked:
Sling_Blade
  • 10
  • 10
1 Solution
 
AGBrownCommented:
Change
HttpContext.Current.Session.Contents["key"]
to
HttpContext.Current.Session["key"]

Does that work now?

Andy
0
 
AGBrownCommented:
Sorry if that was a little short - my keyboard stopped working, so I just finished it off and hit return.

HttpSessionState.Contents is provided for backwards compatibility with ASP, so its best to try and use the indexer straight from Session instead of Session.Contents in new code. Contrary to what I said, I don't think that will fix your problem though, as it just wraps the indexer and does nothing else.

So, if I get this right, you have a code file "foo.cs" with a class that has a method such as:
public class Foo {
     public void PutItInSession(key, value)
     {
          HttpContext.Current.Session.Contents[key] = value;
     }
}

and in your pages you have code that does:
     ...
     Foo myFoo = new Foo();
     myFoo.PutItInSession("example key", "value");

This code works in some pages, but in others the exact same code doesn't work, is that right? Which version of .NET is this running in?

Andy
0
 
Sling_BladeAuthor Commented:
To confirm; I have remove .Contents and your right it doesn't change anything (thanks for the tip though).

I am running version 1.1.

What is happening is this app acts as the back end of a shopping cart, it processes credit cards to the bank and returns the results of the transaction to the users website.

How this works is our clients will call our application when a user want to finalize the purchase (via in putting card details and actually purchasing).

We then scrape the users shopping cart (the retailers who use our service) and display the copied cart to their customer (the purchaser who is trying to buy something from their website) In this way the transaction is happening over our secure server but it looks to the user like they are still at the retailers website.

We process the transaction and pass the results back to the user.

During this phase I am able to set the session variable in the processTransaction.cs class.

<Here is where the trouble lies>
The retailers can logon to our website and check certain details of the transactions, more importantly they can also perform bulk transactions.

It is during the bulk transactions where I can't set the session variable (more to the point I can't access the session object as I get a null reference exception).

Bulk transactions and regular one off transactions performed via the end customer (see above) both use the same purchasing class, <processTransaction.cs>. It is in this class where I am trying to set the session variable.

To summaries, it is during bulk transaction where I get the Object Reference exception but for the end customer I can set the session variable.

The only difference I can see is that for bulk transactions the retailer has logged on to our server via a web interface (our website). And the end customer has been transferred to us from the retailer’s website.
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
AGBrownCommented:
Cool, thanks for that. It doesn't give me any immediate ideas, but there are possibilities. I'm going to throw quite a few things down, so I'll number them and if you can give me answers (short or long doesn't matter) it may either get us to the solution, or trigger something for you.

1) Is the retailer "bulk transaction" website (I'll call it "retailer portal"), and the customer page which is incorporated into the retailer's own site (I'll call it "customer portal") run in the same .NET application?
-I suspect they may not be as you are getting different behaviour for each.
2) Does the null reference exception happen on every line with a session access, or just the same line each time? The second part of this is: is the exception consistent or does it happen only sometimes on a given line, and other times that line succeeds?
-If the answer is every line, and the answer to 1 is "different applications" then that suggests an issue with the session state configuration of the retailer portal. I don't think this is very likely though. If the failure of a given line is not consistent then there is a different problem.
3) What is the exact line of code for one of the lines that gives the error, and what is the full stack trace and inner exception details of that exception?
4) What is the session state mode of the (or both the) applications (it is set in the web.config sessionState section). Are you using a third party sessionstate tool to run session state?
5) Is the application hosted on a web farm, garden, or just a single process on a single server (and is it Windows server 2000 or 2003)?
6) How is authentication done for the retailer portal, and for the customer portal. I presume the retailer portal is forms authentication? How about the customer portal?
7) What do you mean by a "bulk transaction" - does that use different code, or just multiple instances of the same processTransaction class?

Andy
0
 
Sling_BladeAuthor Commented:
Great questions.
I was in the middle of answering question 2 when I discovered it is only when I actually <i>enter<\i> the bulk transactions class that I can no longer access the HttpContext.Current.Session object.

I then realised that the bulk processing module actually starts a new thread for each bulk transaction in its initilizer.

Could it be that this is what is killing the ability to create a session object?

0
 
AGBrownCommented:
Yup, that's it. You can't access HttpContext.Current.Session from a new thread spawned from your executing thread. Deep inside the framework, there is a bit that gets the session object, and it does "Thread.CurrentThread.GetBlah()"; (not GetBlah, but some such thing) to get the session object. Now, I'm not sure, but I'm guessing that that is the problem.

Anyway, to re-enforce that, I managed to reproduce it in about 2 minutes:
1) I made a page with a textbox and a button. In the button handler I put the following:
            private void Button1_Click(object sender, System.EventArgs e)
            {
                  if (Session["Counter"] == null)
                  {
                        //lock(processTransaction.Instance.SyncRoot)
                        //{
                        //      processTransaction.Instance.Session = this.Session;
                        //}
                        Session["Counter"] = Int32.Parse(this.TextBox1.Text);

                        ThreadStart myDelegate = new ThreadStart(processTransaction.Instance.IncrementCounter);
                        Thread t = new Thread(myDelegate);
                        t.Start();
                        Thread.Sleep(100);
                        while (!processTransaction.Instance.Complete)
                        {
                              Thread.Sleep(100);
                        }
                        t.Join();
                        Server.Transfer("Result.aspx");
                  }
            }
Note the commented out bits, they are the fix for this that I'll give you.

2) I made my "processTransaction" class (which happens to be a singleton, but on the off-chance you don't know what a singleton is, don't worry neither do I ;). No, really, its just a single instance of a class, which means I can simulate a threaded instance of your processTransaction class):
      public class processTransaction
      {
            //public HttpSessionState Session;
            //public readonly object SyncRoot = new object();
            public bool Complete = false;
            public static readonly processTransaction Instance = new processTransaction();
            private processTransaction() {}
            public void IncrementCounter()
            {
                  System.DateTime dtFinish = DateTime.Now.AddSeconds(1d);
                  while (DateTime.Now < dtFinish)
                  {
                        Complete = false;
                  }
            //      lock(this.Session)
            //      {
            //            this.Session["Counter"] = (int)this.Session["Counter"] + 1;
            //      }
                  HttpContext.Current.Session["Counter"] = (int)HttpContext.Current.Session["Counter"] + 1; <!-- Null reference exception occurs here
                  Complete = true;
            }
      }
3) And finally I made a Result.aspx page, which displays the contents of Session["Counter"] in a label in the page_load:
            private void Page_Load(object sender, System.EventArgs e)
            {
                  this.Label1.Text = Session["Counter"].ToString();
            }

If you run this, with the commented out lines as they are (commented out), sure as eggs is eggs I get your NullReferenceException on the line I've indicated.

So, the fix:
If you understand it already, just uncomment the lines that are commented, and comment out the line which is marked as throwing the exception. I'll not paste all the code again, just the relevant part of processTransaction:
            ...
                  lock(this.Session)
                  {
                        this.Session["Counter"] = (int)this.Session["Counter"] + 1;
                  }
            //      HttpContext.Current.Session["Counter"] = (int)HttpContext.Current.Session["Counter"] + 1; <!-- Null reference exception occurs here
                  Complete = true;
            }

My surmised explanation:
It looks like the new thread can't access our HttpSessionState object through HttpContext.Current.Session. I suspect that it isn't actually the Session object that its failing on, but HttpContext.Current, which tries to pull the current HttpContext object out of the current thread. The Session property then simply gets an object out of a dictionary, so that should be cross-thread accessible. "Current", by the way, is a static property similar to Instance in that it directs access to a particular instance of an object. The difference here is that instead of that instance being particular to an entire AppDomain, it looks like this is thread-specific, which is where our problem seems to lie.

So, what we want to do is pass the relevant HttpSessionState instance to the processTransaction class so that it can use it in the other thread. I do this by setting the Session field equal to this.Session before starting the new thread. Best practice note: make the processTransaction.Session a set-only property for real-life code, which has a private backer variable. Better still, you could maybe force passing HttpContext.Current.Session (or Page.Session as this.Session from the page) in the constructor, or just put this.Session = HttpContext.Current.Session in the private constructor of the processTransaction class, making Session private too. That would be down to personal preference.

Anyway, once Session has been set, the processTransaction instance can access the relevant HttpSessionState object from whichever thread its on, so the code now works. I have put in locking on processTransaction.SyncRoot as well, just to indicate what you might do if you had a more complex situation that needed it.

So, with the commented code back in play, and the exception line commented out, this now works. We get passed to Result.aspx, and it shows a result of "2" in the label.

Notes for real-life:
You can't obviously implement processTransaction as a singleton, as you could have multiple retailers online at any give time, each accessing a different HttpSessionState instance. In this case, you would have to do something a little scary. You would store your processTransaction instance in the Session (presumably you are doing this already as you are multi-threading, that implies you are using an asynchronous class to do the transaction while you display a wait page to the user which keeps checking back for the status of that class). You would then also put a reference to that Session inside the instance of the processTransaction class. As such, each Session could therefore have as many processTransaction instances as it needed, and they would all contain a reference to the HttpSessionState object that held them.

Does that make sense? Its a bit late here, so I hope its not too long-winded. Even if I wasn't clear enough, that is definitely your problem, this code both reproduces it and fixes it.

Andy
0
 
AGBrownCommented:
Actually, in terms of best-design, I think we can take this a stage further. You may have all this covered, which is why I wacked it into a separate post.

I don't like Session for the reason that it's unreliable for most things. Without going into too much detail, Session is basically a thinly-veiled Cache object with a sliding expiry - a state bag on the server which disappears a given number of minutes (default 20) after the user last posted back. It stores (user+application)-instance wide information. In actual fact a user can spawn multiple pages that all access the same HttpSessionState object, so if you have some kind of sequential process on a fixed object that is stored in Session with a key that never changes, you can get in a real mess. That is more and more of a reality with users that have multi-tab browsers and want to do other things while a long running process is going, so spawn a new tab for the same session. Many people seem to use SessionState when they really want ViewState, or ApplicationState instead.

So we need to loosen our class coupling. I would suggest therefore that:
1) processTransaction should _never_ touch the HttpSessionState directly
2) The class that kicks off the processTransaction method on the new thread should do so by passing the session variables that processTransaction needs either as properties before the thread starts, or better still as parameters of the method.
3) Once going, the session objects that processTransaction is using should be read-only. This is hard, so even better they should be unavailable. If they are not there then an object asking for them should know to see if there is a processTransaction instance underway, and act appropriately. This stops the user going back to the "go" page, and starting a second processTransaction bulk transaction on the same objects.
4) processTransaction is able to complete its operation, and never report back to the user if the user goes away/gets bored.
5) processTransaction should be able to report the status of its operation if the user wants to know, pass results to any class who needs them once completed, or cancel the operation if the user needs to/wants to/has to/gets bored.

So I would:
-Create an instance of processTransaction. Put it into the HttpSessionState of the page using this.Session on the page, but store it with a unique key (System.Guid.NewGuid()).
-Set the thread running. Either pass the objects it needs in parameters to the method, or as properties before starting the new thread. Most importantly, remove the objects that have been passed to processTransaction from Session so that any other class that tries to modify them can't, and you don't end up with synchronisation problems.
-Pass the session key you used to the status page (maybe in a hidden field, or query string in the refresh meta tag, such as <meta http-equiv="REFRESH" content="1;URL=Wait.aspx?Guid" />)

Now processTransaction doesn't need to access Session. In addition, it will act on the objects that it was given without them being updated by another page in the same session while it is doing its long running transaction.

processTransaction should then be programmed:
-with the ability to report the progress of its long running transaction,
-so that if it is completed, it should return the original/modified objects for use by the calling page,
-so that if the user hits a "cancel" button, it will rollback its transaction,
-so that if the method has been started once, it can't be started again.

All the above depends on a lot of things that are specific to your application, but they are things that I would consider implementing if at all possible. If you like to think of it as such, it is a disconnected (web) approach to the event-based asynchronous pattern.

Andy
0
 
Sling_BladeAuthor Commented:
Brilliant!

You've answered my question and I've learned enough information to begin to understand where possible some other very significant bugs are coming from.

I need time to digests this and to put it all together with how the author of this application is using session states.

I will get back to you, I just didn't want to leave you hanging without an explanation.

Again, brilliant!

0
 
AGBrownCommented:
Good luck.
0
 
Sling_BladeAuthor Commented:
The reason I am upgrading the site is on occasion transactions are going to the wrong bank account! It happens only occasionally but once is too much and it happened again this week.

To process a transaction the app need two separate pieces of info. First it needs the credit card details (amount, card number ect) and secondly it needs the merchant details so it knows whose account to give the money too. It puts these two together and sends to the bank for processing.

I now realize that when the merchant first logs on to the site, the application is storing the merchant account details in a session object. Then during the bulk transaction a new thread is spawned and the app is reaching back into the original thread to grab the session object that contains the merchant data.
<code from newly spawed thread; processTransaction>
merchantAccountData=(MerchantAccountData)Session["merchantAccountData"];

Does my theory sound right, by reaching back to the original thread to grab the merchant details from a session object, the app is grabbing the wrong session object (ie the wrong merchant account data)?

I don't know enough about session objects and threads to say for sure; how does a new thread know anything about the session object from the original thread?
0
 
AGBrownCommented:
Well, in the test code I had (posted above, somewhere) it appeared that I couldn't access Session from a newly spawned thread. Does your code actually manage to access Session then? I thought it didn't due to your original error. Or does your code above refer to the fix, where we set the HttpSessionState to a Session property of processTransaction?

Well, you certainly seem to have a problem where you are crossing information between sessions. It is possible that it comes from this. I wouldn't rule out other possibilities though. The second part to my answer, which talked about not giving processTransaction access to Session at all would help with this if this is the problem.

I think I have just understood something though. Is the original application an asp application that you are now upgrading to asp.net?

Andy
0
 
Sling_BladeAuthor Commented:
Sorry for the confusion, I meant to tell you that I changed the code and the orginal processTransaction is a aspx.cs page and my processTransaction is just a .cs class.

This will be why (in my last post) I am accessing the session object in the new thread.

I am going to do like you suggest and create a wrapper class for my processTransaction and put my session varialbles in private variables in the top of the wrapper. Then the new thread, spawned from inside the wrapper, can access the private variables.

Hopefully this will sort it out.

I'm also putting in a couple of MSMQ's to help sort out the misdirected accounts.

But I'll leave that story for another day :-)

Mate you have earned your points!
0
 
Sling_BladeAuthor Commented:
One thing before you go, how can I report back to the calling page from my wrapper class that is performaing a long running process?
0
 
AGBrownCommented:
I see, I think that will at least sort out the session access from processTransaction.

As for reporting back, well, that shouldn't be too hard. I briefly laid out a scenario in the post you accepted as the answer.
-Create your instance of processTransaction,
-Store it in the Session state (no, I'm not kidding). You'll store it with a key created with Guid.NewGuid().
-You can't easily "tell" the page on the client that you are done, instead the client has to keep checking back, so:
-Give that Guid key to a page that refreshes itself on a regular basis by doing:
    HttpContext.Current.Items.Add("guid", <the guid>); // this puts the guid into the context, so that wait.aspx can pick it up after the internal transfer
    Server.Transfer("Wait.aspx");
-In the Page_Load of the Wait page do:
if (!IsPostback)
{
    if (HttpContext.Current.Items.Count > 0)
        // then we are opening this page for the first time, so we tell the page to refresh itself using the meta tag <meta http-equiv="REFRESH" content="5;URL=Wait.aspx?job=<insert guid string here>" />) in the header. How you insert this is up to you.
-Each time that that page refreshes, it will cause another !IsPostback in the Page_Load, except this time HttpContext.Current.Items.Count will be 0, so we would then do:
    (processTransaction)Session[Guid.NewGuid(QueryString["job"])]
-Finally, you can access some property of processTransaction which indicates the progress, and report it on the page accordingly.

For a really robust solution, I would maybe submit jobs to a job server which is written outside ASP.NET, or at the least log job status in a sql server database so that the retailer can go off for a day and come back and check the job status long after the session has died and that processTransaction instance went with it. But that's a whole other issue ;)

Does that help? That isn't a plugnplay implementation, you'll have to work out how to write the guid into the meta tag, and there will be cast issues somewhere I'm sure, but that is more or less how I would do it.

Andy
0
 
Sling_BladeAuthor Commented:
I will be sending all transaction the a Queue and I am implimenting a service that listens for the MSMQ and will process the transactions one at a time.

Along with the other ideas you've helped me with, this should give it more of a robust "disconnected (web) approach".

Thanks for all of your help.

You've been great!
0
 
AGBrownCommented:
That sounds like a good piece of work, hope its all worth it in the end!

Can I ask another question? Did the crossover of retailer information happen in your new implementation, or in the old one. Presumably the old one wasn't using threading to do this?

Andy
0
 
Sling_BladeAuthor Commented:
The crossover happens in the old one, which is the main reason why I'm updating the application.

It's the old app that is using new threads to do the processing of transactions while at the same time it is reaching back to the orginal thread to grab the merchant data (this is where I am assuming the mix-up is happening).

I am still going to call a new thread to do the work but it will be in the service. The plan is for each instance of the app to put a message containing the transaction details in a MSMQ named "transaction" (not transactional). It will also place a GUID as the label of each transaction to uniquely identify it.

The processing service will be watching the transaction queue and when a message arrives it will take the message and process the transaction. The service will then take the results of the transaction and place it in the results queue and it will label it with the same GUID.

The calling instance (of the website/application) will poll the results queue looking for the GUID of the message that it passed to the processing queue. When it finds the GUID It will then take the results and update the user.

Hopefully this will allow me to tell if all/which of the transactions have been processed encase of a catastrophic failure during any long processing.

That’s the plan anyway. It looks complicated but it's disconnected and I like the ability to tell which transactions have been processed.

0
 
Sling_BladeAuthor Commented:
New Question:
<quote>
if (HttpContext.Current.Items.Count > 0)
        // then we are opening this page for the first time ...
</quote>

Why is it there is something in the HttpContext.Current.Items repository when we first open the Wait.aspx page?

I understand why when we refresh the page Current will have no items as HttpContext.Current.Items stores information per request but I could use a little understanding with what items are present in the first request.

Thanks
0
 
AGBrownCommented:
Just because of this:
    HttpContext.Current.Items.Add("guid", <the guid>); // this puts the guid into the context, so that wait.aspx can pick it up after the internal transfer
which the previous step did. When you then do Server.Transfer to Wait.aspx, you are still maitaining the current HttpContext, and so the Items are still stored. As soon as you return to the client, you loose them. See http://msdn.microsoft.com/msdnmag/issues/03/04/ASPNETUserState/ for more info.
0
 
Sling_BladeAuthor Commented:
Cheers, thanks again.
0
 
navyjax2Commented:
Actually, I found that you could spawn a thread within a Default.aspx.cs and it still has access to Session variables.  To duplicate, create a button, then use a Button1_Click() event to launch a function that sets a Session variable to "", then start the thread:

    protected void Button1_Click(object sender, EventArgs e)
    {
        initSession(); // where I set my Session variables to "", then can give them a value if I want
      // Set up timer refresh process for progress bar
        // and perform database extraction to CSV
        Timer1.Interval = 250;
        Timer1.Enabled = true;

        Thread objThread = new Thread(new ThreadStart(db2csvExport));
        objThread.Start();
        Session["Thread"] = objThread;
    }

If you take that thread process and retrieve a Session variable, it will work - do debugging and set a breakpoint on the line in this function and it will get populated if you do so in initSession():

    protected void db2csvExport()
    {
        string iAmAVariable = (string)Session["iAmAVariable"];
   }

The problem lies when you try to retrieve a Session variable from another .cs file that has the same namespace, but another class and doesn't inherit the same context.  I never figured out how to make HttpContext.Current not null because of this, even after putting pages enableSessionState=true in my web.config, using System.Web in my namespaces, inheritanting Page or IRequiresSessionState or doing a "this.Session" or "this.Page", it doesn't have the context to know the session, because it's a .cs file that isn't attached to an ASPX page, is my belief...  Never figured out the real reason.  

Glad this mod worked, but I don't think it was needed if everything was kept within Default.aspx.cs.  But then I didn't read everything here in grave detail, either, just giving some observations.

-Tom
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

  • 10
  • 10
Tackle projects and never again get stuck behind a technical roadblock.
Join Now