views:

1021

answers:

6

The property HttpContext.Current.Request.ApplicationPath represents the virtual directory in IIS or WebDev.WebServer.

 HttpContext.Current.Request.ApplicationPath evaluates to "/virtualdirectory"

This can be used in conjunction with VirtualPathUtility to make a path root relative :

 VirtualPathUtility.ToAbsolute("~/images/cat.jpg",
                               HttpContext.Current.Request.ApplicationPath)

 // (this evaluates to "/virtualdirectory/images/cat.jpg")

In IIS6 and WebDev.WebServer the Request object is available in global.asax.cs, but IIS7 complains that it is 'not available in current context'. Therefore the second line of code above works but not in IIS7.

The problem is I need to access the virtual directroy name within global.asax.cs. I need it to construct some paths that are used in dynamically created CSS. Is there an alternative way to access this value?

Edit: This is the error you get in IIS 7 for calling HttpContext.Current.Request in global.asax.cs under Application_Start:

 HttpException (0x80004005): Request is not available in this context]
    System.Web.HttpContext.get_Request() +8789264
A: 

Can you use ResolveUrl("~/images/cat.jpg") to build your path?

Edit: ResolveUrl is a method of Control, not just the Page class, so you can do it this way instead (bit ugly maybe):

System.Web.UI.Control c = new Control();
String s = c.ResolveUrl(@"~/images/cat.jpg");
Nick
no because there is no instance of Page
Simon_Weaver
A: 

Hmmm... I wasn't aware of the IIS7 change. I wonder if it wouldn't be simpler to defer this operation until you have got a page. For example, you could try putting something "once only" in Application_BeginRequest or Session_Start?

Or (completely untested) for a self-unsubscribing hook:

    public override void Init() {
        base.Init();
        EventHandler handler = null;
        handler = delegate {
            // do stuff, once only
            this.BeginRequest -= handler;
        };
        this.BeginRequest += handler;
    }

The trick is doing it once only (if multiple requests arrive at once); perhaps a static ctor? For example, I think this fires once only, and only when there is a page available in context:

    static class DelayedLoader {
        static DelayedLoader() {
            string s = VirtualPathUtility.ToAbsolute("~/images/cat.jpg",
                           HttpContext.Current.Request.ApplicationPath);
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void Init() { }
    }
    public override void Init() {
        base.Init();
        EventHandler handler = null;
        handler = delegate {
            DelayedLoader.Init();
            this.BeginRequest -= handler;
        };
        this.BeginRequest += handler;
    }
Marc Gravell
Application_BeginRequest is probably safest. I'm not sure about the lifecyle of Session_Start, but when I tried to use it the page output was corrupted - I think it is conflicting with my compression - which is very odd but I'll stick with Begin_Request for now
Simon_Weaver
A: 

This is the best I came up with : Application_BeginRequest (via mark)

I use asax so rarely that I had temporarily forgotten you get different events with it. Until now I'd been creating the CSS sprites in Application_Start. Moving it to BeginRequest was the best I could come up with.

One boolean check for every request is negligible, but would be nice if there is a different way.

  protected void Application_Start()
    {
        RegisterRoutes(RouteTable.Routes);
    }

    protected void Application_BeginRequest()
    {
        if (!_initialized)
        {
            lock (thisLock)
            {
                _initialized = true;
                GenerateCSSSprites();  
            }
        }
    }
Simon_Weaver
Note that it seems to be theoretically possible to run the `GenerateCSSSprites` twice if two requests come in in close enough. Unlikely, but... you might want use double-checked locking (i.e. check _initialized inside the lock as well), or another approach (such as the static ctor trick).
Marc Gravell
oops. thanks. i was trying to avoid locking on every request and managed to miss that. its important since running GenerateCSSSprites twice will actually cause an exception becasue it adds items to a global dictionary and complains if this is done twice. thanks
Simon_Weaver
it actually DID run twice when i originally tried : GenerateCSSSprites(); _initialized=true. this is a little wierd since I'm the only person hitting the page and even though i'm accessing several resource on that host the browser cant ask for them until the first request is rendered - so IIS7 must be making more than one 'pseudo' request for my one real request. wierd
Simon_Weaver
A: 

I had this problem too when switching to IIS7 but I was able to refactor out the need for Request. Which is what this guy also suggests and provides a workaround if you can't.

http://mvolo.com/blogs/serverside/archive/2007/11/10/Integrated-mode-Request-is-not-available-in-this-context-in-Application_5F00_Start.aspx

Chad Grant
A: 

There may be another approach, depending on the reason you are accessing the virtual directory name. In my case I was able to change to this piece of code to get the full file system path to the current virtual directory.

public class Global : System.Web.HttpApplication
  {
  protected void Application_Start(object sender, EventArgs e)
    {
    string baseLocation = System.AppDomain.CurrentDomain.BaseDirectory;
    // ...
    }
  }

baseLocation will have a value like this:

"D:\path\to\solution\virtualdirectory"

EDIT: My original solution above does not address the question properly (base path is not virtual directory) ... so I have provided the following which I have used on IIS 7 to obtain the virtual directory path and provide a one-time initialization. It uses a simple lock and check mechanism to protect against double execution.

public class Global : System.Web.HttpApplication
   {
   protected Boolean _initialized;

   protected void Application_BeginRequest(object sender, EventArgs e)
      {
      if (!_initialized)
         {
         lock (Application)
            {
            if (!_initialized)
               {
               string path = VirtualPathUtility.ToAbsolute(
                  "~/images/cat.jpg", HttpContext.Current.Request.ApplicationPath);
               // do stuff, once only
               _initialized = true;
               }
            }
         }
      }
   }

Depending on the complexity of the initialization I would consider using the DelayedLoader class approach instead.

AdamC
thats not the virtual directory. thats a path. i need the part that just says 'virtualdirectory', which may or may not be there depending upon the context (so you cant just pick off the last part after the slash)
Simon_Weaver
+1  A: 

Finally found the simple answer!

 HttpRuntime.AppDomainAppVirtualPath

Available immediately during Application_Start

Simon_Weaver