Search code examples
c#asp.netsessiondata-access-layer

How will a static delegate access non-static, server-side, session-based information?


Sorry about the title. This is fairly complex. I'll try to summarise it in a simplified, abstract way (as much as I can).

In a web forms application, the DAL will be used from two different contexts:

  1. From code-behind
  2. From a Web API controller

In each call, the DAL needs to be made aware of the current User Id so that it can store this id against database transactions. I've added a static property to the DAL that is of type Func, such that when the DAL is used, it can be provided with a method (by the callng code) that allows it to fetch the user id.

The calls made from code-behind would provide a GetUserId method that get the user id from session, whereas the Web API controller calls (which are stateless) will have the user id as a parameter and will be able to return that value.

What concerns me is that statics in a web environment are shared across sessions. So..

  1. When user A runs a session that sets the GetUserId method to return the Session["UserId"], and user B runs a session that does the same thing at the same time, will each transaction receive the correct user id? I'm thinking that because HttpContext.Current is a static itself, this should be okay.
  2. When user A makes a call through the Web API controller and sets the GetUserId method to return the parameter value (let's say id 1), and then user B makes the same call at the same time, but the user id parameter is 2... will one replace the other, or will they remain distinct? I'm thinking that they're probably going to clash.

If they're going to clash, then I'll find another way to do it, but I wanted to avoid having to pass the user id into every object Save().

SOLUTION

Prompted by Steven's answer, I've had a play CallContext just to prove to myself that it will retain context against a specific thread, in spite of being called through a static. Following is a very simple test with overlapping threads. Next I'll try it in a Web API environment, which should be the same.

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 10; i++)
            StartTestThread(i);

        Console.ReadKey();
    }

    private static void StartTestThread(int i)
    {
        Thread.Sleep(2000);

        ThreadPool.QueueUserWorkItem(delegate {

            Console.WriteLine(string.Format("[{0}] Setting context...", i));
            SetCallContext(i);
            Console.WriteLine(string.Format("[{0}] Waiting...", i));
            Thread.Sleep(5000);
            Console.WriteLine(string.Format("[{0}] Context is {1}", i, GetCallContext()));
        });
    }

    private static void SetCallContext(int i)
    {
        CallContext.LogicalSetData("Test", i);
    }

    private static int GetCallContext()
    {
        return (int) CallContext.LogicalGetData("Test");
    }
}

Solution

  • Reading from HttpContext.Current.Session is obviously safe, since HttpContext.Current returns a different HttpContext instance for each request. But since your Func field is static, replacing the Func will of course make your code fail misserably, since there is just one instance of a static field per App Domain. So you should take a different approach.

    For instance, instead of replacing the Func, initialize it just once and let it handle the Web API case as well. For instance:

    GetUserId = () =>
    {
        var context = HttpContext.Current;
    
        // MVC / Web Forms
        if (context.Session != null && context.Session["UserId"] != null) {
            return (int)context.Session["UserId"];
        }
    
        // Web API
        return (int)CallContext.LogicalGetData("__userId");
    };
    
    public static void RegisterWebApiUserId(int userId) {
        CallContext.LogicalSetData("__userId", userId);
    }