Monday, March 7, 2016

Microsoft 70-486: Design an exception handling strategy

Exam Objectives


Handle exceptions across multiple layers, display custom error pages using global.asax or creating your own HTTPHandler or set web.config attributes, handle first chance exceptions

Quick Overview of Training Materials



Handling exceptions across layers


This is one of those conceptual objectives that doesn't have a clear, canonical answer, so I did my best to distill the answers I found online.  The Exam Ref, while marginally more useful than the other two books for this particular objective, is just a handful of paragraphs, half of which are just explaining the gist of an n-tier application:  UI on top, business layer in the middle, data access layer on the bottom.  UI talks to BL only, BL talks to UI and DAL, DAL talks only to BL.  There is my 15 second primer on N-Tier applications.  Here are the highlights of what Google found for me:

StackOverflow Exception handling in n-tier applications?
Answer 1
  • catch exceptions at top level and log - most information available
  • catch exceptions in other tiers if:
    • exception condition can be handled (connection retry)
    • exception is rethrown with more info
    • exception is translated into more general exception
Answer 2
  • exceptions should be caught in the layer that implements logic to handle them. Usually top layer.
  • all exceptions should be caught and at least logged.  Global exception handler can log otherwise unhandled exceptions
  • don't use catch if you aren't handling exception, just try/finally.  To rethrow exception, just use throw; as this will preserve the stack trace.
  • if rethrowing exception as another type, include the original exception in the InnerException property.  Use meaningful exception types.
Answer 3
  • never throw a general Exception, and don't catch them in any layer below the UI layer.  The UI layer can display a user-friendly message to user.
  • unless wrapping the exception, rethrow with throw; not throw new...
Answer 4
  • examines three scenarios in which exceptions may be thrown:
  • exceptions that result from errors in your code:
    • at least log errors, return default or null values to higher levels so that life can go on
  • exceptions caused by user error:
    • get the exception back to the user; either rethrow or don't catch
  • exceptions you can handle:
    • handle (automatic retry of web service, for example)
Answer 5
  • create unique exception class for each layer.
  • display clear and understandable message to end user
  • Calling code (higher teir) always has more context than lower teir, so only try to handle errors if you can, otherwise let them bubble up to caller.
  • Use Appdomain.UnHandledException to deal with unhandled exceptions globally.
  • When a child call throws an exception, throw a new exception up the chain, including the child exception as the InnerException
  • two comments specifically disagreeing on whether exceptions should be caught, logged, and rethrown, or if they should just be allowed to propagate.  Also disagreeing on whether it is appropriate for business logic to throw exceptions, since the code calling this logic has no way to know if it needs to handle exceptions, arguing that complex return object (class/struct) is a more meaningful contract.

Exceptions in the rainforest
  • Real code is divided into three layers:
    • Adapting the software beneath you (bottom layer)
    • Building pieces of your system (business logic)
    • Combining it all together (integration, UI)
  • When dealing with the bottom layer, you make calls and often get back statuses, i.e. calling a web service, you won't get an exceptions, you will get a response with one of many HTTP status codes.  Based on these codes, your adapting code can raise an exception for error conditions.
  • The middle layer (business layer) generally doesn't throw or catch many exceptions.  Exceptions from bottom layer are mostly passed through to UI.
  • The top, integration layer catches many exceptions.  This makes sense because this layer has all the information about what is actually happening.
  • Argues that exceptions should be used in favor of status returns:
    • exceptions can carry more information
    • keeps the business layer clean
    • exceptions make errors visible
    • exceptions don't clutter the return value

Exam Ref
  • No errors should pass through a layer.  Instead all errors from a lower layer should be caught and either handled or rethrown
  • Some information would not make sense to pass on to the user.  Instead of propogating a SqlException all the way to the UI, the business layer should handle and throw a custom DatabaseException instead.



Creating custom error pages


When exceptions are thrown in the business or data layer of the application, information about the error can bubble up to the UI, leading to the "yellow page of death".  Not only does this detract from the user experience, it can present a very serious security risk, as these error pages can contain detailed information about the implementation of an application.


A much better alternative is to present the user with a custom error page.  In MVC, this can be as simple as adding one attribute to the <system.web> element in the web.config file: <customErrors mode="On" />. Custom errors can be set to "On", "Off" and "RemoteOnly". On and off are pretty self explanitory, while Remote Only will show detailed information to localhost, while custom errors are show to everyone else (this is the default value).  So even if you do nothing, you should see this on the deployed app:




The <customErrors> element can be used to configure which redirects are used.  The defaultRedirect attribute is just that, the default page displayed for errors.  The <error> sub-element can be used to define different redirects based on the statusCode.  So a 404 error can go to a custom "Not Found", while everything else goes to the default error page.

    <customErrors mode="On" defaultRedirect="GenericErrorPage.htm">
      <error statusCode="404" redirect="~/error/notfound"></error>
    </customErrors>



Handle first chance exceptions


Like the Exam Ref, the information I have on first chance exceptions all comes from MSDN, primarily the article AppDomain.FirstChanceException Event

When an exception is thrown in the application, before the Common Language Runtime (CLR) tries to find a handler for the exception, it fires a FirstChanceException event (in most cases).  Handling this event does not handle the exception itself, but does offer an opportunity for logging.  One exception (no pun intended) is for exceptions that indicate corruption of process state, in which case the event is only raised if the event handler has the attributes SecurityCriticalAttibute and HandleProcessCorruptionStateExceptionsAttribute (spellcheck gonna love that one...).  When the process state is corrupted, by default the CLR will not execute any further managed code when such exceptions occur.

The MSDN article points out that because the handler for the FirstChanceException event is invoked any time an exception is raised, it is critical that this handler not raise it's own exceptions. Otherwise, the first chance exception handler will be called recursively, resulting in a stack overflow.  I put together a quickie console app to demonstrate this:

using System;
using System.Runtime.ExceptionServices;
 
namespace FirstChanceExceptions
{
    class Program
    {
        static void Main()
        {
            AppDomain.CurrentDomain.FirstChanceException += FirstChanceHandler;
 
            try
            {
                throw new Exception("Oh Noes!");
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine("Caught the exception! " + ex.Message);
            }
 
            Console.In.ReadLine();
        }
 
        static int count = 0;
        static void FirstChanceHandler(object source, FirstChanceExceptionEventArgs e)
        {
            Console.WriteLine("FirstChanceException event raised in {0}:" + 
                Environment.NewLine + "    {1}",
                AppDomain.CurrentDomain.FriendlyName, e.Exception.Message);
            if(count < 5){
                int level = count;
                count++;
                try 
                {
                    throw new Exception("Recursion #" + level + " Oh Noes!!");
                } catch(Exception ex){
                    Console.Out.WriteLine(level + ": Stack overflow!! It's all burning!" + 
                        " **chaos sound effects**");
                }
                
            }
        }
    }
}

The results show that the recursion would have continued unabated if not for the global counter finally cutting off the flow of new exceptions.  Notice that the fact that the exception was surrounded with a try/catch block didn't matter.  This is because FirstChanceHandler is called the moment the exception is thrown, before the clr even looks for code to handle the exception (catch block):


I tried to execute the code example from the MSDN article on FirstChanceException event, but I got a type loading exception before any of the interesting code ever executes.  One aspect of the exception handling flow that this example was aiming to demonstrate is the fact that first chance exceptions can be handled across Application Domains (AppDomains).  This isn't really the place to get into the nitty gritty of what AppDomains are, but if you are curious (I was), MSDN has a decent article: Application Domains.

No comments:

Post a Comment