Contents tagged with ASP.NET

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 5 – (JavaScript)

    Tags: ASP.NET, MVC, Logging, Error Handler, Elmah, javascript

    Now we need a way to track those pesky client side error messages. Tracking the server side errors is the easy part, but what about those browser errors that go unnoticed and unfixed?

    First, create the Server Side Handler

    To handle and log JavaScript errors, we'll first need to setup some server side code. The client (browser) will pass these errors to the server, which will hand them off to ELMAH for tracking. So let's start by creating a new Exception type for our JavaScript exceptions. This will allow us to differentiate the server errors from the JavaScript errors more easily. It doesn't need much so this should be fine...

    public class JavaScriptException : Exception
    {
        public JavaScriptException(string message) : base(message)
        {
        }
    }
    

    We are also going to need a Controller to receive the exception and hand it off to ELMAH.

    public class ErrorController : Controller
    {
        public void LogJavaScriptError(string message)
        {
            ErrorSignal
                .FromCurrentContext()
                .Raise(new JavaScriptException(message));
        }
    }
    

    That's it for the server side changes. Next we need to setup the client (browser) to actually handle these errors and pass them onto the controller.

    JavaScript stack trace

    First we need to implement a JavaScript Stack Trace. This will help us get more debugging information and context surrounding the errors. So copy and paste this stacktrace.js file to your project...

    // FROM: http://helephant.com/2007/05/diy-javascript-stack-trace
     
    function logError(ex, stack) {
        if (ex == null) return;
        if (logErrorUrl == null) {
            alert('logErrorUrl must be defined.');
            return;
        }
     
        var url = ex.fileName != null ? ex.fileName : document.location;
        if (stack == null && ex.stack != null) stack = ex.stack;
     
        // format output
        var out = ex.message != null ? ex.name + ": " + ex.message : ex;
        out += ": at document path '" + url + "'.";
        if (stack != null) out += "\n  at " + stack.join("\n  at ");
     
        // send error message
        $.ajax({
            type: 'POST',
            url: logErrorUrl,
            data: { message: out }
        });
    }
     
    Function.prototype.trace = function()
    {
        var trace = [];
        var current = this;
        while(current)
        {
            trace.push(current.signature());
            current = current.caller;
        }
        return trace;
    }
     
    Function.prototype.signature = function()
    {
        var signature = {
            name: this.getName(),
            params: [],
            toString: function()
            {
                var params = this.params.length > 0 ?
                    "'" + this.params.join("', '") + "'" : "";
                return this.name + "(" + params + ")"
            }
        };
        if (this.arguments)
        {
            for(var x=0; x < this.arguments.length; x++)
                signature.params.push(this.arguments[x]);
        }
        return signature;
    }
     
    Function.prototype.getName = function()
    {
        if (this.name)
            return this.name;
        var definition = this.toString().split("\n")[0];
        var exp = /^function ([^\s(]+).+/;
        if (exp.test(definition))
            return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
        return "anonymous";
    }
    
    window.onerror = function (msg, url, line) {
        if (arguments != null && arguments.callee != null && arguments.callee.trace)
            logError(msg, arguments.callee.trace());
    }
    

    Finally, add the Client Side Handlers

    Now we need add a reference the script in the template file and also initialize the JavaScript variable 'logErrorUrl' so errorhandler.js knows where to post the error details to.

    <script type="text/javascript">
      var logErrorUrl = '@Url.Action("LogJavaScriptError", "Error")';
    </script>
    <script src="@Url.Content("~/Scripts/errorhandler.js")" type="text/javascript"></script>
    

    That should be everything. All JavaScript errors should now be tracked via ELMAH.

    Testing the Error Handler

    If you want to test this, just to make sure it all works (which of course you do), then you can use this code to run some tests...

    <script type="text/javascript">
      function getPropertyData(value) {
        var x = value.property["data"];
      }
    
      function testCapturedError() {
        var x = getPropertyDataCaptured(null);
      }
    
      function getPropertyDataCaptured(value) {
        try {
          var x = value.property["data"];
        } catch (err) {
          if (arguments != null && arguments.callee != null && arguments.callee.trace)
              logError(err, arguments.callee.trace());
        }
      }
    </script>
    
    <ul>
    <li><a href="javascript:getPropertyData(null);">execute getPropertyData(null) method.</a></li>
    <li><a href="javascript:testCapturedError();">execute testCapturedError() method.</a></li>
    </ul>
    
    

    This page contains two exception tests. The first test will execute an unhandled exception. The second test will demonstrate a captured error (which you should always be doing) and stack trace and log it to ELMAH. Now when you check your ELMAH page, you should see the JavaScript exceptions show up like this...

    Elmah JavaScript Error

    Download the full project:

    ElmahAndMvc3.zip

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 4 - (HandleErrorAttribute)

    Tags: ASP.NET, MVC, Logging, Error Handler, Elmah

    Creating a custom HandleErrorAttribute for ELMAH (Optional)

    Remember when we commented out that line from our Global.asax.cs in Part 1? Well, building our own HandleErrorAttribute will allow us to put it back or more importantly prevent the HandleErrorAttribute from overriding our ELMAH behavior. Currently the HandleError attribute will prevent any errors from bubbling up to ELMAH and we can't guarantee down the line, someone won't use a HandleError attribute like this...

    [HandleError]
    public class MyClass
    {
        // This error won't be handled by ELMAH
        public MyMethod() {
            throw new Exception("oops");
        }
    }
    

    The solution to this problem is to implement our own custom HandleErrorAttribute. Add the following class to your project...

    using System;
    using System.Web;
    using System.Web.Mvc;
    using Elmah;
    
    namespace ElmahAndMvc3
    {
        public class ElmahHandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
        {
            public override void OnException(ExceptionContext context)
            {
                base.OnException(context);
    
                var e = context.Exception;
                if (!context.ExceptionHandled   // if unhandled, will be logged anyhow
                    || RaiseErrorSignal(e)      // prefer signaling, if possible
                    || IsFiltered(context))     // filtered?
                    return;
    
                LogException(e);
            }
    
            private static bool RaiseErrorSignal(Exception e)
            {
                var context = HttpContext.Current;
                if (context == null)
                    return false;
                var signal = ErrorSignal.FromContext(context);
                if (signal == null)
                    return false;
                signal.Raise(e, context);
                return true;
            }
    
            private static bool IsFiltered(ExceptionContext context)
            {
                var config = context.HttpContext.GetSection("elmah/errorFilter")
                             as ErrorFilterConfiguration;
    
                if (config == null)
                    return false;
    
                var testContext = new ErrorFilterModule.AssertionHelperContext(
                                          context.Exception, HttpContext.Current);
    
                return config.Assertion.Test(testContext);
            }
    
            private static void LogException(Exception e)
            {
                var context = HttpContext.Current;
                ErrorLog.GetDefault(context).Log(new Error(e, context));
            }
        }
    }
    

    And modify the RegisterGlobalFilters in your Global.asax.cs...

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ElmahHandleErrorAttribute());
    }
    

    Now you have no worries about [HandleError] intercepting errors on your site.

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 5 – (JavaScript) »

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 3 – (Filtering)

    Tags: ASP.NET, MVC, Logging, Error Handler, Elmah

    Filtering Unwanted Errors

    Ok, the reason I really like receiving an email for the every error message is because it will get annoying really fast. This results in me fixing the errors really fast. But there are just some errors that you just can't (or don't want to) "fix". If you noticed in Part 1, the error page contained some 404 errors for /favicon.ico. Errors like these will get annoying really quickly, and fortunately there's an easy way to filter them out. This chunk of code will filter out all 404 errors with a URL matching /favicon.ico.

    <system.web>
      <httpModules>
        <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
      </httpModules>
    </system.web>
    
    <system.webServer>
      <modules>
        <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
      </modules>
    </system.webServer>
    
    <elmah>
      <errorFilter>
        <test> <!-- do not log favicon.ico 404's -->
          <and>
            <equal binding="HttpStatusCode" value="404" type="Int32" />
            <regex binding="Context.Request.ServerVariables['URL']"
                   pattern="/favicon\.ico(\z|\?)" />
          </and>
        </test>
      </errorFilter>
    </elmah>
    

    Filtering 404 errors out of the ErrorMailModule

    Some errors are less important than others, for example 404 errors. I want to log all the 404's my site may receive, but I do not want an email every time someone mistypes a url on my site. To keep logging all errors (including 404's), but filter some from being sent via email, we can add this method to our Global.asax.cs file.

    public void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e)
    {
        var httpException = e.Exception as HttpException;
        if (httpException != null && httpException.GetHttpCode() == 404)
        {
            e.Dismiss();
        }
    }
    

    The ELMAH filtering can do a lot of cool stuff, you can even write scripts in JScript, but for the most part, the example above will be enough to do what you want. For more on filtering go to the wiki page.

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 4 – (HandleErrorAttribute) »

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 2 – (Notifications)

    Tags: Elmah, ASP.NET, MVC, Logging, Error Handler

    Setup Notifications for ELMAH Errors

    Now that ELMAH is setup, you need a way to be notified when an error occurs. Though you can just subscribe to your ELMAH RSS feed at /elmah.axd/rss, I prefer to receive an email notification as soon as an error happens. To do this simply merge the following changes into your web.config's <configuration> section.

    <system.web>
      <httpModules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      </httpModules>
    </system.web>
    </pre>
    
    <system.webServer>
      <modules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      </modules>
    </system.webServer>
    
    <elmah>
      <errorMail from="e@mail.address"
                 to="e@mail.address"
                 subject="elmah error mail" />
    </elmah>
    

    There's a lot more configurability, but those are the basics. Here's a good article showing more options. Also if you haven't already done it, set your smtp settings too...

    <system.net>
      <mailSettings>
        <smtp>
          <network host="localhost" />
        </smtp>
      </mailSettings>  
    </system.net>
    

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 3 – (Filtering) »

    This Post is Part of a Multi-Part Series

  • Getting ASP.NET MVC 3 working on DiscountASP.net

    Tags: ASP.NET, DiscountASP.net, MVC

    Though this article should apply to any IIS7 shared-hosting platform, I can only confirm it works on DiscountASP.net.

    Getting Errors After Deploying your Application?

    After deploying your application, you might see the following error message...

    Could not load file or assembly 'System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

    This is due to the MVC binaries not being installed in the GAC on the server. But do not fret, the GAC is only optional for MVC 3!

    Adding References to the Assemblies

    MVC 3 References

    Add references to all of the following ASP.NET MVC 3 Assemblies.

    • Microsoft.Web.Infrastructure
    • System.Web.Abstractions
    • System.Web.Helpers
    • System.Web.Mvc
    • System.Web.Razor
    • System.Web.WebPages
    • System.Web.WebPages.Deployment
    • System.Web.WebPages.Razor

    MVC 3 Copy Local


    Finally set them all to Copy Local = True. Now when you deploy all the required MVC libraries will be included.


    EDIT: You can also use Deployable Dependencies.