Add HttpContext into HangFire
Asked Answered
T

2

7

I am a beginner with HangFire and looking forward to using HangFire to call some actions in my web application monthly. But these actions require HttpContext.

Then my question is: Is there any way to add (or create) a httpcontext in HangFire project?

I tried to google but no suitable answer. Thanks for your help!

I found a short discussion. Sad to see the answer is "no way". Update: Ref https://discuss.hangfire.io/t/passing-site-url-to-hangfire-recurrent-jobs/2641

Titos answered 21/11, 2017 at 2:59 Comment(3)
Do you really need a whole HttpContext by itself, or a small set of functionalities available through the HttpContext ? That would make a huge differenceIhs
Hi @Ihs I tried with a simpler httpContext (ibb.co/m3OF06) and then an error happened (ibb.co/cCk67m).Titos
I've added my code below (as an answer on how to create a fake HttpContext which is serializable). Just want to stress that if you don't cleanup your fake HttpContext at the end of your job you might have problems since hangfire jobs will reuse the thread from previous jobs, and in case will inherit the context (which is associated to the thread).Prefigure
P
3

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
        }
    }
}


Prefigure answered 25/11, 2019 at 22:46 Comment(0)
T
2

After 3 days with this problem, I found that it is POSSIBLE to create a fake HttpContext inside HangFire. There are many things need to be constructed in this fake HttpContext. However, you can just initialize properties that you need, no need to define all.

Big thank @jbl

Titos answered 23/11, 2017 at 2:14 Comment(2)
can you post your result?Cytochemistry
@BrokenRobot You can create your fake HttpContext in HangFire by following this post: #4379950Titos

© 2022 - 2024 — McMap. All rights reserved.