Can I access virtual directory name in global.asax.cs? - asp.net

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

Finally found the simple answer!
HttpRuntime.AppDomainAppVirtualPath
Available immediately during Application_Start
This is of the form /myapplication including the / prefix.

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");

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

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();
}
}
}

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

Related

Asp.net global output cache

Last few days I thinkin about output cache in asp.net. In my task I need to implement output cache for the very big project. After hours of searching I did not find any examples.
Most popular way to use output cache is declarative, in this case you need to write something like this on the page which you want to cache.
But if you need to cache whole site you must write this on all pages or master pages on project. It is madness. In this case you cant store all configuration in one place. All page have his own configurations..
Global.asax could help me, but my site contains about 20 web progects and ~20 global.asax files. And i don't want copy same code to each project.
For these reasons, i made decision to create HTTPModule.
In Init method i subscribe to two events :
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(OnApplicationPreRequestHandlerExecute);
app.PostRequestHandlerExecute += new EventHandler(OnPostRequestHandlerExecute);
}
In method "OnPostRequestHandlerExecute" I set up output caching parameters for each new request :
public void OnPostRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpCachePolicy policy = app.Response.Cache;
policy.SetCacheability(HttpCacheability.Server);
policy.SetExpires(app.Context.Timestamp.AddSeconds((double)600));
policy.SetMaxAge(new TimeSpan(0, 0, 600));
policy.SetValidUntilExpires(true);
policy.SetLastModified(app.Context.Timestamp);
policy.VaryByParams.IgnoreParams = true;
}
In "OnApplicationPreRequestHandlerExecute" method I set calback method to cache validation:
public void OnApplicationPreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
app.Context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), app);
}
And last part - callback validation method :
public void Validate(HttpContext context, Object data, ref HttpValidationStatus status)
{
if (context.Request.QueryString["id"] == "5")
{
status = HttpValidationStatus.IgnoreThisRequest;
context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), "somecustomdata");
}
else
{
status = HttpValidationStatus.Valid;
}
}
To attach my HttpModule I use programmatically attach method :
[assembly: PreApplicationStartMethod(typeof(OutputCacheModule), "RegisterModule")]
This method works perfectly, but I want to know is there other ways to do this.
Thanks.
Try seeing if IIS caching provides what you need.
http://www.iis.net/configreference/system.webserver/caching

Call the default asp.net HttpHandler from a custom handler

I'm adding ASP.NET routing to an older webforms app. I'm using a custom HttpHandler to process everything. In some situations I would like to map a particular path back to an aspx file, so I need to just pass control back to the default HttpHandler for asp.net.
The closest I've gotten is this
public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on
var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);
MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);
// write headers, etc. & send stream to Response
}
It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."
From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler
There is also this thing System.Web.UI.PageHandlerFactory which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.
This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.
Any ideas on how I can do this? Is it possible?
Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.
http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
string newTarget = "default.aspx";
string newQueryString = // whatever you want
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the file name.
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget , "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.
I expect this will only work in IIS7.
I'm you're using Routing in webforms you should be able to just add an ignore route for the specific .aspx files you want. This will then be handled by the default HttpHandler.
http://msdn.microsoft.com/en-us/library/dd505203.aspx
Another option is to invert the logic by handling the cases in which you do NOT want to return the default response and remap the others to your own IHttpHandler. Whenever myCondition is false, the response will be the "default". The switch is implemented as an IHttpModule:
public class SwitchModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += app_PostAuthenticateRequest;
}
void app_PostAuthenticateRequest(object sender, EventArgs e)
{
// Check for whatever condition you like
if (true)
HttpContext.Current.RemapHandler(new CustomHandler());
}
public void Dispose()
}
internal class CustomHandler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hallo");
}
public bool IsReusable { get; }
}

Handling application-wide events without Global.asax

My project has no "global.asax" for various reasons and I can't change that (it's a component). Also, I have no access to web.config, so an httpModule is also not an option.
Is there a way to handle application-wide events, like "BeginRequest" in this case?
I tried this and it didn't work, can someone explain why? Seems like a bug:
HttpContext.Current.ApplicationInstance.BeginRequest += MyStaticMethod;
No, this is not a bug. Event handlers can only be bound to HttpApplication events during IHttpModule initialization and you're trying to add it somewhere in the Page_Init(my assumption).
So you need to register a http module with desired event handlers dynamically. If you're under .NET 4 there is a good news for you - there is PreApplicationStartMethodAttribute attribute (a reference: Three Hidden Extensibility Gems in ASP.NET 4):
This new attribute allows you to have
code run way early in the ASP.NET
pipeline as an application starts up.
I mean way early, even before
Application_Start.
So the things left are pretty simple: you need to create your own http module with event handlers you want, module initializer and attribute to your AssemblyInfo.cs file . Here is a module example:
public class MyModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
public void Dispose()
{
}
void context_BeginRequest(object sender, EventArgs e)
{
}
}
To register module dynamically you could use DynamicModuleUtility.RegisterModule method from the Microsoft.Web.Infrastructure.dll assembly:
public class Initializer
{
public static void Initialize()
{
DynamicModuleUtility.RegisterModule(typeof(MyModule));
}
}
the only thing left is to add the necessary attribute to your AssemblyInfo.cs:
[assembly: PreApplicationStartMethod(typeof(Initializer), "Initialize")]

How can I utilize or mimic Application OnStart in an HttpModule?

We are trying to remove the global.asax from our many web applications in favor of HttpModules that are in a common code base. This works really well for many application events such as BeginRequest and PostAuthentication, but there is no Application Start event exposed in the HttpModule.
I can think of a couple of smelly ways to overcome this deficit. For example, I can probably do this:
protected virtual void BeginRequest(object sender, EventArgs e)
{
Log.Debug("Entered BeginRequest...");
var app = HttpContext.Current.Application;
var hasBeenSet app["HasBeenExecuted"] == null ? false : true;
if(!hasBeenSet)
{
app.Lock();
// ... do app level code
app.Add("HasBeenExecuted", true);
app.Unlock();
}
// do regular begin request stuff ...
}
But this just doesn't smell well to me.
What is the best way to invoke some application begin logic without having a global.asax?
Just keep a static bool in the HttpModule:
private static bool _hasApplicationStarted = false;
private static object _locker = new object();
private void EnsureStarted()
{
if (_hasApplicationStarted) return;
lock (_locker)
{
if (_hasApplicationStarted) return;
// perform application startup here
_hasApplicationStarted = true;
}
}
Then have any method that needs the application to have started just call EnsureStarted.
HttpModules and HttpHandlers will execute on every single request, while the Global.asax App Start event is when the application starts, thus only once.
You could make a general global.asax which will load all assemblies with a specific interface, and then drop in the dll's you want executed for that specific application. Or even register them in your web.config, and have your general global.asax read the keys, and then load and execute the code you want.
I think this is better than putting app once code in a module and checking on a state variable.

ASP.NET MVC Relative Paths

In my applications, I often have to use relative paths. For example, when I reference JQuery, I usually do so like this:
<script type="text/javascript" src="../Scripts/jquery-1.2.6.js"></script>
Now that I'm making the transition to MVC, I need to account for the different paths a page might have, relative to the root. This was of course an issue with URL rewriting in the past, but I managed to work around it by using consistent paths.
I'm aware that the standard solution is to use absolute paths such as:
<script type="text/javascript" src="/Scripts/jquery-1.2.6.js"></script>
but this will not work for me as during the development cycle, I have to deploy to a test machine on which the app will run in a virtual directory. Root relative paths don't work when the root changes. Also, for maintenance reasons, I cannot simply change out all the paths for the duration of deploying the test - that would be a nightmare in itself.
So what's the best solution?
Edit:
Since this question is still receiving views and answers, I thought it might be prudent to update it to note that as of Razor V2, support for root-relative urls is baked in, so you can use
<img src="~/Content/MyImage.jpg">
without any server-side syntax, and the view engine automatically replaces ~/ with whatever the current site root is.
Try this:
<script type="text/javascript" src="<%=Url.Content("~/Scripts/jquery-1.2.6.js")%>"></script>
Or use MvcContrib and do this:
<%=Html.ScriptInclude("~/Content/Script/jquery.1.2.6.js")%>
While an old post, new readers should know that Razor 2 and later (default in MVC4+) completely resolves this problem.
Old MVC3 with Razor 1:
Application home page
New MVC4 with Razor 2 and later:
Application home page
No awkward Razor function-like syntax.
No non-standard markup tags.
Prefixing a path in any HTML attributes with a tilde ('~') tells Razor 2 to "just make it work" by substituting the correct path. It's great.
Breaking change - MVC 5
Watch out for a breaking change change in MVC 5 (from the MVC 5 release notes)
Url Rewrite and Tilde(~)
After upgrading to ASP.NET Razor 3 or ASP.NET MVC 5, the tilde(~)
notation may no longer work correctly if you are using URL rewrites.
The URL rewrite affects the tilde(~) notation in HTML elements such as
<A/>, <SCRIPT/>, <LINK/>, and as a result the tilde no longer maps to
the root directory.
For example, if you rewrite requests for asp.net/content to asp.net,
the href attribute in
<A href="~/content/"/> resolves to
/content/content/ instead of /. To suppress this change, you can set
the IIS_WasUrlRewritten context to false in each Web Page or in
Application_BeginRequest in Global.asax.
They don't actually explain how to do it, but then I found this answer:
If you are running in IIS 7 Integrated Pipeline mode try putting the
following in your Global.asax:
protected void Application_BeginRequest(object sender, EventArgs e)
{
Request.ServerVariables.Remove("IIS_WasUrlRewritten");
}
Note: You may want to check Request.ServerVariables actually contains IIS_WasUrlRewritten first to be sure this is what your problem is.
PS. I thought I had a situation where this was happening to me and I was getting src="~/content/..." URLS generated into my HTML - but it turned out something just wasn't refreshing when my code was being compiled. Editing and resaving the Layout and page cshtml files somehow triggered something to work.
In ASP.NET I usually use <img src='<%= VirtualPathUtility.ToAbsolute("~/images/logo.gif") %>' alt="Our Company Logo"/>.
I don't see why a similar solution shouldn't work in ASP.NET MVC.
<script src="<%=ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>" type="text/javascript"></script>
Is what I used. Change path to match your example.
For what it's worth, I really hate the idea of littering my app with server tags just to resolve paths, so I did a bit more research and opted to use something I'd tried before for rewriting links - a response filter. In this way, I can prefix all absolute paths with a known prefix and replace it at runtime using the Response.Filter object and not have to worry about unnecessary server tags. The code is posted below in case it will help anyone else.
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace Demo
{
public class PathRewriter : Stream
{
Stream filter;
HttpContext context;
object writeLock = new object();
StringBuilder sb = new StringBuilder();
Regex eofTag = new Regex("</html>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex rootTag = new Regex("/_AppRoot_", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public PathRewriter(Stream filter, HttpContext context)
{
this.filter = filter;
this.context = context;
}
public override void Write(byte[] buffer, int offset, int count)
{
string temp;
lock (writeLock)
{
temp = Encoding.UTF8.GetString(buffer, offset, count);
sb.Append(temp);
if (eofTag.IsMatch(temp))
RewritePaths();
}
}
public void RewritePaths()
{
byte[] buffer;
string temp;
string root;
temp = sb.ToString();
root = context.Request.ApplicationPath;
if (root == "/") root = "";
temp = rootTag.Replace(temp, root);
buffer = Encoding.UTF8.GetBytes(temp);
filter.Write(buffer, 0, buffer.Length);
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return filter.CanSeek; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
return;
}
public override long Length
{
get { return Encoding.UTF8.GetBytes(sb.ToString()).Length; }
}
public override long Position
{
get { return filter.Position; }
set { filter.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return filter.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return filter.Seek(offset, origin);
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
}
public class PathFilterModule : IHttpModule
{
public void Dispose()
{
return;
}
public void Init(HttpApplication context)
{
context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
}
void context_ReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app.Response.ContentType == "text/html")
app.Response.Filter = new PathRewriter(app.Response.Filter, app.Context);
}
}
}
The Razor view engine for MVC 3 makes it even easier and cleaner to use virtual-root relative paths that are properly resolved at run-time. Just drop the Url.Content() method into the href attribute value and it will resolve properly.
Application home page
Like Chris, I really can't stand having to put bloated server-side tags inside my clean markup just purely to tell the stupid thing to look from the root upwards. That should be a very simple, reasonable thing to ask for. But I also hate the idea of having to go to the effort of writing any custom C# classes to do such a simple thing, why should I have to? What a waste of time.
For me, I simply compromised on "perfection" and hardcoded the virtual directory's root path name inside my path references. So like this:
<script type="text/javascript" src="/MyProject/Scripts/jquery-1.2.6.js"></script>
No server-side processing or C# code required to resolve the URL, which is best for performance although I know it would be negligible regardless. And no bloated ugly server-side chaos in my nice clean markup.
I'll just have to live with knowing that this is hardcoded and will need to be removed when the thing migrates to a proper domain instead of http://MyDevServer/MyProject/
Cheers
I use a simple helper method. You can easily use it in the Views and Controllers.
Markup:
About Us
Helper method:
public static string Root()
{
if (HttpContext.Current.Request.Url.Host == "localhost")
{
return "";
}
else
{
return "/productionroot";
}
}

Resources