EfA-Fontsize zerstört Session Cookies

Das Problem

Das ASP.NET Session Cookie funktioniert nicht mehr wie es soll. Befindet man in einer Webapplikation und ist angemeldet, oder hält irgendeinen anderen Status in der ASP.NET Session, teilt sich diese Information auch mit neu geöffneten Browsern oder Fenstern. Es ist also beispielsweise nicht möglich, sich mit einem anderen Benutzer in einer zweiten Browserinstanz an derselben Applikation anzumelden.

Der Übeltäter

Nach langer Suche haben wir festgestellt, dass der Cookie-Manager im EfA-Fontsize Skript buggy ist. Das eingebundene Cookie-Skript geht alle Cookies (vorher in this.cookies abgelegt) durch und schreibt sie neu.

for(var name in this.cookies) {
  expires = (this.expiration[name])?this.expiration[name]:this.defaultExpiration;
  path = this.defaultPath;
  domain = this.defaultDomain;
  if(name) {
    var cookies = name + '=' + this.cookies[name] + '; expires=' + expires + '; path=' + path + '; domain=' + domain;
    if(cookies != '') {
      document.cookie = cookies;
     }
   }
}

Dabei wird die expires auf Heute+7 Tage gesetzt. Der Wert wird zwar über this.expiration[name] vermeintlich "wiederhergestellt", er wurde aber nie abgelegt. Dadurch werden temporäre Cookies zu permanenten. Auch path und domain für alle Cookies auf den gleichen Wert zu setzen ist ein kleines Verbrechen.

Cookiemanager.prototype.getExpiration = function() {
  var date = new Date();
  date.setTime(date.getTime()+(7*24*60*60*1000));
  return date.toGMTString();
}

 

Die Lösung

Das Skript für die EfA-Fontsize-Switcher scheint nicht mehr verfügbar zu sein: http://www.einfach-fuer-alle.de/artikel/fontsize/

Also habe ich mir auch nicht die Mühe gemacht, dass Skript zu patchen, sondern den Cookiemanager durch ein jQuery Plugin ersetzt.

  1. Plugin herunterladen und einbinden: Plugins | jQuery Plugins | Cookie
  2. Die Datei efa_fontsize.js anpassen

  3. Efa_Fontsize06.prototype.getCookie = function() {
      var cookie = $.cookie(this.cookieName);
      return (cookie)?cookie:false;
    }
    Efa_Fontsize06.prototype.setCookie = function(cookieValue) {
      return $.cookie(this.cookieName, cookieValue, { expires: 7, path: '/' });
    }
    
  4. Alle anderen Referenzen auf cookies.js aktualisieren oder entfernen und die Skript-Datei löschen.
Advertisement

How to make ASP.net session state testable

In the initial version, the wrapper simply throws an exception if HttpContext.Current or HttpContext.Current.Session is not available.

So in our Test-Methods we have to “mock” out the SessionState somehow. Basically we just need to override the Session State with a static dictionary within the computing Thread.

I would like to write my tests like this…

[Test]
public void TestOverride()
{
  using(SessionVariableStorage.Override(new Dictionary<string, object>()))
  {
    // normally a call to code that uses session variables internally
    var sessionString = new SessionVariable("sessionString"); 
    Assert.IsFalse(sessionString.HasValue);
    sessionString.Value = "Hello, world!";
    Assert.IsTrue(sessionString.HasValue);
    Assert.AreEqual("Hello, world!", sessionString.Value);
  }
}

To enable that, I need an extra static class “SessionVariableStorage” and some modifications in the SessionVariable<T>.

public static class SessionVariableStorage
{
  internal static readonly ThreadVariable<IDictionary<string, object>> Storage =
    new ThreadVariable<IDictionary<string, object>>();

  public static IDisposable Override(IDictionary<string, object> storage)
  {
    return Storage.Use(storage);
  }
}

Every time before I access the HttpSession I have to check wether it’s currently overridden or not. Here is the new version (requiring ThreadVariable<T>)

/// <summary>
/// Wrapper class for <see cref="HttpContext.Session"/>.
/// Author: Lars Corneliussen
/// Source: https://startbigthinksmall.wordpress.com/2008/05/14/how-to-wrap-the-aspnet-session-state/
/// </summary>
/// <typeparam key="T">
/// The type of the value to be stored.
/// </typeparam>
public class SessionVariable<T>
{
  private readonly string key;
  private readonly Func<T> initializer;

  /// <summary>
  /// Initializes a new session variable.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  public SessionVariable(string key)
  {
    if (string.IsNullOrEmpty(key))
      throw new ArgumentNullException("key");

    this.key = GetType() + key;
  }

  /// <summary>
  /// Initializes a new session variable with a initializer.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  /// <param name="initializer">
  /// A function that is called in order to create a 
  /// default value per session.
  /// </param>
  public SessionVariable(string key, Func<T> initializer) : this(key)
  {
    if (initializer == null) 
      throw new ArgumentNullException("initializer");

    this.initializer = initializer;
  }


  private object GetInternalValue(bool initializeIfNessesary)
  {
    if (SessionVariableStorage.Storage.HasCurrent)
    {
      var dictionary = SessionVariableStorage.Storage.Current;

      object value;

      if (!dictionary.TryGetValue(key, out value) && initializeIfNessesary
          && initializer != null)
        dictionary.Add(key, value = initializer());

      return value;
    }
    else
    {
      HttpSessionState session = CurrentSession;

      var value = session[key];

      if (value == null && initializeIfNessesary
          && initializer != null)
        session[key] = value = initializer();

      return value;
    }
  }

  private static HttpSessionState CurrentSession
  {
    get
    {
      var current = HttpContext.Current;

      if (current == null)
        throw new InvalidOperationException(
          "No HttpContext is not available.");

      var session = current.Session;
      if (session == null)
        throw new InvalidOperationException(
          "No Session available on current HttpContext.");
      return session;
    }
  }

  /// <summary>
  /// Indicates wether there is a value present or not.
  /// </summary>
  public bool HasValue
  {
    get { return GetInternalValue(false) != null; }
  }

  /// <summary>
  /// Sets or gets the value in the current session.
  /// </summary>
  /// <exception cref="InvalidOperationException">
  /// If you try to get a value while none is set. 
  /// Use <see cref="ValueOrDefault"/> for safe access.
  /// </exception>
  public T Value
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        throw new InvalidOperationException(
          "The session does not contain any value for '"
          + key + "'.");

      return (T) v;
    }
    set
    {
      if (SessionVariableStorage.Storage.HasCurrent)
        SessionVariableStorage.Storage.Current.Add(key, value);
      else
        CurrentSession[key] = value;
    }
  }

  /// <summary>
  /// Gets the value in the current session or if 
  /// none is available <c>default(T)</c>.
  /// </summary>
  public T ValueOrDefault
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        return default(T);

      return (T)v;
    }
  }

  /// <summary>
  /// Clears the value in the current session.
  /// </summary>
  public void Clear()
  {
    if (SessionVariableStorage.Storage.HasCurrent)
      SessionVariableStorage.Storage.Current.Remove(key);
    else
      CurrentSession.Remove(key);
  }
}

How to wrap the ASP.net session state

Update:

After reading Why Should You Wrap Your ASP.NET Session Object by Shahar A I thought the Community is ready for another step of encapsulation – and this is what I want to share.

We all know about the problems storing data in untyped dictionaries using some key, so I spare you.

This is how I want to store and retrieve session variables:

public class MySessionVariables
{
  // just an example, pls use Asp.Net Membership instead…
  public static readonly SessionVariable<string&#91;&#93;> UserGroups
    = new SessionVariable<string&#91;&#93;>(“UserGroups”);
}

// Fill the UserGroups after Login, for example

public void DeleteDatabase()
{
  if (MySessionVariables.UserGroups.HasValue &&
      MySessionVariables.UserGroups.Value.Contains(“Admin”))
    MyApplication.DeleteDatabase();
}

I would really like to not have a key to store the value, but I couldn’t avoid. I thought about creating a new Guid per class instance. But this won’t work when you run multiple Applications using some network session state. If someone has an idea on this, please share!

Initializer

Sometimes I need a dictionary or a List<T> to cache some values. So in order to avoid checking wether my SessionVariable has any value before accessing it I would like it to be initialized automatically on first use.

public class MySessionVariables
{
  public static readonly SessionVariable<List<string>> MarkedArticles
    = new SessionVariable<List<string>>(“MarkedArticles”, () => new List<string>());
}

If you’re not using C# 3 yet, you could easily add a delegate instead of using Func<T>.

   1: public delegate T CreateInitialValueHandler();
 
Additionally I added a property called ValueOrDefault that gives back default(T) if no value is assigned yet, while Value throws an exception if neither a value nor an initializer is available.

Free Source Code

/// <summary>
/// Wrapper class for <see cref=”HttpContext.Session”/>.
/// Author: Lars Corneliussen
/// Source: https://startbigthinksmall.wordpress.com/2008/05/14/how-to-wrap-the-aspnet-session-state/
/// </summary>
/// <typeparam key=”T”>
/// The type of the value to be stored.
/// </typeparam>
public class SessionVariable<T>
{
  private readonly string key;
  private readonly Func<T> initializer;

  /// <summary>
  /// Initializes a new session variable.
  /// </summary>
  /// <param name=”key”>
  /// The key to use for storing the value in the session.
  /// </param>
  public SessionVariable(string key)
  {
    if (string.IsNullOrEmpty(key))
      throw new ArgumentNullException("key");

    this.key = GetType() + key;
  }

  /// <summary>
  /// Initializes a new session variable with a initializer.
  /// </summary>
  /// <param name=”key”>
  /// The key to use for storing the value in the session.
  /// </param>
  /// <param name=”initializer”>
  /// A function that is called in order to create a
  /// default value per session.
  /// </param>
  public SessionVariable(string key, Func<T> initializer) : this(key)
  {
    if (initializer == null)
      throw new ArgumentNullException("initializer");

    this.initializer = initializer;
  }


  private object GetInternalValue(bool initializeIfNessesary)
  {
    HttpSessionState session = CurrentSession;

    var value = session[key];

    if (value == null && initializeIfNessesary
      && initializer != null)
      session[key] = value = initializer();

    return value;
  }

  private static HttpSessionState CurrentSession
  {
    get
    {
      var current = HttpContext.Current;

      if (current == null)
        throw new InvalidOperationException(
          "No HttpContext is not available.");

      var session = current.Session;
      if (session == null)
        throw new InvalidOperationException(
          "No Session available on current HttpContext.");
      return session;
    }
  }

  /// <summary>
  /// Indicates wether there is a value present or not.
  /// </summary>
  public bool HasValue
  {
    get { return GetInternalValue(false) != null; }
  }

  /// <summary>
  /// Sets or gets the value in the current session.
  /// </summary>
  /// <exception cref=”InvalidOperationException”>
  /// If you try to get a value while none is set.
  /// Use <see cref=”ValueOrDefault”/> for safe access.
  /// </exception>
  public T Value
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        throw new InvalidOperationException(
          "The session does not contain any value for ‘"
          + key + "‘.");

      return (T) v;
    }
    set { CurrentSession[key] = value; }
  }

  /// <summary>
  /// Gets the value in the current session or if
  /// none is available <c>default(T)</c>.
  /// </summary>
  public T ValueOrDefault
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        return default(T);

      return (T)v;
    }
  }

  /// <summary>
  /// Clears the value in the current session.
  /// </summary>
  public void Clear()
  {
    CurrentSession.Remove(key);
  }
}

kick it on DotNetKicks.com