Proper way to skip page execution after Response.RedirectToRoute - asp.net

I'm writing an asp.net 4.5 application using the new routing features. I have a page that displays some information about an item. In the Page_Load event I check the route data (item id) and user permissions, and if something isn't right (e.g. the id is for a deleted item) I use Response.RedirectToRoute to send them packing, right back to the home page. Do not pass GO, do not collect $200.
This made perfect sense until I tried to access a deleted item and instead of the home page I got an error page. I did some digging and discovered that even after I use RedirectToRoute (unlike the standard Redirect method) the rest of the page code continues to execute, which at the very least seems wasteful (since I'm just going to throw away the results) and throws errors when the necessary data doesn't exist.
I did a little more SO mining and discovered the incredible evil that is Response.End(). It does what I need, but even the MSDN page tells me that Response.End is the bastard child of an ancient accursed language and isn't fit to see the light of day. The primary objection seems to be the fact that Response.End throws an exception, and that's bad for performance. I'm not the most experienced developer, so I don't understand the issue entirely, but I have trouble believing that throwing an exception is more expensive than loading the entire web page. The workarounds seem rather complex and excessive for a task so simple, especially since most pages require some kind of validity check.
What am I supposed to do in this situation? Use Response.End and beg forgiveness for my insolence? Cobble together some ugly workaround? Or is my perspective on the problem all wrong to begin with? I'd really like to know.
Update: Now that I've thought it over a bit more, I wonder if I do have the wrong perspective on the problem. Perhaps an immediate redirect is the not the best response for the user experience. Would I be better off wrapping all the controls in a panel, and using something like this?
Private Sub Page_Init(sender As Object, e As EventArgs) Handles Me.Init
'Validation Code
If notValid Then
ControlsPanel.Visible = false
ErrorPanel.Visible = true
End If
End Sub

RedirectToRoute is actually wraps Response.Redirect passing false for ending the request - hence, the request continues. You can use HttpApplication.CompleteRequest as immediate call to terminate the request so that next application events would not be invoked.
Response.End (and other Redirect variation) throws ThreadAbortException to abort the request processing thread which is really a bad way to stop request processing. In .NET world, exception processing is always considered expensive because CLR then needs to search up the stack all the way up for exception processing blocks, create stack trace etc. IMO, CompleteRequest was introduced in .NET 1.1 to avoid the same which actually relies on setting flag in ASP.NET infrastructure code to skip further processing except EndRequest event.
Yet another (and better) way is to use Server.Transfer and avoid client round-trip for setting redirect all together. Only issue is that client would not see the redirected URL in the browser address bar. I typically prefer this method.
EDIT
CompleteRequest wouldn't never work in page case where subsequent page events would be still invoked because page being a handler, all its events happens within a single (and current) application event ProcessRequest. So only way seems to be setting a flag and check that flag in overrides such as Render, PreRender, RaisePostBackEvent etc.
From maintenance perspective, it make sense to have such functionality in base page class (i.e. maintaining the flag, offering CompleteRequest method to subclasses and overriding life cycle event methods). For example,
internal class PageBase: System.Web.UI.Page
{
bool _requestCompleted;
protected void CompleteRequest()
{
Context.ApplicationInstance.CompleteRequest();
_requestCompleted = true;
}
protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl,
string eventArgument)
{
if (_requestCompleted) return;
base.RaisePostBackEvent(sourceControl, eventArgument);
}
protected internal override void Render(HtmlTextWriter writer)
{
if (_requestCompleted) return;
base.Render(writer);
}
protected internal override void OnPreRender(EventArgs e)
{
if (_requestCompleted) return;
base.OnPreRender(e);
}
... and so on
}

I may be going out on a limb by not answering the question directly, but I liked seeing your update regarding user experience. I prefer your suggested approach.
I like to give a 410 error for id's that are not valid and extend it a bit with (translated from C#):
Protected Sub ItemDoesNotExist()
'item does not exist, serve up error page
ControlsPanel.Visible = False
ErrorPanel.Visible = True
'add meta tags for noindex
Dim mymeta As New HtmlMeta()
mymeta.Name = "robots"
mymeta.Content = "noindex"
Page.Header.Controls.Add(mymeta)
'RESPOND WITH A 410
Response.StatusCode = 410
Response.Status = "410 Gone"
Response.StatusDescription = "Gone"
Response.TrySkipIisCustomErrors = True
'important for IIS7, otherwise the Custom error page for 404 shows.
Page.Title = "item gone"
End Sub

Related

Can you trust ViewState to handle program control?

I've read a lot about ViewState on MSDN and looked at these questions (among others):
Can malicious users modify viewstate?
How to modify viewstate of a label in ASP.net
I see that if you have EnableViewStateMac turned on, ViewState is signed with a calculated value called a MAC to detect if it's been tampered with during round-trips, and ASP.NET will throw an exception if it detects that the MAC does not match the client's ViewState data.
To me, this means that it is safe unless the private key used to sign the ViewState is somehow reverse-engineered, similar to how SSL cryptography works. Hopefully this is true, but correct me if it's not. I might be missing another piece to how ASP.NET works internally, but it seems to me that you should be able to rely on a control's state to control program execution and flow, since the only way to modify the control's state is in server code based on a postback containing valid changes to the client's form.
The question is: Practically, is it okay to use a control's state (if it is not supposed to be changeable by the user) for programmatic decisions, and what are the possible dangers and how could those cause a practical problem?
Here are two specific examples of what I'm wondering is safe from bypassing via ViewState tampering (this is a mock-up of what I'm doing):
Example 1
Public Sub SetPageState()
If User.IsLoggedIn() Then
MultiView1.ActiveViewIndex = 0 'user is logged in
Else
MultiView1.ActiveViewIndex = 1 'user is not logged in
End If
End Sub
Private Sub PersonalizePage()
If MultiView1.ActiveViewIndex = 0 Then
'Do logged-in stuff
ElseIf MultiView1.ActiveViewIndex = 1
'Do not-logged-in stuff
End If
End Sub
Example 2
Public Sub SetUserLoginControl()
Label1.Visible = User.IsLoggedIn()
End Sub
Private Sub DoLoginThings()
If Label1.Visible Then
'Do logged-in stuff
Else
'Do not-logged-in stuff
End If
End Sub
I realize the "correct" way would be to check for User.IsLoggedIn() (or whatever needs checked) in every place where that's what is supposed to be controlling it, but in some cases the function is computationally expensive, and it's much cheaper to check the state of a control that was modified based on the return value of the expensive function. I realize there are ways around this, such as storing a temporary copy of the function return value, etc., but this is more of a conceptual question than a "here's my problem, now solve it for me" question.
Your code when using controls is unreadable which is bad. You might want to cache state of User.IsLoggedIn() in HttpContect.Current.Items dictionary. It's somewhat a cache that lives for a single request only.

Faking MVC Server.Transfer: Response.End() does not end my thread

I have two issues here, the second one is irrelevant if the first one got answered, but still technically interesting in my opinion... I will try to be as clear as possible:
1st question: my goal is to fake a Server.Transfer in MVC, is there any descent way to do that, I found quite a few articles about it, but most where about redirecting / rerouting, which is not possible in my case (not that I can think of at least).
Here is the context, we have two versions of our website, a "desktop" one and a mobile one. Our marketing guy wants both versions of the home page to be served on the same url (because the SEO expert said so).
This sounds trivial and simple, and it kind of is in most cases, except... Our desktop site is a .NET 4.0 ASPX site, and our mobile site is MVC, both run in the same site (same project, same apppool, same app).
Because the desktop version represents about 95% of our traffic, this should be the default, and we want to "transfer" (hence same url) from the ASPX code behind to the MVC view only if user is on a mobile device or really wants to see the mobile version. As far as I saw so far, there is no easy way to do that (Server.Transfer only executes a new handler - hence page - if there is a physical file for it). Hence question has any one done that in a proper way so far?
And which brings me to:
2nd question: I did build my own transfer to MVC mechanism, but then figured out that a Response.End() does not actually ends the running thread anymore, does anyone have a clue why?
Obviously, I don't expect any answer out of the blue, so here is what I am doing:
in the page(s) which needs transfering to mobile, I do something like:
protected override void OnPreInit(EventArgs e) {
base.OnPreInit(e);
MobileUri = "/auto/intro/index"; // the MVC url to transfer to
//Identifies correct flow based on certain conditions 1-Desktop 2-Mobile
BrowserCheck.RedirectToMobileIfRequired(MobileUri);
}
and my actual TransferToMobile method called by RedirectToMobileIfRequired (I skipped the detection part as it is quite irrelevant) looks like:
/// <summary>
/// Does a transfer to the mobile (MVC) action. While keeping the same url.
/// </summary>
private static void TransferToMobile(string uri) {
var cUrl = HttpContext.Current.Request.Url;
// build an absolute url from relative uri passed as parameter
string url = String.Format("{0}://{1}/{2}", cUrl.Scheme, cUrl.Authority, uri.TrimStart('/'));
// fake a context for the mvc redirect (in order to read the routeData).
var fakeContext = new HttpContextWrapper(new HttpContext(new HttpRequest("", url, ""), HttpContext.Current.Response));
var routeData = RouteTable.Routes.GetRouteData(fakeContext);
// get the proper controller
IController ctrl = ControllerBuilder.Current.GetControllerFactory().CreateController(fakeContext.Request.RequestContext, (string)routeData.Values["controller"]);
// We still need to set routeData in the request context, as execute does not seem to use the passed route data.
HttpContext.Current.Request.RequestContext.RouteData.DataTokens["Area"] = routeData.DataTokens["Area"];
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"] = routeData.Values["controller"];
HttpContext.Current.Request.RequestContext.RouteData.Values["action"] = routeData.Values["action"];
// Execute the MVC controller action
ctrl.Execute(new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData));
if (ctrl is IDisposable) {
((IDisposable)ctrl).Dispose(); // does not help
}
// end the request.
HttpContext.Current.Response.End();
// fakeContext.Response.End(); // does not add anything
// HttpContext.Current.Response.Close(); // does not help
// fakeContext.Response.Close(); // does not help
// Thread.CurrentThread.Abort(); // causes infinite loading in FF
}
At this point, I would expect the Response.End() call to end the thread as well (and it does if I skip the whole faking the controller execution bit) but it doesn't.
I therefore suspect that either my faked context (was the only way I found to be able to passed my current context with a new url) or the controller prevents the thread to be killed.
fakeContext.Response is same as CurrentContext.Response, and the few attempts at ending the fake context's response or killing the thread didn't really help me.
Whatever code is running after the Response.End() will NOT actually be rendered to the client (which is a small victory), as the Response stream (and the connection, no "infinite loading" in the client) is being closed. But code is still running and that is no good (also obviously generates loads of errors when trying to write the ASPX page, write headers, etc.).
So any new lead would be more than welcome!
To sum it up:
- does anyone have a less hacky way to achieve sharing a ASPX page and a MVC view on the same url?
- if not, does anyone have a clue how I can ensure that my Response is really being ended?
Many thanks in advance!
Well,
for whoever is interested, I at least have answer to question 1 :).
When I first worked on that feature, I looked at the following (and very close) question:
How to simulate Server.Transfer in ASP.NET MVC?
And tried both the Transfer Method created by Stan (using httpHandler.ProcessRequest) and Server.TransferRequest methods. Both had desadvantages for me:
the first one does not work in IIS, (because I need to call that in a page, and that seems too late already).
the second one makes it terribly annoying for developers who all need to run their site in IIS (no biggy, but still...).
Seeing that my solution obviously wasn't optimal, I had to come back to the IIS solution, which seems to be the neatest for production environment.
This solution worked for a page and triggered an infinite loop on another one...
That's when I got pointed to what I had lazily discarded as not being the cause: our url redirect module. It uses Request.RawUrl to match a rule, and oh surprise, Server.TransferRequest keeps the original Request.RawUrl, while app.Request.Url.AbsolutePath will contain the transfered-to url. So basically our url rewrite module was always redirecting to the original requested which was trying to transfer to the new one, etc.
Changed that in the url rewriting module, and will hope that everything still works like a charm (obviously a lot of testing will follow such a change)...
In order to fix the developers issue, I chose to combine both solutions, which might make it a bit more of a risk for different behaviors between development and production, but that's what we have test servers for...
so here is my transfer method looks like in the end:
Once again this is meant to transfer from an ASPX page to a MVC action, from MVC to MVC you probably don't need anything that complex, as you can use a TransferResult or just return a different view, call another action, etc.
private static void Transfer(string url) {
if (HttpRuntime.UsingIntegratedPipeline) {
// IIS 7 integrated pipeline, does not work in VS dev server.
HttpContext.Current.Server.TransferRequest(url, true);
}
// for VS dev server, does not work in IIS
var cUrl = HttpContext.Current.Request.Url;
// Create URI builder
var uriBuilder = new UriBuilder(cUrl.Scheme, cUrl.Host, cUrl.Port, HttpContext.Current.Request.ApplicationPath);
// Add destination URI
uriBuilder.Path += url;
// Because UriBuilder escapes URI decode before passing as an argument
string path = HttpContext.Current.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
// Rewrite path
HttpContext.Current.RewritePath(path, true);
IHttpHandler httpHandler = new MvcHttpHandler();
// Process request
httpHandler.ProcessRequest(HttpContext.Current);
}
I haven't done much research, but here's what seems to be happening upon Response.End():
public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
if (this._context.ApplicationInstance != null)
{
this._context.ApplicationInstance.CompleteRequest();
}
}
}
That could at least provide the "Why" (_context.IsInCancellablePeriod). You could try to trace that using your favourite CLR decompiler.

How to tell if Page_PreRender has run?

I'm running a method in an overridden Page OnUnload, but only if the Page_PreRender method has run.
Obviously, I can flip a class-level bool when I'm in Page_PreRender and check it in OnUnload, but if there's a more intrinsic way to tell is Page_PreRender has run, I'd like to use that.
Any ideas?
Thanks for any thoughts.
UPDATE: Let me rephrase my question slightly. I'm looking for the answer to whether there is a simple way, inherent in the Page life cycle, perhaps a property that is set by the ASP.Net frameowork, perhaps something else, that is different after Page_PreRender has run versus when Page_PreRender has not run.
I am currently setting a boolean in Page_PreRender to tell me if it has run. It works, but I don't like this solution if there is a way to accomplish the same thing without adding the extra boolean check. Creating an event that fires during Page_PreRender is the same level of redundancy I'd like to avoid, if possible.
You mention (in your comments on another post) that your problem manifests itself when calling Response.Redirect() because it throws a ThreadAbortException, which leads to your OnPreRender() event not being called. So why not use this instead?:
Response.Redirect("~/SomePage.aspx", false);
The "false" you see there indicates if execution of the page should terminate right there and then. By default, Response.Redirect() uses "true". If you need your OnPreRender() event to run so that your OnLoad() event will have everything it needs, then set it to "false" and just make sure you either jump to the end of your Page_Load() after calling Response.Redirect() or that the code that would execute after it is fine to run.
Maybe you don't like the idea of passing "false" using the overloaded Response.Redirect() method so that's why you didn't go that route. Here is some documentation that may help sway your mind:
Microsoft states that "passing false for the endResponse parameter is recommended" because specifying "true" calls the HttpResponse.End() method for the original request, which then throws a ThreadAbortException when it completes. Microsoft goes on to say that "this exception has a detrimental effect on Web application performance". See here in the "Remarks" section: http://msdn.microsoft.com/en-us/library/a8wa7sdt.aspx
This was posted last year on MSDN:
The End method is also on my “never
use” list. The best way to stop the
request is to call
HttpApplication.CompleteRequest. The
End method is only there because we
tried to be compatible with classic
ASP when 1.0 was released. Classic
ASP has a Response.End method that
terminates processing of the ASP
script. To mimic this behavior,
ASP.NET’s End method tries to raise a
ThreadAbortException. If this is
successful, the calling thread will be
aborted (very expensive, not good for
performance) and the pipeline will
jump ahead to the EndRequest event.
The ThreadAbortException, if
successful, of course means that the
thread unwinds before it can call any
more code, so calling End means you
won’t be calling any code after that.
If the End method is not able to raise
a ThreadAbortException, it will
instead flush the response bytes to
the client, but it does this
synchronously which is really bad for
performance, and when the user code
after End is done executing, the
pipeline jumps ahead to the EndRequest
notification. Writing bytes to the
client is a very expensive operation,
especially if the client is halfway
around the world and using a 56k
modem, so it is best to send the bytes
asynchronously, which is what we do
when the request ends the normal way.
Flushing synchronously is really bad.
So to summarize, you shouldn’t use
End, but using CompleteRequest is
perfectly fine. The documentation for
End should state that CompleteRequest
is a better way to skip ahead to the
EndRequest notification and complete
the request.
I added this line after calling Response.Redirect(), as MSDN suggests, and noticed everything appeared to run the same. Not sure if it's needed with 4.0, but I don't think it hurts:
HttpContext.Current.ApplicationInstance.CompleteRequest();
Update 1
Using "false" in the call to Response.Redirect() avoids the ThreadAbortException, but what about other Unhandled Exceptions that could be thrown on your page? Those exceptions will still cause your problem of OnUnload() being called without OnPreRender(). You can use a flag in OnPreRender() as everyone suggests to avoid this, but if you're throwing Unhandled Exceptions, you've got bigger problems and should be redirecting to an error page anyway. Since Unhandled Exceptions aren't something you plan to throw on every postback, it would be better if you wrapped your OnUnload() logic in a Try-Catch. If you're logging and monitoring your exceptions you will see that an Unhandled Exception was thrown right before logging a NullReference Exception in the OnUnload() event and will know which one to ignore. Because your OnUnload() will have a Try-Catch, it will safely continue processing the rest of the page so you can Redirect to the error page as expected.
Update 2
You should still have your OnUnload() wrapped in a Try-Catch, but I think this is what you're really looking for (remember IsRequestBeingRedirected will be true when calling Response.Redirect or when redirecting to an error page after an Unhandled Exception).:
if (HttpContext.Current.Response.IsRequestBeingRedirected != true)
{
//You're custom OnUnload() logic here.
}
With this, you will know if it is safe (or even worth it) to process your custom logic in the OnUnload() event. I realize I should have probably lead off with this, but I think we learned a lot today. ;)
NOTE: The use of Server.Transfer() will also call the dreaded Response.End(). To avoid this, use Server.Execute() with the preserveForm attribute set to "false" instead:
Server.Execute("~/SomePage.aspx", false);
return;
NOTE: The thing about Server.Execute("~/SomePage.aspx", false); is that IsRequestBeingRedirected will be false, but your OnPreRender() will still execute, so no worries there.
The answer is Yes, you can, but not always :)
According the Reflection code, the ScriptManager class contains the private bool field _preRenderCompleted, which is set to true while handling internal IPage interface PagePreRenderComplete event.
You can use the Reflection to get this field from ScriptManager.GetCurrent(page) resulting object
I am not sure what exactly you mean by this. According to the ASP.NET Page Lifecycle PreRender always runs before Unload. If you perform some if condition inside this PreRender event and you would like to test in the Unload whether the condition was satisfied a boolean field on the page class seems a good idea.
Add trace=true to the page directive.
Set a boolean field in the PreRender event handler, then check if it was set in the Unload event handler.
Create a custom event that fires in the PreRender event.
I don't think there is any sort of state stored because the ASP.NET engine does not really need that, as it knows its state implicitely.
Searching with .NET Reflector, it seems the page render events are raised from this internal System.Web.UI.Page method:
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
You can have a look at it, there is no notion of state. The only information you can get is the trace. If you have access to the Unload event, then you should have access to the trace? or I miss something :-)
Since the trace is in fact a dataset undercovers (see my answer here: Logging the data in Trace.axd to a text/xml file), you maybe could get the information. But
setting trace=true is not recommended in production though...

Redirecting ASP.NET Page

A word to be said. This is the best .NET community ever. Keep it up !
I had a problem lately that I managed to have for it 2 solutions and I am wondering which one should I choose.
Here the issue :
When a user requests my site ( www..com), I am gonna redirect him to (www..com/en) because it is a multilingual site.
I am using .NET routing to to that.
In the Page_Load of the default.aspx page I check if the language in the Routing Collection is available, I don't redirect. If it is not available I redirect to (www.___.com/en). Here is the code :
if (Page.RouteData.Values.Count == 0)
{
if (SessionManager.IsUserAuthenticated)
{
//Redirect To User HomePage for his Main Language
Page.Response.Redirect(UserManager.GetUserMainPageWhenLoggedIn(SessionManager.LoggedUser.LanguageID,true));
}
else
{
Page.Response.Redirect(String.Format("~/{0}", Cultures.en.ToString()), true);
Helpers.SetCulture(Cultures.en.ToString());
}
}
I am using Response.Redirect to do that. Now if I set to End The Response the method parameter, it will throw an exception so I can handle it throught
try
{
this.InitializeLayout();
}
catch (ThreadAbortException ex)
{
}
catch (Exception ex)
{
ExceptionManager.LogException(ex);
}
If I don't end the Response, the page will execute the whole lifecyle, redirect and then do it again which results a double execution of the page.
My main objective is not to execute the page 2 times to minimize processing ( if the site gets hammered by a big traffic). If I end the Response, a ThreadAbortExeption will be thrown and I think this is not good for the site.( I can catch it and not log it).
What do u think guys?
You could avoid throwing the exception by, as you said, passing false to the endResponse parameter. If you want to then avoid executing the page, you could also call HttpContext.Current.ApplicationInstance.CompleteRequest(); which should skip the rest of the code on your page.
Note, however, that postback and render methods will still execute. If you want to avoid that, you must explicitly include code to ignore them. A great example of how to do this by overriding the RaisePostBackEvent and Render methods is here: --> http://www.c6software.com/articles/ThreadAbortException.aspx
You can use the Application_BeginRequest event handler in global.asax file or a HttpHandler to do your redirections. In both cases you will avoid 2 calls being made.
I would also suggest that you use server.Transfer instead of response.redirect.

Handling Request Validation 'silently'

I'm trying to override the onError event handler of a web form to allow "A potentially dangerous Request.Form value was detected from the client" type errors to be handled within the form rather than ending up at the application level error handler.
I found some sample code like this :
protected override void OnError(EventArgs e)
{
// At this point we have information about the error
HttpContext ctx = HttpContext.Current;
Exception exception = ctx.Server.GetLastError();
string errorInfo =
"<br>Offending URL: " + ctx.Request.Url.ToString() +
"<br>Source: " + exception.Source +
"<br>Message: " + exception.Message +
"<br>Stack trace: " + exception.StackTrace;
ctx.Response.Write(errorInfo);
// --------------------------------------------------
// To let the page finish running we clear the error
// --------------------------------------------------
ctx.Server.ClearError();
base.OnError(e);
}
Which satisfactorily catches the error and writes an error message out to the screen but what I really want to do is to be aware of the error when Page_Load fires and so be able to display a 'normal' error message on the webform.
I'm sure there's a good way to do this but I don't know it ! Suggestions ?
(BTW for various reason I don't want to turn off the checking at either form or app level and neither do I wish to rely on Javascript - thanks)
You actually can catch the error at the page level, but it will kill the page lifecycle. So you have to use a trick to get around it. Example:
public override void ProcessRequest(HttpContext context)
{
try
{
base.ProcessRequest(context);
}
catch(HttpRequestValidationException ex)
{
context.Response.Redirect("HandleValidationError.aspx");
}
}
HandleValidationError.aspx can be anything, including a redirection back to the same page (perhaps with a querystring with information regarding the error, e.g. "ContactForm.aspx?error=Invalid+Request")
I think I understand what you want to do, but I'm afraid it might be impossible. When your ASP.NET page performs a postback, a new thread is created on the server to handle the request. Before your page lifecycle even has a chance to begin, the offending XSS is found and an exception is thrown. Once this exception is thrown, you are "evicted" from the ASP.NET page lifecycle and there is no way to re-enter it. At this point, the only thing you can do on the client side is output the error, or redirect to an error page.
What you seem to want to do is catch the exception, write it out somewhere on the page, and continue with the ASP.NET page lifecycle (i.e. restoring the control tree, restoring viewstate, invoking event handlers, etc). The problem is once an unhandled exception is thrown you no longer have access to the ASP.NET page lifecycle. In this particular case, there is nowhere to put a try/catch block because the exception is thrown internally from the ASP.NET lifecycle before your own code is called.
I know you said you don't want to rely on Javascript, but in this case I think using Javascript is the only way to get the behavior you want. You can still keep server-side validation, just in case your users disable Javascript or type some input that your Javascript doesn't handle.
I don't think you'll be able to handle the error in the Page_load event. In the ASP.NET Page Life cycle validation events occur after the page loads.
Maybe you can add a hidden div (<asp:Panel Visible=false ...) that contains your "normal error message". if the OnError event fires display the error message div.
jason

Resources