Session variables are not unique for each user??

Topics: Troubleshooting
Jan 3, 2011 at 2:22 PM

Dear forum,

 

I have a very serious problem:

I have made an ascx that stores some information in the Session state:

HttpContext.Current.Session["myvar"] = myObject;

 

The problem is, that every user on the page seems to SHARE the same session (??).

This is a huge problem since users will overwrite the content of each other sessionvariables.

 

Also it seems that the session is very slow. When putting something in the SessionState variable - it takes 30-60 seconds before I can read the variable.

 

I have tried my ascx on a simple site on the same server - not composite, but just a plain .Net 4.0 og IIS 7.5. Here the session variables are private to each session as they should be. And there is no delay putting something into the session variables.

 

Please help me. This is completely destroying my usage of Composite C1... :-(

Jan 3, 2011 at 2:25 PM

Wow, that sure seems like a very serious problem. Is it possible for you to paste all of your usercontrol here or maybe send it to per. email, and i will take a look. (pst at punktum dot gl)

Jan 3, 2011 at 2:45 PM

Thank you for answering!

Of course I will provide the code.

I will have to simplify the control to keep the complexity down. My real ascx uses a DAL and BLL to work.

I will recreate the problem using a very simple ascx and be back in a few minutes.

Coordinator
Jan 3, 2011 at 2:45 PM

I expect this is page output caching that is at play here.

Try turning of ASP.NET Full Page Caching - edit ~/Renderers/Page.aspx and delete the cache directive.

Jan 3, 2011 at 3:09 PM

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Dummy.ascx.cs" Inherits="Inface.WebShopControls.Dummy" %>
<div>
  <h2>
    Dummy demo</h2>
    <h3>
		Please fill in the form below to login</h3>
        Email
        <asp:TextBox ID="tbxEmail" runat="server" CssClass="loginBox"></asp:TextBox>
		<asp:Button ID="btnLogin" runat="server" Text="Login" OnClick="btnLogin_Clicked"  />
    <asp:Button ID="btnLogout" runat="server" Text="Logout" OnClick="btnLogout_Clicked"  />
    <div>
      <asp:Literal ID="litLoginResult" runat="server" />
    </div>

</div>

Code behind

 

using System;

namespace Inface.WebShopControls {
  public partial class Dummy : System.Web.UI.UserControl {
    protected void Page_PreRender(object sender, EventArgs e) {
      litLoginResult.Text = (string)Session["LOGIN"];
    }

    protected void btnLogin_Clicked(object sender, EventArgs e) {
      Session["LOGIN"] = tbxEmail.Text;
    }

    protected void btnLogout_Clicked(object sender, EventArgs e) {
      Session["LOGIN"] = "";
    }
  }
}

 

 

The result: A user types something in the tbxEmail - it will show at the litLoginResult.Text.

But when reloading the page, it will not be shown. After 45-60 seconds it will show up. Then it will show up for every user.

 

When making this simple edition - I began to suspect cache as well. And the answer from mawtex has shown to be correct.

Even though, I will post the entire code for the sake of the discussion to be usable for other users as well. :-)

 

Jan 3, 2011 at 3:12 PM

Hi,

You are absolute correct!

This IS the issue.

 

Information from the database accessible only for a logged in user, was shown to all users (even those, not logged in). The problem was a common cache!

 

I wonder if there is any way to still use the cache, but avoid caching of User Control content? Anyone?

 

Thank to both of you (burningice and mawtex). This saved my day!

Coordinator
Jan 3, 2011 at 4:03 PM

>> I wonder if there is any way to still use the cache, but avoid caching of User Control content? Anyone?

Yes, there is. You can make user id a part of cache key, so different users will have different cache entries (http://msdn.microsoft.com/en-us/library/5ecf4420.aspx)

The quickest way to do it is to edit global.asax. 

Find

    public override string GetVaryByCustomString(HttpContext context, string custom)
    {
        return ApplicationLevelEventHandlers.GetVaryByCustomString(context, custom) ?? base.GetVaryByCustomString(context, custom);
    }
Replace with
    public override string GetVaryByCustomString(HttpContext context, string custom)
    {
        var result = ApplicationLevelEventHandlers.GetVaryByCustomString(context, custom);
       if(result == null) {
           return  base.GetVaryByCustomString(context, custom);
       }
            return result + /* insert UserID here */;
    }


Coordinator
Jan 3, 2011 at 4:15 PM
Edited Jan 3, 2011 at 4:16 PM

I re-read the question and understood that haven't answered it correctly. With standard asp.net caching you can

1) Cache the whole page

2) Cache some controls

Standard asp.net caching does not support logic "cache everything except for some controls" since it caches the page as a text rather than a tree of controls, so it cannot render just some controls without building a page. 

There're some workarounds, how to reuse the asp.net's full page caching and still have controls that are not affected. But they're kind of "hardcore" and it is easier just to use 2) on those controls that do hit the performance. Until it is a problem there's no point in spending time on it.

Jan 3, 2011 at 4:21 PM

Thanks a lot.

I definitely will go with number 2 - the controls form a b2b webshop and the load on it is not that high. Anyway it would have been nice to still cache the public parts of the site, but there really is no performance argument for doing it in this case.

Coordinator
Jan 3, 2011 at 4:26 PM
Edited Jan 3, 2011 at 4:29 PM

No worries - with ASP.NET you can have your cake and eat it too ;-)

Reintroduce full page caching and then add the code below to controls that contain sensitive data:

 

  HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
  HttpContext.Current.Response.Cache.SetNoServerCaching();
  HttpContext.Current.Response.Cache.SetNoStore();

 

This will disable full page caching for the current request.

There is also a C1 Function you can add to pages/templates/XSLT output etc. that does the job (disable caching for a given request): Composite.Web.Response.SetServerPageCacheDuration

Jan 3, 2011 at 4:30 PM

var cache = Response.Cache;
cache.SetCacheability(HttpCacheability.NoCache);
cache.SetNoServerCaching();
cache.SetNoStore();

( just making sure we're writing optimal code :) )

Feb 1, 2011 at 8:02 PM

Hi. 

I am trying to implement a simple Webshop with Composite and Mvc..

Where should I use burningice's suggestion? 

tryed this:

  public class BasketController : Controller
    {
        public ActionResult Index()
        {
            var cache = Response.Cache;
            cache.SetCacheability(HttpCacheability.NoCache);
            cache.SetNoServerCaching();
            cache.SetNoStore();
.... rest here: http://pastebin.com/0Fdw043A

Dosents seems to work..
Hope someone can help.

 

Coordinator
Feb 1, 2011 at 10:47 PM
Edited Feb 1, 2011 at 10:49 PM

If by "doesn't work" you mean it doesn't compile, try 

 

var cache = System.Web.HttpContext.Current.Response.Cache;
...

http://pastebin.com/8HYNwBMb


Feb 2, 2011 at 6:25 AM

It compiles fine, but it still catches my request to the controller..

When i delete the cache directive at ~/Renderers/Page.aspx everything works.

Could i somehow cache the site, except the request to the MVC controller?

or should I somehow add "Composite.Web.Response.SetServerPageCacheDuration" to all MvcPlayer functions?

Coordinator
Feb 3, 2011 at 10:47 AM
Edited Feb 3, 2011 at 10:47 AM

It is a bug in the current version of the MVC player - all the MVC calls are executed in separate requests, so when you disable caching in from the controller, it does not affect the original http request. Until we fixed the player, I'd recommend just to turn off the asp.net caching.