Worker queue and user context

1k Views Asked by At

We have a worker queue that a user can add work to. When the worker item is added the context is the users (HttpContext). But its a background thread that polls the queue and executes the items one by one in order.

I cant just store the User because when the HttpContext is disposed so will the Principal object

The code that can run in the worker needs the Principal to be correct for stuff like PrincipalPermissions etc.

Also, Lifetime management (IoC) uses the HttpContext for InRequest scopes, is it possible to recreate a HttpContext with the correct principal etc.

edit: Faking HttpContext is just a nice to have feature for Life time management, this I can work around. But our backend code heavily depends on having the correct user principal for the thread since we use this to validate if user has access to that part of the system. I would mark as answer if someone can answer how to store a user principal with identity, roles and IsAuthenticated state and later use that on another thread

3

There are 3 best solutions below

5
Eugene On
public void TestMethod1()
{
    System.Net.WebClient client = new System.Net.WebClient();
    client.BaseAddress = "http://www.teejoo.com";            

    //Invoke your function here
    client.OpenReadAsync(new Uri("http://www.teejoo.com/YourLogicalPage.aspx"));
    //Pur your logical in your page, so you can use httpContext 

    client.OpenReadCompleted += new System.Net.OpenReadCompletedEventHandler(client_OpenReadCompleted);
}

void client_OpenReadCompleted(object sender, System.Net.OpenReadCompletedEventArgs e)
{            
    //to Check the response HERE
}
1
nmat On

Why don't you use an auxiliar class to hold the information you need? You can create it during the web request with the appropriate values and pass it down as an argument to the background worker.

Cloning the HTTPContext object is not possible because of the internal server session state. Even if it were possible, using it outside of a real HTTP request just to check for values doesn't seem like a good solution.

2
BenSwayne On

Your best practice for consuming stateful data from the HttpContext is to create your own application specific context which accepts an HttpContext in the constructor (Dependency Injected).

Your business logic should never be dependent on an HttpContext but rather your new application specific context (which may have been created using info from an HttpContext).

This will not only solve your above problems, but also increase testability of your code.

Example:

public class MyApplicationContext
{
    public IPrincipal ContextPrincipal { get; set; }

    public MyApplicationContext(HttpContext httpContext)
    {
        // Store the current user principal & identity
        ContextPrincipal = httpContext.User;

        // Need to grab anything else from the HttpContext? Do it here! 
        // That could be cookies, Http request header values, query string 
        // parameters, session state variables, etc.
        //
        // Once you gather up any other stateful data, store it here in 
        // your application context object as the HttpRequest can't be passed 
        // to another thread.
    }

}

public class MyHttpHandler : IHttpHandler
{
    #region IHttpHandler Members

    public bool IsReusable
    {
        // Return false in case your Managed Handler cannot be reused for another request.
        // Usually this would be false in case you have some state information preserved per request.
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        // Do some work on another thread using the ThreadPool
        ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), new MyApplicationContext(context));
    }

    public void DoWork(object state)
    {
        // Grab our state info which should be an instance of an 
        // MyApplicationContext.
        MyApplicationContext context = (MyApplicationContext) state;

        // Assign this ThreadPool thread's current principal according 
        // to our passed in application context.
        Thread.CurrentPrincipal = context.ContextPrincipal;

        // Check if this user is authenticated.
        if (context.ContextPrincipal.Identity.IsAuthenticated)
        {
            var userName = context.ContextPrincipal.Identity.Name;
        }

        // Check if this user is an administrator.
        if (context.ContextPrincipal.IsInRole("Administrator"))
        {
        }

        // Do some long-ish process that we need to do on the threadpool 
        // after the HttpRequest has already been responded to earlier.
        //
        // This would normally be some fancy calculation/math, data 
        // operation or file routines.
        for (int i = 0; i < 30; i++)
        {
            Thread.Sleep(1000);
        }
    }

    #endregion
}

Neither the IPrincipal nor IIdentity interface explicitly offer a dispose method. So they should both be ok to keep a reference to them. However, I haven't tested the above code, I wrote it just for this question.

If by some poor design they actually do depend on an underlying database connection to query the roles membership, you'd simply have to evaluate that earlier in the constructor of your application context while the HttpContext and asp.net forms authentication provider are still non disposed/closed.

You can always take apart the principal and identity and recreate a new instance of GenericPrincipal and GenericIdentity or even create your application Identity class which implements IIdentity. There is lots of room for customization/extension here.