I have a similar scenario where the system relies heavily on Session, and I took this same approach of faking an HttpContext and passing along session variables.
In my method I receive a serializable context, which contain all session variables:
public static void MyMethod(Hangfire.Server.PerformContext context, Hangfire.IJobCancellationToken cancellationToken,
SerializeableHttpContext serializeableHttpContext, ... etc)
{
using (var fakeContext = serializeableHttpContext.CreateFakeHttpContext()) {
// ...
}
}
During enqueue, I pass current context to my serializable context, which will capture all current variables:
// null and null are injected by Hangfire
Hangfire.BackgroundJob.Enqueue(() => MyMethod(null, null, new SerializeableHttpContext(System.Web.HttpContext.Current), etc..);
And this is where the magic happens. This will save all Session variables, and will restore them.
Please note that using IDispose is important because your next Hangfire job does NOT want to inherit a fake HttpContext from the previous job, so you need to clean up HttpContext.
/// <summary>
/// This serializes HttpContext with primitive Session variables
/// </summary>
[Serializable]
public class SerializeableHttpContext
{
public Uri RequestUrl { get; set; }
public Dictionary<string, object> SessionVariables { get; set; }
/// <summary>
/// Given a real HttpContext (you can pass System.Web.HttpContext.Current), this saves all useful information
/// into this serializable class, so that you can later reuse (restore) a cloned fake HttpContext
/// </summary>
/// <param name="httpContext">You'll probably want to pass System.Web.HttpContext.Current</param>
public SerializeableHttpContext(HttpContext httpContext)
{
this.RequestUrl = httpContext.Request.Url;
// Save all Session variables
this.SessionVariables = new Dictionary<string, object>();
foreach (object objkey in httpContext.Session.Keys)
{
string key = objkey as string;
if (key == null || httpContext.Session[key] == null)
continue;
Type type = httpContext.Session[key].GetType();
if (type.IsPrimitive || type == typeof(string))
{
try
{
// ignore if not serializable
object val = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(httpContext.Session[key]));
this.SessionVariables.Add(key, httpContext.Session[key]);
}
catch (Exception) { }
}
}
}
/// This is for internal usage, when deserializing.
public SerializeableHttpContext()
{
}
/// <summary>
/// Deserializes into a Fake HttpContext
/// </summary>
/// <returns></returns>
protected HttpContext Deserialize()
{
var httpRequest = new HttpRequest("", this.RequestUrl.AbsoluteUri, "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
// Restore session variables
if (this.SessionVariables != null)
foreach (string key in this.SessionVariables.Keys)
httpContext.Session[key] = this.SessionVariables[key];
// Restore context variables
if (this.ContextVariables != null)
foreach (string key in this.ContextVariables.Keys)
httpContext.Items[key] = this.ContextVariables[key];
return httpContext;
}
/// <summary>
/// Deserializes this class back into a fake HttpContext, and automatically sets that into System.Web.HttpContext.Current
/// Don't forget to DISPOSE this instance at the end, so that the Context is cleared (else Hangfire will reuse this thread with previous HttpContext)
/// </summary>
public FakeHttpContext CreateFakeHttpContext()
{
return new FakeHttpContext(this.Deserialize());
}
public class FakeHttpContext : IDisposable
{
HttpContext previousContext;
public FakeHttpContext(HttpContext context)
{
previousContext = HttpContext.Current;
HttpContext.Current = context;
}
public void Dispose()
{
HttpContext.Current = previousContext; // previousContext is probably null, but one might be using FakeHttpContexts even inside an existing web context
}
}
}