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.
Related
I have some simple classes that need to be disposed a the end of the request.
For that end I call the Dispose method on those objects from the Application_EndRequest event in Global.asax.
This "works fine on my machine" but causes some problems on my production server where I get Cannot access a disposed object. This happens in some MVC helpers.
It seemed to me like Application_EndRequest is triggered at the end of the request. Is this not the case? Is there another event I should be using to dispose my objects?
Application pool issues - likely
I suspect that your disposable object isn't bound to request but rather app wide (it may be instantiated per request but it may be using some shared resources). As long as you're testing your application in development environment it seems to behave as expected but as soon as you put it in production you get issues. This indicates you may have problems with application pool.
IIS web application pool capabilities actually instantiate several HttpApplication instances for your application and they may all share common disposable resources. If that's the case with your disposable object and you're sharing it it may be that it isn't thread safe. The same would be true when you wouldn't wrap your shared resource usage inside thread safe operations.
That's why it may happen that while one request is in progress another one begins and the first one disposed the object while the second process still uses it.
More information is always helpful
If you'd explain the nature of your disposable object/resource and how you're using it in your application we could help you much better. But in the meantime, you could read my blog post that talks about application pools and handling them. It's not about disposable objects per se, but you may still find all the information very useful and helpful.
If you need some object disposable per-request to use inside your controllers, I would recommend you using controller's lifecycle handlers instead of using Application_BeginRequest and Application_EndRequest. See the following example.
The Controller:
public class BaseController : Controller
{
protected override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
this.HttpContext.Items["MyDisposableObject"] = new MyDisposableObject();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
if (this.HttpContext.Items.Contains("MyDisposableObject"))
{
var myDisposableObject =
this.HttpContext.Items["MyDisposableObject"] as IDisposable;
if (myDisposableObject != null)
{
myDisposableObject.Dispose();
}
}
}
}
The IDisposable object:
public sealed class MyDisposableObject : IDisposable
{
private bool disposed;
public void Dispose()
{
if (!this.disposed)
{
// Dispose all managed
// and unmanaged resources.
// Note disposing has been done.
this.disposed = true;
}
}
}
If the objects are scoped to controller level you can override the Dispose method of Controller to dispose those objects.
protected override void Dispose(bool disposing)
{
if(disposing)
{
// dispose the objects here
}
base.Dispose(disposing);
}
If you are using some DI framework (like Ninject) in your application you can delegate that job to them.
Instead of disposing the objects at the end of the request you can also try wrapping them in an using statement wherever you access by this way you make sure the object is disposed.
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
I have this code
//file Globals.cs in App_Code folder
public class Globals
{
public static string labelText = "";
}
and a simple aspx page which has textbox, label and button. The CodeFile is:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = Globals.labelText;
}
protected void Button1_Click1(object sender, EventArgs e)
{
Globals.labelText = TextBox1.Text;
}
}
That is when I click on the button the Globals.labelText variable initializes from the textbox; the question is: why when I open this page in another browser the label has that value, which I set by the first browser, that is the static member is common for the every users. I thought that the every request provides in the individual appDomain which created by the individual copy of IIS process. WTF?
Yes you may use static variable to store application-wide data but it is not thread-safe. Use Application object with lock and unlock method instead of static variables.
Take a look at ASP.NET Application Life Cycle Overview for IIS 7.0 and ASP.NET Application Life Cycle Overview for IIS 5.0 and 6.0
No, static in this case is static in that manner only for the lifecycle of the process the request lives on. So this variable will be static the entire time you're processing a single request. In order to have a "static" variable in the manner you describe, you'd have to make it an application variable. Something like this:
//file Globals.cs in App_Code folder
public class Globals
{
// I really recommend using a more descriptive name
public static string LabelText
{
get
{
return Application("LabelText") ?? string.Empty;
}
set
{
Application("LabelText") = value;
}
}
}
By making it an application variable it should survive multiple page requests. A vulnerability it has though is that it will not survive an application pool recycle, and for large applications this can be problematic. If you truly want this variable to behave in a static manner reliably you're probably better off storing its state in a database somewhere.
I've got an HttpModule in my application that hooks into the FormsAuthenticationModule's Authenticate event with the following code:
public void Init(HttpApplication context)
{
FormsAuthenticationModule faModule =
(FormsAuthenticationModule)context.Modules["FormsAuthentication"];
faModule.Authenticate +=
new FormsAuthenticationEventHandler(faModule_Authenticate);
}
Unfortunately, the call to context.Modules fails because the app needs to run in a medium-trust environment. Is there another way that I can hook into this event?
That's a tough one - you can't even access the Modules collection from within your Global application file.
You could try calling your custom code from the AuthenticateRequest handler in Global:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Call your module's code here..
}
You can't grab your custom module from the collection, either, so you'd need a static reference to your module's library.
Other than granting the AspNetHostingPermission (as detailed for other permissions here) to your site in the machine level web.config, I'm out of ideas!
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