How do I find the application path in an HttpModule timer?

Here's a pickle (to me, anyway)... How do you determine an application's path from an HttpModule inside of a running timer? I tried the obvious by passing in the application context and looking it up from Request.ApplicationPath, but that works only when the app first starts (which makes sense since there is no request when the timer fires). Code looks something like this...

    public class Module : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            timer = new Timer(new TimerCallback(this.myMethod),
                application.Context, TimeSpan.Zero, new TimeSpan(0, 15, 0));
        }

        static Timer timer;

        private void myMethod(object sender)
        {
            // this works the first time, but not on subsequent calls
            string path = ((HttpContext)sender).Request.ApplicationPath;
        }

        public void Dispose()
        {
            timer.Dispose();
        }
}

23 Comments

  • You can't do what you're trying to do. Since you're using a timer, it might go off when there are no users accessing your application. Thus there's no HttpContext, thus no Request, etc.



    Based upon what you're doing, I'd suggest caching the application path in a static field. You'll need to do this when your Init fires.



    -Brock

  • Oh, also note that there might be many instances of your Module created by ASP.NET, thus



    1) You could end up with many timers scheduled. Is this what you intended?



    2) You're using a static filed to store the timer. Each Module created will overwrite the previous timer reference. Your Dispose() only cleans up one of them, since it's in the static field. You'll end up leaking Timer objects. I doubt you want this.





  • Why would there be more than one instance of the module? I have never, ever noticed any behavior that would indicate there was more than one of any module.

  • Because that's how it's implemented. Have you tried to test more than one client at a time against your application?



    Check out HttpApplicationFactory.GetNormalApplicationInstance. This is what the HttpRuntime calls to get an Application and it's set of modules. These tuples are all pooled. This means there are potentially many instances of your HttpApplication-derived class created also (the one from global.asax).



  • Jeff, a better method is the static HttpRuntime.AppDomainAppPath property.

  • Ok. I was just trying to help. Good luck.

  • Well, first you said it couldn't be done, and James gave a working solution. Then you made statements about a class that isn't even documented in the SDK, so naturally I'm skeptical without a more thorough explanation.



    Don't get me wrong, I appreciate the help, but making unqualified statements makes things difficult.

  • Here ya go:



    http://msdn.microsoft.com/library/en-us/cpguide/html/cpconworkingwithhttpapplicationinstances.asp?frame=true

  • And where does it say there are multiple module instances?



    What I'm doing in that code is the same thing I've done a hundred times to track users around a site. Rob Howard does the very same thing in his forum (on asp.net or communityserver.org). He'd know better than any of us that it's an acceptable thing to do.

  • Nice link Brock. I didn't know a pool of HttpApplication objects were maintained and reused.



    Jeff since you don't need a request, you could either start the timer on Application_Start, or keep doing what you're doing and test in the module if timer is null before creating one (doing double check locking obviously).



    Oh, fyi the Cache is also stored on the HttpRuntime :)

  • Ummm... your module instance is guarenteed to be used only by that HttpApplication instance (that's the contract). So all you need to do is stuff the application instance handed to you in Init into a member variable and then in your timer call back you use the same varible.



    HTH,

    Drew

  • Also the fact that you use a static Timer variable is also technically a "strange" design. You should make it an instance variable. Like I said the HttpApplication instantiates an instance of your module and their lifetimes are (basically) tied to one another. Now in a web farm there N applications running so using a static is kinda confusing. In fact, the only reason you get away with it is because each application is separated by an AppDomain which is the boundary for static variables[1].



    HTH,

    Drew



    [1] http://blogs.msdn.com/cbrumme/archive/2003/04/15/51317.aspx

  • Oh and since I didn't actually address your problem (sorry):



    First Brock's advice about the HttpContext not being available is absolultely right. The only properties you can safely access from your own thread is Application, Modules and Server. However, since the application path is determined once for the lifetime of the application instance you can just grab *that* in Init and save that as a member variable since, as you observed, Context is available in Init.



    HTH,

    Drew

  • OK... I got some clarification on some of these things from Rob Howard. Yes, it's true that each request gets an instance of the module, however, it's that very reason that you MUST use a static variable for the timer, or you'll start firing off those things all over the place and kill your thread pool in virtually no time.

  • "OK... I got some clarification on some of these things from Rob Howard. Yes, it's true that each request gets an instance of the module, however, it's that very reason that you MUST use a static variable for the timer, or you'll start firing off those things all over the place and kill your thread pool in virtually no time."



    Indeed. That's why creating a new one per instance of the Module in Init is not what I thought you had intended (thus my comments). The code as it stands above is insufficient. Here's a quick rewrite from memory of how it'd have to be given all the nasty goo that one can encounter:



    public class Module : IHttpModule

    {

    public void Init(HttpApplication application)

    {

    if (timer == null)

    {

    lock (TheLock)

    {

    if (timer == null)

    {

    AppPath = HttpContext.Current.Request.ApplicationPath;



    Timer temp = new Timer(new TimerCallback(this.myMethod),

    application.Context, TimeSpan.Zero, new TimeSpan(0, 15, 0));

    System.Threading.Thread.MemoryBarrier();

    timer = temp;

    }

    }

    }

    }



    static string AppPath;

    static Timer timer;

    static object TheLock = new object();



    private void myMethod(object sender)

    {

    string path = AppPath;

    // other stuff ...

    }



    public void Dispose()

    {

    if (timer != null)

    {

    lock (TheLock)

    {

    if (timer != null)

    {

    timer.Dispose();

    timer = null;

    }

    }

    }

    }

    }



    It's possible that all of that Init stuff could have been done in a static constructor, but I'm not sure if the CLR guarentees that all static ctors have *completed* execution prior to a second thread accessing accessing the type. Also, as for the Dispose() and the double lock, I'm not too sure if it's needed.... I'd need to spend some time thinking about that one. But, as it's there now, it's perhaps over cautious, but like I said, it may not be needed.



    -Brock

  • Ick, sorry for the really bad code formatting. :(

  • While I get what you're saying, the truth is that my Timer NEVER fires more frequently than scheduled. Considering it's set to fire right away, you'd think then by your logic that it would be firing constantly, but this is not the case.

  • "While I get what you're saying, the truth is that my Timer NEVER fires more frequently than scheduled. Considering it's set to fire right away, you'd think then by your logic that it would be firing constantly, but this is not the case. "



    It's not the Timer that's important to see more than once, it's the ctor for the Module. As I asked before, have you done any load testing? More than just your one client browser? Since this is a timing issue I'd run many clients and have my test pages take many seconds to render (Thread.Sleep(10), perhaps) to see the Modules under pseudo-stress.



    Anyway, I can see you're quite obstinate about this issue. Honestly my point wasn't to criticize you, but you did ask for comments and put your code out for scrutiny. I was simply trying to tell you what I knew of the conditions that your code would see. Just because you don't see this condition today doesn't mean it doesn't or won't exist.



    I'll leave it at that. Once again, good luck.

  • I'm not taking it as criticism... what I describe has been in use with thousands of simultaneous users in a real live application. It has long since moved beyond testing.

  • I think you're still misunderstanding the architecture...



    "OK... I got some clarification on some of these things from Rob Howard. Yes, it's true that each request gets an instance of the module,"



    Actually this is false. Either you misunderstood Rob or Rob misunderstood your question. Each request *does not* get it's own instance of the module. Each instance of an HttpApplication that uses the module gets it's own instance of the module. The module instance is then held onto for the life of the application and is only disposed of when the HttpApplication is shut down.



    "however, it's that very reason that you MUST use a static variable for the timer, or you'll start firing off those things all over the place and kill your thread pool in virtually no time."



    Your module's Init method is called one time when the module is loaded for the application. Init is not called for each request for example, which is what I think you're thinking happens.



    HTH,

    Drew

  • No, that's not what I thought... I just repeated what he said incorrectly. I understand that it's one instance per app instance, not request.



    And I did some testing on this as well. I had a module log when it was created in Init and when it died in Dispose. Total number of instances in the test (with 200k requests)? Two.



    I'll make sure to test to see if the static variable has an instance of the timer each time and not create another one if it's there. Problem solved.

  • Jeff,



    Ah, ok cool. Glad to hear you worked it all out. :)



    I'm still not sure where you'd end up with more than one instance unless your HttpApplication was recycled, in which case the AppDomain should have been recycled too thus cleaning up your static. I would place my money on not even needing to check if the timer is already there since it should never be there unless there's a serious flaw in the ASP.NET runtime.



    Happy coding,

    Drew

  • AppDomain.CurrentDomain.SetupInformation

Comments have been disabled for this content.