Exception Filtering in C#

What do you do when you have a piece of code that can fail, and when it fails, you need to log to a database? You wrap your code in a try-catch block and chuck a Log call in the catch block. That’s all good! What if I tell you that there is a better way to do it?

try
{
    // Code that might fail
}
catch(Exception ex)
{
    // Handle
    // Log to database
}

What’s the problem with the typical approach?

When your code enters a catch block – the stack unwinds. This refers to the process when the stack goes backwards / upwards in order to arrive the stack frame where the original call is located. Wikipedia can explain this in a bit more detail. What this means is that we might lose information with regards to the original stack location and information. If a catch block is being entered just to log to the database and then the exception is being re-thrown, this means that we’re losing vital information to discover where the issue exists; this is especially true in release / live environments.

What’s the way forward?

C# 6 offers the Exception Filtering concept; here’s how to use it.

try
{
    //Code
}
catch (FancyException fe) when (fe.ErrorCode > 0)
{
    //Handle
}

The above catch block won’t be executed if the ErrorCode property of the exception is not greater than zero. Brilliant, we can now introduce logic without interfering with the catch mechanism and avoiding stack unwinding!

A more advanced example

Let’s now go and see a more advanced example. The application below accepts input from the Console – when the input length is zero, an exception with code 0 is raised, else an exception with code 1 is raised. Anytime an exception is raised, the application logs it. Though, the exception is only caught if only if the ErrorCode is greater than 0. The complete application is on GitHub.


class Program
{
    static void Main(string[] args)
    {
        while (true)
        {
            new FancyRepository().GetCatchErrorGreaterThanZero(Console.ReadLine());
        }
    }
}

public class FancyRepository
{
    public string GetCatchErrorGreaterThanZero(string value)
    {
        try
        {
            return GetInternal(value);
        }
        catch (FancyException fe) when (LogToDatabase(fe.ErrorCode) || fe.ErrorCode > 0)
        {
            throw;
        }
    }

    private string GetInternal(string value)
    {
        if (!value.Any())
           throw new FancyException(0);

        throw new FancyException(1);
    }

    private bool LogToDatabase(int errorCode)
    {
        Console.WriteLine($"Exception with code {errorCode} has been logged");
        return false;
    }
}

 

1st Scenario – Triggering the filter

In the first scenario, when the exception is thrown by the GetInternal method, the filter successfully executes and prevents the code from entering the catch statement. This can be illustrated by the fact that Visual Studio breaks in the throw new FancyException(0); line rather than in the throw; line. This means that the stack has not been unwound; this can be proven by the fact that we can still investigate the randomNumber value. The Call Stack is fully preserved – we can go through each frame and investigate the data in each call stack.

1

2nd Scenario – Triggering the catch

In the second scenario, when the exception is thrown by the GetInternal method, the filter does not handle it due to the ErrorCode is greater than 0. This means that the catch statement is executed and the error is re-thrown. In the debugger, we can see this due to the fact that Visual Studio break in the throw; line rather than the throw new FancyException(1); line. This means that we’ve lost a stack frame; it is impossible to investigate the randomNumber value, since the stack has been unwound to the GetCatchErrorGreaterThanZero call.

2catch

What’s happening under the hood?

As one can assume, the underlying code being generated must differ at an IL level, since the stack is not being unwound. And one would assume right – the when keyword is being translated into the filter instruction.

Let’s take two try-catch blocks, and see their equivalent IL.


try
{
    throw new Exception();
}
catch(Exception ex)
{

}

Generates

 .try
 {
     IL_0003: nop
     IL_0004: newobj instance void [mscorlib]System.Exception::.ctor()
     IL_0009: throw
 } // end .try
 catch [mscorlib]System.Exception
 {
     IL_000a: stloc.1
     IL_000b: nop
     IL_000c: nop
     IL_000d: leave.s IL_000f
 } // end handler

The next one is just like the previous, but it introduces a filter to check on some value on whether it’s equivalent to 1.


try
{
    throw new Exception();
}
catch(Exception ex) when(value == 1)
{

}

Generates

.try
 {
     IL_0010: nop
     IL_0011: newobj instance void [mscorlib]System.Exception::.ctor()
     IL_0016: throw
 } // end .try
 filter
 {
     IL_0017: isinst [mscorlib]System.Exception
     IL_001c: dup
     IL_001d: brtrue.s IL_0023
     IL_001f: pop
     IL_0020: ldc.i4.0
     IL_0021: br.s IL_002d
     IL_0023: stloc.2
     IL_0024: ldloc.0
     IL_0025: ldc.i4.1
     IL_0026: ceq
     IL_0028: stloc.3
     IL_0029: ldloc.3
     IL_002a: ldc.i4.0
     IL_002b: cgt.un
     IL_002d: endfilter
 } // end filter
 { // handler
     IL_002f: pop
     IL_0030: nop
     IL_0031: nop
     IL_0032: leave.s IL_0034
 } // end handler

Although the second example generates more IL (which is partly due the value checking), it does not enter the catch block! Interestingly enough the filter keyword, is not available in C# directly (only available through the use of the when keyword.

Credits

This blog post would have been impossible if readers of my blog did not provide me with the necessary feedback. I understand that the first version of this post was outright wrong. I’ve taken feedback received from my readers and changed it so now it delivers the intended message. I thank all the below people.

Rachel Farrell – Introduced me to the fact that the when keyword generates the filter IL rather than just being syntactic sugar.

Ben Camilleri – Pointed out that when catching the exception, the statement should be throw; instead of throw ex; to maintain the StackTrace property properly.

Cedric Mamo – Pointed out that the logic was flawed and provided the appropriate solution in order to successfully demonstrate it using Visual Studio.

Until the next one!

Handling uncaught exceptions in a SharePoint environment through customErrors in web.config

Handling exceptions in SharePoint environment is quite straightforward; it’s a out-of-the-box feature. Any unhandled exception is caught by the SharePoint environment and a pretty “Sorry, something went wrong” screen is shown.

Sorry, something went wrong exception
Generic error when an unhandled exception is caught

That’s all great! Things can get a bit tougher if exceptions occur outside the SharePoint context. How can this happen? In my case, it can happen when custom code is being loaded through an HttpContext, and some DLL might fail to load. In this case, there is no SharePoint context to catch the uncaught exception, thus it remains as an unhandled exception. By default, unhandled exceptions generate the Yellow Screen of Death.

Yellow Screen of Death
Yellow Screen of Death – Source

That’s not something we should be displaying, right? Of course not! First and foremost, we’re showing the actual code to the public! That’s never good. Even if we manage to turn off the details in such exceptions, it’s still terrible practice to show such screen to public.

Alright then, let’s use some custom page to show our error! This is where it gets a bit trickier. Sadly enough, we cannot safely and reliably meddle with the Global.asax file, to globally catch errors and handle them nicely. We need to thing of something else.

Whilst I was trying to tackle this issue, my obvious guess was to amend the web.config – more specifically the customError section. My first attempt at solving this was by creating a simple custom error page, putting it in my IIS folder and configuring IIS to load this page in the customError section. My web.config looked something like this:

<customErrors defaultRedirect="errorPage.html" mode="On" />

After trying that out, I was still stuck with the same Yellow Screen of Death. What was happening? It so happens that when an error is thrown, indeed a redirect to my custom page happens. But when this page tries to load, it attempts to re-load all my HttpModules and this failing and throwing a new exception all over again. This means that this approach could never work.

In order to solve this, I had to do some additional steps. The solution to this issue is to create a new Site in IIS, that is designed specifically to handle such erroneous cases. Let’s go through the steps in order to carry this out.

These steps assume that you are in possession of a functional SharePoint on-premise solution.

1) Create a friendly error page to display to the user when something goes wrong

In order to keep this as simple as possible, we’re just going to display a header that something went wrong, no fancy images or CSS. You could be a bit more creative if you want but for the sake of this blog, it’s fine.

<html>
<body>
<h1>Sorry, something went wrong</h1>
</body>
</html>

2) Create a new IIS Site to host our newly created HTML

This site will simply be used to host the newly created HTML. We can opt to create this site either on the same Application Pool or a new one; it does not really matter in this case. I’ll just follow the default configuration provided by IIS.

Adding a new site in IIS
Adding a new site in IIS

3) Map your newly created IIS site to a subdomain.

Since we still need to use the same external port, we need to make sure that our newly created IIS site is accessible from outside. We need to create bindings in IIS to make sure that this is OK. If you’re running HTTPS (please do), you’ll need a wildcard certificate and IIS 7.5 or higher.

siteBindings
Editing site bindings for our new IIS site

4) Assign permissions to Everyone on the newly created Site.

Creating the site is not enough, we need to allow Everyone to access the site. We’ll use one of the out of the box special identities provided; the “Everyone” identity. Therefore anyone will be able to access it.

Permissions
Step 1: Right click your site -> Edit Permissions

securityEdit
Step 2: In the Security tab, press Edit

addEveryone
Step 3: In this new window, hit Add. In the Object name, type “Everyone”

addReadPermission
Assign Read Permission to “Everyone”

5) Configure the customErrors tag in your SharePoint site to point to the newly created Site

All we need now is to point the customErrors tag to thew newly created site.


<customErrors defaultRedirect="https://error.mysharepointsite.com" mode="On" />

 

All done! Keep in mind that this page is used in very special circumstances: when exceptions occur and the SharePoint context has not kicked in. All other typical exceptions are to be handled by the default out of the box SharePoint behavior.