ASP.NET Common Error Page Best Practises - asp.net

I am working on an ASP.NET web application. I am implementing the logging framework for the entire application.
web application has around 7-8 pages and is a simple CRUD operations web application.
Its an Azure hosted application. Following is the approach i am following for logging and exception handling.
1) Added Try...Catch blocks in the Data Access Layer, and Click events.
2) Upon catching errors, I am propagating the exceptions upto the Globabl.asax leve, and in Application_Error event logging the error into Event Logs and Trace Logs.
3) After this in the Global.asax file I am Redirecting to an Error Page to show a User Friendly Message and link to the failed page.
4) Just wanted to know whether is it a good approach to do this.
Thanks Friends.

Are you actually handling exceptions on the DAL (ie, logging, trying to fix it, etc)? If not, then the try catch serves no purpose other than spinning cycles. The same is true for the click events, but it is not a bad practice to handle errors on the UI, even if you are not truly doing anything with them, as you will divert the user from the ugly error page to your own friendly message.
A single error page works fine, if you truly cannot handle the exception thrown. The upside is time to market, as you write precious little code to avoid showing the user an ugly message. The downside is the user misses context. I am not really up on the one size fits all exception handler, except as a backup (have an error I did not envision that got past my first line handler).
There is a variation of the common error page, if you are handling based on HTTP statuses and that is to use config.
Another pattern is to set up your own base page and have it work as an error handler. You can then set aside a container to fill when an error occurs. This approach works nicely for adding context, as the user still sees part of the page he was on, but you have given an error message, so he knows things have failed. I have seent this pattern used with a user control that is added to the container when an error occurs, but this is a bit more invovled, as you have to set up a table of codes and proper messages to show (which can be buggy in and of itself).

Why not using ASP.NET custom error pages ? You can specify each error page for each status code or you can specify a default redirect.
You can configure this in the web config and you are all set.
<customErrors defaultRedirect="GenericError.htm" mode="On">
<error statusCode="404" redirect="notfound.htm"/>
</customErrors>
You can configure it for showing the custom error pages to all users or only to remote users etc..
http://aspnetresources.com/articles/CustomErrorPages
I totally agree that you should log all errors in your catch block and write it to a log.

It sounds like you're kind of reinventing the wheel here. ASP.NET already includes things to help you achieve the desired result. Unless you need handling logic to cleanup after the errors, I wouldn't use try catch blocks. Have a look at the ASP.NET Health Monitoring Overview for logging errors. As far as presenting a custom error page see How to create custom error reporting pages in ASP.NET by using Visual C# .NET.

I think, You need not to use Exception Handling. Suppose You have a Presentation Layer and a Business Logic Layer/DataAccess Layer.
Upon facing the error in say Business Logic, it will move directly to Glogal.asax.cs file under Application_Error Event without going back to the calling function. Here you can log the error message like below....
HttpContext.Current.Server.GetLastError().InnerException.StackTrace
HttpContext.Current.Server.GetLastError().InnerException.Message
HttpContext.Current.Server.GetLastError().InnerException.Source
HttpContext.Current.Server.GetLastError().InnerException.TargetSite.DeclaringType.FullName
HttpContext.Current.Server.GetLastError().InnerException.TargetSite.DeclaringType.Name
HttpContext.Current.Server.GetLastError().InnerException.TargetSite.DeclaringType.Namespace
Now in the Web Config you can write code to redirect the user on some default page like below.
<customErrors defaultRedirect="ErrorPage.htm" mode="On">
<error statusCode="404" redirect="ErrorPageNotFound.htm"/>
</customErrors>

Related

Intermittent ASP.net IIS8.5 uncatchable 500 internal-server-error on Azure cloud service

Lets start with a little background information. I am running a very simple ASP.net MVC Azure cloud service (a web role, Windows Server 2012 R2 with IIS 8.5). This service receives statistics from a flash client, which posts data roughly every 10 seconds (for potentially very large number of clients) and JavaScript. All the service contains is a single controller with two simple actions with a bunch of parameters (representing the individual statistics which are send in various combinations). All the service does is set the CORS and cookie responses (the clients/JavaScript can be embedded on random domains), verify the integrity of the received data and then store it into an Azure table storage account.
In order to ensure our service operates optimally we use New Relic to track service performance, and in order to ensure that our data is accurate (i.e. we successfully record all received messages) we implemented a custom error handling solution so we can fix any problems/bugs that might arise.
We have load tested our service using jmeter and encountered no problems, but now that we have deployed to a live environment and our service is being used we are starting to encounter occasional 500 internal server errors (approx 5% of requests). The big problem being that our own error handling code is not detecting these errors, however New Relic does report certain requests generating a 500 internal server error (with no further information like a stack trace, sometimes with, sometimes without reported parameters).
Our custom error handling consists of an HTTP module which registers to both the AppDomain.CurrentDomain.UnhandledException and the context.Error events. In theory this should be catching (and then logging) any exceptions which are not already being caught (and logged) inside our own code. Relevant web.config sections are configured in the following manner:
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/500.aspx">
<error statusCode="404" redirect="~/404.aspx" />
<error statusCode="500" redirect="~/500.aspx" />
</customErrors>
and
<httpErrors existingResponse="Replace">
<clear />
<error statusCode="404" path="404.html" responseMode="File" />
<error statusCode="500" path="500.html" responseMode="File" />
</httpErrors>
<modules>
<add type="namespace.UnhandledExceptionModule" name="UnhandledExceptionModule" preCondition="managedHandler" />
</modules>
However, this is not the case. I have tried turning on all kinds of logging but the IIS logs are useless (they only show that a 500 response was returned, but no other useful information). The only useful information I have been able to gather is from the failed request traces, but I have not been able to determine what the actual problem is from that information (googling the error code or exception leads to nothing concrete). A screenshot of the relevant section of a failed trace can be found here:
http://i57.tinypic.com/20acrip.jpg
I also uploaded the complete trace here:
http://pastebin.com/fDt3thvr
Each failed request generates exactly the same log, so the errors we are seeing are consistently being caused by the same problem. However, I am not able to determine what this problem is, let alone find a way of fixing it. Even though I have an error code and message, googling them only returns very old topics on issues that have been fixed 6 years ago.
It is pretty important for our business that these messages can be recorded with a high degree of accuracy, but as it stands now I have no further ideas on how to gain better information on what is happening on these servers. We are also not able to replicate this behavior in a controlled environment.
Also, our error logging itself does work properly. 'Normal' errors are logged as expected and we have also verified the HTTP module actually works.
Edit:
The controller pseudo code is as follows:
[HttpPost]
public ActionResult Method(...)
{
// Set cookie and CORS reponse, check for early out.
if(earlyOut)
return 404;
// Store received values.
azuretable.ExecuteAsync(TableOperation.InsertOrMerge(...));
return 200;
}
Edit2:
I have spend some time analyzing failed request traces and they mostly seem to be generated by users with IE9. I actually managed to reproduce the error 2 times by quickly leaving the page while it is loading, as the problem seems to be caused by aborted Ajax calls (which we make the most of during page load). Why would an aborted call cause a 500 error though instead of being handled neatly?
Do the cookies exceed 4k ? The same thing happened to us on IIS, and the requests sometimes ended up with 500 Internal Server error. The errors were virtually untraceable.
I reproduced the issue by simply inflating a cookie over the 4093 bytes limit.
I think that it is because you are not awaiting your async method call, or your are not returning an awaitable response. I had exactly this issue when I forgot to do that.
await azuretable.ExecuteAsync(TableOperation.InsertOrMerge(...))
Then you should be good. I think you'll find that the async call is finishing after your call has completed back to the caller.

Asp.net Event code 3003

My event log has thousands of entries for code 3003 which says "A validation error has occurred.". I am properly handling the validation on server side and error is caught in global.asax and proper message is shown to the user. I don't want to add code to handle the characters on client side by javascript or turning on ValidateRequest=false
My concern is only the event log entries and is there any way I can stop asp.net not to log event 3003 in event log?
Just add:
<pages validateRequest="false" enableEventValidation="false"/>
To your web.config to apply it to the whole site.
Edit:
Goes in the System.web section.

ASP.NET session has expired or could not be found -> Because the Session.SessionID changes (Reporting Services)

1.-I'm using reporting services and sometimes I get this error ASP.NET session has expired or could not be found when I try to load a report.
2.-I realized that I get this error when the Session.SessionID property changes even though the user is the same. If it does not change, the report is loaded. I mean, if I refresh the report a number of times, whenever the Session.SessionID is the same than the last one, the report is loaded.
3.-Microsoft Documentation says:
When using cookie-based session state, ASP.NET does not allocate
storage for session data until the Session object is used. As a
result, a new session ID is generated for each page request until the
session object is accessed. If your application requires a static
session ID for the entire session, you can either implement the
Session_Start method in the application's Global.asax file and store
data in the Session object to fix the session ID, or you can use code
in another part of your application to explicitly store data in the
Session object.
If your application uses cookieless session state, the
session ID is generated on the first page view and is maintained for
the entire session.
The point is that I can not use a cookieless session state because I need cookies.
What could I do to avoid this error? Or What could I do to avoid the Session.SessionID to change on every request?
You are probably storing your session InProcess. Try changing it to session state server. You can find more details here.
I'm using report viewer 11.0.0; in your web config on system.web section, put the next configuration:
<sessionState timeout ="120" mode="InProc" cookieless="false" />
When you are generating the report (C# code bellow) in the reportviewer object change the KeepSessionAlive property to false and the AsynkRendering property to false, and that's all
this.rvReporte.KeepSessionAlive = false;
this.rvReporte.AsyncRendering = false;
(rvReporte) is a ReportViewer control located on my asp.net Form
This solution work for me, i hope that work for other people.
Regards
<httpCookies httpOnlyCookies="false" requireSSL="false"/>
Solved the problem.
Thanks to :
http://www.c-sharpcorner.com/Blogs/8786/reportviewer-Asp-Net-session-has-expired.aspx
I had the same issue on report viewer page when the web site was accessed from outside intranet. hardvin's suggestion saved the day for me which is to set
this.rvReporte.KeepSessionAlive = false;
this.rvReporte.AsyncRendering = false;
I changed the property on the control itself. I am using report viewer on a user control which raises a custom event for supplying parameters programmatically at the host page instead of prompting the users.
I solved this issue by setting AsyncRendering to false on reportviewer server control
Having the reportviewer being displayed in iframe was giving us this error. If displayed outside of iframe it works nice.
The reportviewer object has this configuration, AsyncRendering = false and KeepSessionAlive = true.
The webapp that has the reportviewer and set the session cookie in the browser was compiled with .net framework 4.6.1. We upgrade to 4.8 and put this in web.config
<sessionState cookieSameSite="None" />
<httpCookies requireSSL="true"/>
Só the solution is from https://learn.microsoft.com/en-us/aspnet/samesite/system-web-samesite#:~:text=The%20updated%20standard%20is%20not%20backward%20compatible%20with,SameSite%3DStrict.%20See%20Supporting%20older%20browsers%20in%20this%20document.
The answer given by Alexsandar is just one of the solution to this problem.
This link clearly explains what is the root cause for this problem and possible solutions:
http://blogs.msdn.com/b/brianhartman/archive/2009/02/15/did-your-session-really-expire.aspx
In case of Brian, the way he has descrived the problem, if he had just a single IIS server, using a session object in his code would have solved the problem because in that case, the SessionID which is passed in the request from browser to the server will get mapped to a corresponding sessionID on the server and hence the session expiry message will not come.
Setting the mode may only work in case of a server cluster where Brian had multiple IIS servers handling the same request. In that case an out of process mode will help to retrieve the session object from the Session Store irrespective of the server hit.
So based on this observation, I would conclude that Brian's problem was not related to cookies but to a server cluster. The information provided by Brian in his question and the subsequent solution misled me and hence this clarification. Hope it helps anyone looking for a similar problem.
Thanks,
Vipul
I have added the below-mentioned line on the web config file and it is working fine for me.
<sessionState mode="InProc" cookieless="true" timeout="3000" />
<pages enableSessionState="false" />
<customErrors mode="Off" />
Try removing SessionState="somevalue" tag from the top of your calling ASPX page. I'm using a custom SessionState and refuse to use InProc since I have multiple instances on Azure. You can even use AsyncRendering=True if you desire. Let me know if this did the trick for you.
For me, it turned out to be having more than one worker process for the app pool.
For me Azure hosted web app turning ON - ARR Affinity fixed the issue.
ARR Affinity ON

Handler with IRequiresSessionState does not extend session timeout

I have a handler, like this, using IRequiresSessionState:
public class MyHandler : IHttpHandler, IRequiresSessionState
{
// code
}
In the code, I am able to read and set Session values. I also check ensure that the caller is logged in. That all works fine.
The web site uses forms authentication, so in the web.config, I have this:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" timeout="10" slidingExpiration="true"></forms>
</authentication>
The problem is that AJAX calls from the client to the server code MyHandler do not extend the life of the session!
So even if the user is busy sending and receiving data from the server, they time out 10 minutes after their last full page load.
In the handler, I've tried specifically changing a value is the session on each call, but not even that extends the time out.
Any suggestions?
Finally got a solution to this... sort of.
I abandoned trying to get the handler to do the job, and instead called a normal ASPX page. In that page, I removed the HTML code, and used Response.Write(...) to send back some JSON that I wanted. However, even with that, my session was not being extended!
I finally realized that in the PreRender event, I used Response.ClearHeaders(). That was the problem. It turns out that the Forms authentication system was updating the session ticket cookie when needed, but was adding it to the Headers before my code was running. So when I cleared the headers, I was erasing the new cookie before it was sent to the browser.
So, if you are having problems with sessions not being extended, check to make sure that your code is not using ClearHeaders()!

How to handle exceptions during an ASP.NET request lifecycle

This question is kind of related to Handle URI hacking gracefully in ASP.NET in that it's too about how to best handle exceptions that occur during an ASP.NET request lifecycle. I've found a way to handle most exceptions gracefully, but then I've found that some exceptions occur so late in the request that there's no way to do stuff like Server.Transfer to compartementalize the whole error presentation logic into its own page.
So, I have to handle the exception inside the Application_Error event instead, and do Response.Writes and whatnot. It's ugly. I understand that in some circumstances the response stream could have already been flushed, so transferring the request isn't really an option. What I want to ask is if there's anyone who's found an elegant solution to this problem?
Also, I find it difficult to know when I can handle the exception gracefully by transferring the request to another page and not. What is the best way to find out where in the request lifecycle we're at when an exception occurs? If it occurs during the loading and rendering of a page, Page_Error will be able to handle it and I've not had a problem doing Server.Transfer there yet. But if the exception occurs either too early or too late for Page_Error to catch it and it bubbles to Application_Error, what do I do to know whether it's early or late?
If it's late in the lifecycle, I'll probably have to do Response.Write directly from Application_Error, but if it's early I can do Server.Transfer. The problem is that trying to do Server.Transfer will itself cause an exception if it's too in the request to do it.
So, is there a global enumeration or something similar that will indicate whether it's too late to do creative stuff with the response or not?
I have used this approach to catch all errors generated, either in web controls or pages. All it requires you to do is to inherit from a base class (one for pages and one for usercontrols) each page or usercontrol can implement its own HandleException method and do whatever it needs to do.
Full code here:
Transparent generic exception handling for asp.net / MOSS2007 (with code)
I think my advice for this would be to use ASP.NET Health Monitoring with WMI Events provider for the errors:
Here is a how to.
http://msdn.microsoft.com/en-us/library/ms178713.aspx
Hope this helps:
Andrew
I suggest that you use asp.net configuration to have a general error page for the unhandled exceptions. From the sample web.config
<!--
The <customErrors> section enables configuration
of what to do if/when an unhandled error occurs
during the execution of a request. Specifically,
it enables developers to configure html error pages
to be displayed in place of a error stack trace.
<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
-->
On the overall handler, just log the exception, and let asp.net do the redirect.
If you still want to go on with your customer approach, I suggest you look at the available asp.net source and check how it is doing that.

Resources