Understanding error handling in ASP .NET (WebForms) - c#

I have a Web Form (.NET 4.0) web application built as:
MyApp.Portal (Main portal with the pages, WCF AJAX services, scripts etc.)
MyApp.BusinessLayer (Class library call DataAccess to get data)
MyApp.Services (Class library contains WCF server for server side code)
MyApp.DataAccess (ADO Oracle)
I am trying to following some tutorials on error handling but so far I am getting inconsistent results.
My question is how should I be handling errors in my app?
Should I be wrapping all methods in try/catch?
Should I just wrap the methods in the business layer with the try/catch?
How would I handle errors that occur in the PortalLayer with the WCF Ajax methods?
For example I added the following to my Global.asax file:
void Application_Error(object sender, EventArgs e)
{
// Code that runs when an unhandled error occurs
Exception exc = Server.GetLastError();
if (exc is HttpUnhandledException)
{
if (exc.InnerException != null)
{
exc = new Exception(exc.InnerException.Message);
Server.Transfer(#"\Pages\Error.aspx?handler=Application_Error%20-%20Global.asax", true);
}
}
}
However most of the time this isnt getting called. I just see error an error message show on the screen (Like a glorified alert). Or if this method is called then exc is not HttpUnhandledException so the transfer never takes place.
I also tried this in my web.config but I don't see that this does anything. (If I comment it out I get the same results)
<customErrors mode="On" defaultRedirect="Error.aspx?handler=customErrors%20section%20-%20Web.config">
<error statusCode="404" redirect="ErrorPage.aspx?msg=404&handler=customErrors%20section%20-%20Web.config"/>
</customErrors>
The ultimate goal is to redirect the user to a page (or maybe just nicely display the error) while out the same time either log it in a file or write the error out to a database.

The void Application_Error(object sender, EventArgs e) will get called for exceptions that happen in the main thread when server is processing pages (request to .aspx,.ashx resource).
It won't get called in two scenarios:
you're firing new thread when processing .aspx resource & the exception will happen in that new thread
in web service methods, wcf methods, etc. (i.e. non-.aspx resource)
exception is "bad ass" exception - the kind that corrupts application domain - StackOverflowException or OutOfMemoryException
So for WCF, WebServices it makes sense to wrap all entry points in
try {}
catch(Exception e) {
Logger.Log(e);
throw;
}
statement (note if you have lot of those, invest your time in more generic solution).
Also note - if there is an error on your error page you'll get yellow screen of death (obviously). So I prefer showing static html pages as error page (lowest probability that something will go wrong).
In your error handling code you're not cleaning up the error state, also I'd prefer Redirect to Server.Transfer for error handling. Final snipped looks like:
var ex = Server.GetLastError();
Logger.Log(ex);
Server.ClearError();
Response.Redirect("/error.aspx");
Final note - no need to do error logging by yourself (good programmers are lazy - in terms of "don't reinvent the wheel) - there are plenty of great logging modules like Elmah, Microsoft Enterprise Library or log4net.

Related

Is it possible to change the value returned by Server.GetLastError()?

I have written an HttpModule for ASP.NET which will inspect the current exception (if any) and wrap it in a new exception with a unique identifier. Then I want Elmah to log that wrapping exception.
My module is working and is sitting in front of the Elmah module, however I can't work out how to change the Server.GetLastError() so that my new exception will be logged!
I have tried:
var originalException = context.Server.GetLastError();
var app = (HttpApplication)sender;
var context = app.Context;
context.ClearError();
context.AddError(new WrapperException(originalException));
But doing so makes the context.Error property return null.
In fact ClearError doesn't do what it's advertised to do: "Clears all errors for the current HTTP request."
It doesn't do this. The AllErrors array still contains exceptions; all it does make GetLastError return null.
You cannot set context.AllErrors, nor poke something into the array (it's a copy).
You also cannot throw a new exception: the error page only sees the original exception, and Elmah doesn't even log it.
I'm beginning to think that it's not possible.
If the only reason you want to replace Server.GetLastError() is to make sure that ELMAH logs the right exceptions, there may be a better approach. ELMAH's filtering feature can be used to override the logged exception. To do so, add the following code to your Global.asax.cs file:
void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs args)
{
var httpContext = args.Context as HttpContext;
ErrorLog.GetDefault(httpContext).Log(new Error(new WrapperException(args.Exception)));
args.Dismiss();
}
The ErrorLog_Filtering method is called by ELMAH just before logging any uncaught exceptions to the configured error log. In the example, I pull the information about the error happening (args.Context and args.Exception) and wrap the thrown exception in a new exception (WrapperException). By logging the new exception using the ErrorLog.GetDefault(...).Log(...) method, I make sure that ELMAH doesn't call the ErrorLog_Filtering method recursively (it would if you used ErrorSignal...Raise()). Finally, I dismiss the original exception, to avoid the error being logged twice.

How to troubleshoot simple injector in web api?

I'm using the Simple Injector IoC container in my web api service. Currently, I'm experiencing an error that I'm finding very difficult to investigate.
The container is configured as follows:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.Register<IQueryHandler<LoginQuery, AccountDTO>, LoginQueryHandler>(
Lifestyle.Scoped);
When requests come into the web service, rather than being routed to controllers, they are instead handled by a custom message handler. These handlers are created by inheriting from the DelegatingHandler class (see here for more info).
The custom message handler will then look at the request and invoke a specific handler to handle the request. This is done as follows:
// For the sake of the example (and my testing), I have hard coded the types
// below, but usually these are pulled from the request.
Type handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(typeof(AccountQuery), typeof(AccountDTO));
try
{
// Something does wrong here
dynamic handler = this.handlerFactory.Invoke(handlerType);
// Code execution never reaches this point, exception is not caught
... other code
}
// I have also just tried catching the general exception
catch (TargetInvocationException ex)
{
// do something
}
My current problem is that something goes wrong when trying to invoke the handler from the handler factory (simple injector). When I'm debugging, as soon as I step through that line, I imagine the web service hits an exception, and returns (my client just gets an "Internal Server Error" message). Despite the try/catch, no exceptions are caught, so I have absolutely no idea what is happening. I would think I probably have something configured wrong for simple injector, but I just don't know what (simple injector verifies successfully).
Does anyone have suggestions on what I can do here to figure out what is going on?

catch exceptions from all Pages

I am trying to log all "general errors" from the site I am maintaining, to debug the code-behind, without having to show the detailed error page to the users. The web.config says:
<customErrors mode="RemoteOnly" defaultRedirect="~/GeneralError.html"/>
which works for problems like divide by zero, null-reference exceptions, missing dll, etc. After lots of reading here I found that these are the "unhandled exceptions", but do not reach the handler Application_Error() in global.asax. What does work, on a per-page basis, is:
protected override void OnError(EventArgs e)
{
Exception TheException = Server.GetLastError();
// todo: log the exception to the database
base.OnError(e); //pass the exception to the regular handler
return;
}
But could this be changed to log the unhandled exceptions of all pages with a single method? How can I overwrite, or modify, the base class Page?
I also thought of put logging in the code-behind of GeneralError.aspx, but then there would be no stack trace or exception message, and also any user may call this page directly just to annoy us.
I investigated using some IErrorHandler interface mentioned in some posts, but this should relate to WinForm apps, not to asp.net web apps.
Use the Application_Error method in your Global.asax file.
Inside your Application_Error method implementation call Server.GetLastError(), log the details of the exception returned by Server.GetLastError().
Exception LastError;
String ErrMessage;
LastError = Server.GetLastError();
if (LastError != null)
ErrMessage = LastError.Message;
else
ErrMessage = "No Errors";
Response.Write("Last Error = " + ErrMessage);
See here at MSDN or if you want a complete Microsoft Example at Complete Example for Error Handlers.
Create a new class:
public class MyPage : System.Web.UI.Page
{
protected override void OnError(EventArgs e)
{
Exception TheException = Server.GetLastError();
// todo: log the exception to the database
base.OnError(e); //pass the exception to the regular handler
return;
}
}
and instead of inheriting from System.Web.UI.Page inherit from MyPage on your individual pages.
I am currently studying https://msdn.microsoft.com/en-us/library/aa479332.aspx, found through https://stackoverflow.com/a/138088/1845672 which talks about exactly my problem with the exact same approach: provide a solution to log errors on all pages in a maintainable way without modifying all pages.
The download link on the microsoft site is stale. According to: https://code.google.com/p/elmah/issues/detail?id=64 the sources are moved from the microsoft site to: elmah.googlecode.com/files/GDN-ELMAH-1.0.5527-setup.zip .
Looks like this is a complete solution . . .

What is the equivalent to SoapExtension for JSON WebMethods?

I've a few web methods that I use to call some external services like the Google Calendar API, obviously these can be extremely brittle.
Unfortunately I now realise that any error thrown on these methods are not causing an exception to bubble up to Global.asax which is where errors are getting logged in this application.
I have seen suggestions to wrap the method in a try/catch, which is a stupid way of doing it as there are a variety of errors that ASP.Net will silently swallow still.
In trying to find a solution I've seen a lot of references to SoapExtension, which is exactly what I want to do but doesn't get fired as I'm returning Json. What I really want is a way to catch the error just like that.
Any pointers appreciated, I still can't understand how the ASP.Net team could have thought that silently swallowing errors like this was a bright idea.
So for example a method like this:
[WebMethod]
[ExceptionHandling] //can I write a handler like this to catch exceptions from JSON webservices?
static public void DeleteItem(string id)
{
var api = new GoogleCalendarAPI(User.InternalUser());
api.DeleteEvent(id);
return "success";
}
There is no equivalent to SoapExtension for JSON WebMethods and having custom errors turned on in your production site will result in a generic error message being returned to the client, no error is ever raised on the server. You cannot circumvent this.
If you inspect the code using something like ILSpy, there is no way to pass a method or class to page WebMethods like SoapExtension. The error is swallowed by ASP.Net as it invokes the web method, the only notification you will get is a HTTP 500 error sent to the client with a total generic error message.
In 4.0, WebMethods get called by this:
// System.Web.Script.Services.RestHandler
internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)
{
try
{
//snip irrelevant code
RestHandler.InvokeMethod(context, methodData, rawParams);
}
catch (Exception ex)
{
RestHandler.WriteExceptionJsonString(context, ex);
}
}
So if invoking your method throws an error it will call the following code with a statusCode of 500, there's no re-throw in there and nothing else you can pass in called so unless I'm being blind it just gets swallowed silently. Even worse if you've got custom errors turned on, which any sane person will, it'll completely obfuscate the original cause:
// System.Web.Script.Services.RestHandler
internal static void WriteExceptionJsonString(HttpContext context, Exception ex, int statusCode)
{
//snip code setting up response
context.Response.TrySkipIisCustomErrors = true;
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))
{
if (ex is TargetInvocationException)
{
ex = ex.InnerException;
}
if (context.IsCustomErrorEnabled)
{
streamWriter.Write(JavaScriptSerializer.SerializeInternal(RestHandler.BuildWebServiceError(AtlasWeb.WebService_Error, string.Empty, string.Empty)));
}
else
{
streamWriter.Write(JavaScriptSerializer.SerializeInternal(RestHandler.BuildWebServiceError(ex.Message, ex.StackTrace, ex.GetType().FullName)));
}
streamWriter.Flush();
}
}
I can't see a way around it, looks like WebMethod is not ready for production code, shame.
It's not so much they get disappeared, it's more that they get passed out to the calling client. Since however you don't always want to (or should) reveal such intimate details of your service, you can prevent errors bubbling out of your service. This gives the impression of them disappearing.
Wrapping the inner detail in a try-catch is about the best way to cope with any errors. Within the method you're dealing with standard error trapping. So I think you'd want something like:
[WebMethod]
static public string DeleteItem(string id)
{
try
{
var api = new GoogleCalendarAPI(User.InternalUser());
api.DeleteEvent(id);
return "success";
}
catch(Exception ex)
{
log.fatal(ex);
return "error";
}
}
If anything throws an exception within the try-catch it'll be caught. ASP.Net won't interfere with it, unless the methods you are calling have been specifically coded to do so.
Edit
If the GoogleCalendarAPI class is in turn calling a method, such as ExecuteWebServiceCall with catches the Exception, then you'd have to parse the response. I'd hope they gave you some other clue, like a response code, to indicate an error state. You could then wrap that in an Exception, throw it have it caught by your default error handler.

How to Reach the Custom Errors page in ASP.NET when Session State Provider is Down?

I am trying to show a customer errors page in ASP.NET when the database is down. I use the SQL Server mode to hold the session data. The problem is that the custom errors page is never called.
Since the session data and the database are on the same server, this does not redirect to the custom error page? I’m guessing the web application has not loaded at this point?. The user is presented with the stack trace for the session state connection failure.
It seems that we need something that sits in front of the initial website load to check connectivity to the database. Any ideas on how to implement this?
Add something like this to your web.config?
<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
You can read more information here
If it is your SqlSessionState that is failing, you should handle the corresponding error in the Application_Error event in Global.asax
You can read more information here
I believe the error is coming from the fact that because you're using an out of memory session state provider (being the database), and the database connection has a failure, then there is actually a perceived error in the web configuration (not in the application). I have a similar problem, where I'm using AppFabric Cache for my session state provider, but when the AppFabric Cache Service is down, I get the Configuration Error page.
Because of this, you can't use the customErrors solution as FlyingStreudel has already suggested, since it isn't an error in your application, but rather in the loading of the configuration.
I've looked around for a way to solve this, but couldn't find any. I hope this question gets answered, it's got me confused already with the various error configuration options...
Update: After investigating this for a while now, it appears that my issue comes from the fact that the SessionStateModule causes the AppFabric cache session state provider to try and connect to the DataCache (which is not available), and an exception (probably timeout) is thrown somewhere. Because this happens in the Init of the HTTP module, there seems to be no way around the yellow screen of death.
I wouldn't be surprised if the original poster's problem is the same - the connection to the SQL server occurring in the initialization of the SessionStateModule.
Because the error page is an ASP.NET Page ( I can see this from your comment), It will hit the session database in page life cycle.
You have to set below directive on Error.aspx Page to tell ASP.Net not to load session information for it:
EnableSessionState="false"
Please note this will only work if you are not using any session information in the error page.
Additionally, I also managed Global.asax page as below :
private static Exception startException;
protected void Application_Start()
{
try
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelBinders.Binders.Add(typeof(DateTime), new MyDateTimeModelBinder());
}
catch (Exception ex)
{
startException = ex;
}
}
static HashSet<string> allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".js", ".css", ".png",".jpg",".jpeg",".gif",".bmp"
};
public bool IsStaticFile(HttpRequest request)
{ //My project was a mix of asp.net web forms & mvc so had to write this
if (Request.RawUrl.ToLower().Contains("/bundles/") || Request.RawUrl.ToLower().Contains("/frontendcss?") ||
Request.RawUrl.ToLower().Contains("/fonts/")
//these are my css & js bundles. a bit of hack but works for me.
)
{
return true;
}
string fileOnDisk = request.PhysicalPath;
if (string.IsNullOrEmpty(fileOnDisk))
{
return false;
}
string extension = Path.GetExtension(fileOnDisk).ToLower();
return allowedExtensions.Contains(extension);
}
protected void Application_BeginRequest()
{
if (startException != null && Request.RawUrl.ToLower() == "/Error.aspx".ToLower())
{
return;
}
if (startException != null && IsStaticFile(Request))
{
return;
}
if (startException != null && Request.RawUrl.ToLower()!= "/Error.aspx".ToLower())
{
//Response.Redirect("Error.aspx");
Server.Transfer("Error.aspx");
this.Context.AddError(startException);
return;
}
}
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
Server.Transfer("Error.aspx");
}

Categories