JavaScript Error Publishing using ASP.NET AJAX
Update: Source Code Stored Here
Let’s face it, as good of programmers that we think we are, we make mistakes. If we didn’t then all my email site would never be done for maintenance.
Now that we’ve set our egos aside, let’s talk about error handling. We’ve been handling errors on the server in a variety of ways for years. There are try/catch blocks, the generic error event at the application level, and other mechanisms for capturing and handling errors on the server. Only managing server errors was a generally accepted and okay concept as older applications used to be all about the server. However, as we continue to move more functionality to the client through JavaScript and the AJAX progamming pattern we’re shifting the balance of tasks from server-oriented to a client-oriented. By doing this we are creating more complex, dynamic applications that execute on the client,which is good for the end user, but as a result of this we have more errors occurring on the client. It just follows, the more complex code gets the more error-prone it becomes. It’s just an unfortunate fact of programming.
So while we’ve been paying attention to errors on the server for years we haven’t been paying attention to errors on the client as closely. This is something that has to change in order for us to ensure that our client code is not blowing up on user’s machines. Given that, we need a way of publishing client errors back to the server so they can be logged. To accomplish this, we’re going to create a new control that encapsulates logging client errors back to the server. It’s not a difficult concept and for this first pass (we’re going to take a second stab at this in a few weeks) we can accomplish this without too much complexity.
ErrorHandler Client Component
/// <reference name="MicrosoftAjax.js"/> ErrorHandler = function ErrorHandler() { ErrorHandler.initializeBase(this); }; ErrorHandler.prototype = { initialize: function ErrorHandler$initialize() { ErrorHandler.callBaseMethod(this, 'initialize'); window.onerror = Function.createDelegate(this, this._unhandledError); }, dispose: function ErrorHandler$dispose() { window.onerror = null; ErrorHandler.callBaseMethod(this, 'dispose'); }, _unhandledError: function ErrorHandler$_unhandledError(msg, url, lineNumber) { try { var stackTrace = StackTrace.createStackTrace(arguments.callee); ErrorDataService.PublishError(stackTrace, msg, url, lineNumber); } catch (e) { } } } ErrorHandler.registerClass('ErrorHandler', Sys.Component); if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
This is a pretty straight-forward client component. When it initializes it adds an event handler to the window’s onerror event. (Notice that we use direct assignment here because using the $addHandler method doesn’t work on the onerror event.)
When an error occurs our _unhandledError method executes and we have three parameters available: the error message (msg), the URL of the page where the error occurred (url), and the line number where the error occurred (lineNumber). We create another variable, stackTrace that uses our previously defined StackTrace object to create the stack trace information for the erroring call.
Once we have our stackTrace we publish our error’s information using a web service method called ErrorDataService.PublishError. We also wrap the entire method in a try/catch block because we don’t want another to get stuck in an infinite loop if we have another error occur inside our error handing code!
ErrorDataService Web Service
using System.Web.Script.Services; using System.Web.Services; [ScriptService] public class ErrorDataService : WebService { [WebMethod] public bool PublishError(string stackTrace, string message, string url, int lineNumber) { // do whatever you want at this point. Log it, create an exception, etc. return true; } }
Once the error’s information reaches the backend its really up to you what to do with it. You could log it to the EventViewer, create a database record, or email somebody … it’s really up to you.
Now that we’ve got our web service defined that’s go back to the server portion of our ErrorHandler control.
ErrorHandler Server Control
using System.Collections.Generic; using System.Web.UI; using System; namespace ErrorHandlerLibrary { public class ErrorHandler : ScriptControl { protected override IEnumerable<ScriptReference> GetScriptReferences() { yield return new ScriptReference("ErrorHandlerLibrary.ErrorHandler.js", typeof(ErrorHandler).Assembly.FullName); } protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() { yield return new ScriptComponentDescriptor("ErrorHandler"); } protected override void OnPreRender(System.EventArgs e) { base.OnPreRender(e); ScriptManager.GetCurrent(this.Page).Services.Add(new ServiceReference("~/ErrorDataService.asmx")); } protected override void OnInit(System.EventArgs e) { base.OnInit(e); if (Page.Items.Contains(typeof(ErrorHandler))) { throw new InvalidOperationException("Only one ErrorHandler control is allowed on the page at a time."); } Page.Items.Add(typeof(ErrorHandler), this); } } }
In the server portion of our ErrorHandler control we do five things:
- We inherit from ScriptControl. This wires our control up into the ASP.NET AJAX server control creation processing.
- We register a ScriptReference for the JS file that contains our client ErrorHandler control.
- We create an instance of our ErrorHandler client component using a ScriptComponentDescriptor.
- We do something a little special and we add a ServiceReference to our ErrorDataService. This is a little abnormal as this is normally done through markup at the page level. However, we’re going for encapsulation here, right?? So we want to encapsulate the need for a the ErrorDataService web service within the control. There is a huge caveat with this approach, though. If the control is added to the control’s collection while in a partial postback (i.e. an UpdatePanel), the ServiceReference will not be added to the page. This is extremely annoying and in my opinion a large oversight by Microsoft. I asked them about this once and their response was that they never planned on a ServiceReference being used this way… wow …
- We ensure that our page only ever has one ErrorHandler control on it at any given time. We do this using the same approach taken by the ScriptManager control.
Finally, we edit the AssemblyInfo.cs file to register our JavaScript file as a WebResource (not shown).
That is all we need to do to build our ErrorHandler control. Let’s build a little test page that shows it in action.
Test Page
The code below shows the important portion of our test page.
<asp:ScriptManagerID=”SM1″runat=”server”>
<Scripts>
<asp:ScriptReferencePath=”~/StackTrace.js” />
</Scripts>
</asp:ScriptManager>
<cc1:ErrorHandlerID=”ErrorHandler1″runat=”server” /><asp:Buttonrunat=”server”OnClientClick=”tester();”Text=”Test Client Error Handling” />
<scripttype=”text/javascript”>
functiontester() {
var test = null;
test.err = 4;
};
</script>
When we click our button we execute the tester method, which causes a null exception to occur. When this happens our ErrorHandler control jumps into action! Seeing it in action is actually hard to see because if we attach a debugger to the client it’ll break on the error and we’ll never get to our onerror handler. So, we turn to Fiddler! The picture below shows Fiddler once our error method kicks off.
The information in the top-right pane shows us the information we passed in to the call. The information in the bottom-right pane shows us that our method returned the value “true.”
So that’s really it. With our ErrorHandler control we can trap unhandled JavaScript errors and publish the information about them back to the server getting more insight into the errors that our users are experiencing at the browser level.
BTW: I’m looking for a place to post ZIP files for the source code as WordPress doesn’t allow .zip extensions to be uploaded. If anybody’s got a good spot, pass it along
February 17, 2008 at 11:19 am
[...] Stack Traces in ASP.NET AJAX and JavaScript Error Publishing using ASP.NET AJAX: Joel Rumerman has put together two nice posts that detail some god ways to capture JavaScript [...]
February 17, 2008 at 11:49 am
Great Blog, im getting into the asp.net AJAX thing and this place is great reference.
For file storage you can try: http://www.mediafire.com/
or else you can also publish your entry in a place like codeproject and post the source code there.
regards
February 17, 2008 at 2:33 pm
Thanks Francisco! I like MediaFire. Pretty nice site. I’ll see how it works and probably update this post with full source.
February 17, 2008 at 9:50 pm
have you thought of putting it on Code Gallery (http://code.msdn.microsoft.com/)?
February 17, 2008 at 10:25 pm
It’s a sad day when I have to admit I’d never even heard of the Code Gallery before… and I thought I knew most everything
It looks like an excellent choice for posting source code.
Thanks!