Use Path.Combine to avoid ugly program failures!

I’ve so often seen people combining paths like this:

string path = basePath + "\\" + filename;

Sometimes they use Path.DirectorySeparatorChar or even check wether basePath or filename already contains. The program then fails with exceptions like "could not find file c:\Program Files\MyProggie\\settings.xml" or even worse files like MyProggiesettings.xml are created, just because the person configuring the program isn’t aware of how and where to use "\".

This what System.IO.Path.Combine(string, string) does for you:

  • It validates both paths. They can be null or empty, but if they contain invalid path chars, an argument exception is thrown.
  • It uses Path.DirectorySeparatorChar to combine, but will never end up having two separators or none.
  • It takes only the second path if it is absolute, so you never get paths like "c:\data\d:\data\logs\myfile.xml"
  • If one of the paths is null or empty, the other one is returned.

Simple combinations

If the second path is relative, it will get combined with the first path.

  • Path.Combine("abc", "file.xml") => "abc\file.xml"
  • Path.Combine("abc\", "file.xml") => "abc\file.xml"
  • Path.Combine("c:\abc\", "file.xml") => "c:\abc\file.xml"
  • Path.Combine("c:\abc\", "data\file.xml") => "c:\abc\data\file.xml"
  • Path.Combine("", "data\file.xml") => "data\file.xml"
  • Path.Combine("c:\abc", "") => "c:\abc"

Absolute second path

If the second path is absolute, the first one will be ignored. (See also Path.IsPathRooted(string))

  • Path.Combine("c:\abc", "\file.xml") => "\file.xml"

    (if the second path starts with the DirectorySeparatorChar it is also treated as absolute!)
  • Path.Combine("c:\abc", "c:\file.xml") => "c:\file.xml"

Example Usage

For NT Services the root directory often is "c:\Windows\System32", so if you want to store something relatively, you should combine it with AppDomain.CurrentDomain.BaseDirectory.

So if you want to write something to a configured file within your application you could code it like this:

var file = ConfigurationManager.AppSettings["MyXmlFile"];
if (string.IsNullOrEmpty(file))
    throw new ConfigurationErrorsException(
                      "Missing app setting: 'MyXmlFile'.");

var filePath = Path.Combine(
                    AppDomain.CurrentDomain.BaseDirectory, 
                    file);

using (var writer = new StreamWriter(filePath))
{
    // write something to your file ...
}

kick it on DotNetKicks.com

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

About Lazy .Net Programmers and Microformats

Mads Kristensen is right in his article saying that WE have to wake up for the possibilities the web offers. It’s not about threats, it’s about opportunities!

I have created some Microformats in my current project. But wrongly! Why? Haven’t installed Operator toolbar for Firefox yet. Ridiculous, isn’t it. Or rather laziness.

But that’s what Mads is talking about. We’re just to lazy to look at things our end user could have benefit of. Instead we think we’re ahead others just because auf cool technologies making our live easier.

Actually I don’t think having the functionality in browsers, by add-ons or even natively, is so important. It’s more interesting to use the data in searches and mash-ups. But as soon as it got excepted, there will be a lot of spam, too.