Introduction
It's always been a problem to handle exceptions in a large enterprise environment, where you want to deal with different exceptions in a different manner. Though error pages can be configured in the web.config, but for only HTTP-specific errors. You cannot define specific Exception types with their respective handlers. One solution that I found on the net was Exceptions Management Block. But that was after all a complex solution according to me. So I devised my own mechanism to configure Exception types with their respective handlers in web.config.
Problem Definition
Normally, in a simple application, we deal with exceptions in a Try
-Catch
block like this:
Try
// Do some code here
Catch sqle as SQLExceptions
// Handle SQLException differently
Catch e as Exceptions
// Handle Generic Exception differently
End Try
This is fine for a simple application, but for an enterprise application where several pages can raise exceptions, it can cause a headache to configure each of these blocks individually. Also, if you have derived your own application-specific exceptions, you have to handle them separately, and if you want to make any change in handling exceptions, you have to dive into your aspx pages.
Proposed Solution
To overcome this problem, I have defined a custom configuration section, that defines exception type, with its error-handler (assembly and class) and redirect-URL. In this way, you can configure an exception, its handler (both assembly and class) and redirect URL without intervening into your previously written code, in an independent manner.
Configuring And Handling Exceptions
First of all, we have to define a custom configuration section in web.config, just below <configuration>
tag.
<configSections >
<section name="exceptionManagers"
type="ExceptionManagers.ExceptionManagersHandler,ExceptionManagers" />
</configSections>
And just before ending <configuration>
tag, write this:
<exceptionManagers>
<exceptionManager application="MyApplication">
<exceptions>
<exception name="System.SQLException" url="ErrorPage.aspx">
<exceptionHandler assembly="ExceptionManagers"
name="SQLExceptionHandler" />
</exception>
</exceptions>
</exceptionManager>
<exceptionManager application="MyApplication">
<exceptions>
<exception name="System.InvalidOperationException"
url="ErrorPage.aspx">
<exceptionHandler assembly="ExceptionManagers"
name="InvalidOperationExceptionHandler" />
</exception>
</exceptions>
</exceptionManager>
</exceptionManagers>
The above section defines an exception of type System.SQLException
, that if raised in application "MyApplication", will be handled by SQLExceptionHandler
class from ExceptionManagers
assembly, and after handling, it will be redirected to ErrorPage.aspx. You can define all other exceptions you want to handle by replicating <ExceptionManager>
tag, and setting its attributes. By defining handlers in this way, we can isolate our Exception handling logic from being written into the code (Try
-Catch
blocks ). What we have to do is to just put a single line in our Try
-Catch
block, which is written below.
Try
//Do Some Code here , that raises Exception
Catch sqle as SQLException
Response.Redirect( ExceptionManagersHandler.PublishException("MyApplication",_
sqle) )
Catch exc as Exception
Response.Redirect(ExceptionManagersHandler.PublishException("MyApplication",_
exc) )
End Try
This line simply calls a shared method PublicshException
of the class ExceptionManagersHandler
and passes it the name of the application that raised this exception and the exception object itself.
Using the code
To define the exception handlers, create a class library project and name it ExceptionManagers (which is the assembly name in exceptionHandler
tag). Now, add a class SQLExceptionHandler
to handle SQLException
. This class implements IExceptionHandler
, an interface that defines a single executeHandler
method. When an exception of type SQLException
is raised, and ExceptionManager.PublishException
is called, it retrieves the configuration settings from the web.config and searches for an appropriate handler of the exception. The method then uses Reflection
namespace to instantiate a handler and delegate a call to it. The code for the said classes is below:
Option Strict On
Imports System
Imports System.Collections
Imports System.Xml
Imports System.Configuration
Imports System.Reflection
Public Class ExceptionManagersHandler
Implements IConfigurationSectionHandler
Public Function Create(ByVal parent As Object, _
ByVal configContext As Object, ByVal section As XmlNode) _
As Object Implements IConfigurationSectionHandler.Create
Dim _HandlerBucket As New Hashtable()
Dim appName, exceptionName, redirectUrl, _
errorHandler, assemblyName As String
Dim managerNode, exceptionNode, handlerNode As XmlNode
Dim managersNL, exceptionsNL, handlersNL As XmlNodeList
Dim exceptionsList As Hashtable
Dim excepInfo As ExceptionInfo
managersNL = section.SelectNodes("//exceptionManager")
For Each managerNode In managersNL
appName = managerNode.Attributes.GetNamedItem("application").Value
exceptionsNL = managerNode.SelectNodes("exceptions/exception")
For Each exceptionNode In exceptionsNL
exceptionsList = New Hashtable()
exceptionName = _
exceptionNode.Attributes.GetNamedItem("name").Value
redirectUrl = _
exceptionNode.Attributes.GetNamedItem("url").Value
excepInfo = _
New ExceptionInfo(appName, exceptionName, redirectUrl)
handlersNL = exceptionNode.SelectNodes("exceptionHandler")
For Each handlerNode In handlersNL
assemblyName = _
handlerNode.Attributes.GetNamedItem("assembly").Value
errorHandler = _
handlerNode.Attributes.GetNamedItem("name").Value
Next
excepInfo.HandlerAssembly = assemblyName
excepInfo.HandlerName = errorHandler
exceptionsList.Add(exceptionName, excepInfo)
Next
_HandlerBucket.Add(appName, exceptionsList)
Next
Return _HandlerBucket
End Function
Public Shared Function PublishException(ByVal appName As String, _
ByVal e As Exception) As String
If (appName Is Nothing) Then
appName = "GenericAppication"
End If
Dim expName As String = e.GetType.ToString
Dim handlerBucket As Hashtable = _
CType(System.Configuration.ConfigurationSettings.GetConfig("exceptionManagers"),_
Hashtable)
Dim exceptionHT As Hashtable = _
CType(handlerBucket.Item(appName), Hashtable)
Dim excepInfo As ExceptionInfo
If Not (exceptionHT Is Nothing) Then
excepInfo = CType(exceptionHT.Item(expName), ExceptionInfo)
Dim assemName, className As String
Dim classType As Type
Try
assemName = excepInfo.HandlerAssembly
className = assemName & "." & excepInfo.HandlerName
Dim assem As [Assembly] = [Assembly].Load(assemName)
classType = assem.GetType(className)
Dim objref As Object = Activator.CreateInstance(classType)
Dim handler As IExceptionHandler = _
CType(objref, IExceptionHandler)
handler.executeHandler(e)
Catch exp As Exception
throw new Exception ("Unknown Exception Occured " & _
"in loading or instantiating handler" & exp.Message)
End Try
End If
Return excepInfo.RedirectUrl
End Function
End Class
The code for IExcceptionHandler
and ExceptionInfo
classes:
Public Interface IExceptionHandler
Sub executeHandler(ByVal except As Exception)
End Interface
Public Class ExceptionInfo
Private _ExceptionName As String
Private _RedirectUrl As String
Private _ApplicationName As String
Private _HandlerName As String
Private _HandlerAssembly As String
Public Sub New(ByVal applicationName As String, ByVal _
exceptionName As String, ByVal errorUrl As String)
_ApplicationName = applicationName
_ExceptionName = exceptionName
_RedirectUrl = errorUrl
End Sub
Public ReadOnly Property ExceptionName() As String
Get
Return _ExceptionName
End Get
End Property
Public ReadOnly Property RedirectUrl() As String
Get
Return _RedirectUrl
End Get
End Property
Public ReadOnly Property ApplicationName() As String
Get
Return _ApplicationName
End Get
End Property
Public Property HandlerName() As String
Get
Return _HandlerName
End Get
Set(ByVal Value As String)
_HandlerName = Value
End Set
End Property
Public Property HandlerAssembly() As String
Get
Return _HandlerAssembly
End Get
Set(ByVal Value As String)
_HandlerAssembly = Value
End Set
End Property
End Class
Conclusion
Handling exceptions in such way gives a user ability to write the exception handling code outside the Catch
block, so that if you want to change the way exception is handled, you don't have to dive into your application's code, just write a new handler or modify the earlier one and it will be available to your application (provided that concerned attributes are updated accordingly). Also, it enables you to define and raise your own exceptions and write their handlers in an independent manner. You can also configure application specific handlers or you can simply write a generic handler to handle multiple exceptions in a similar manner.
Points of Interest
I used 'Command Pattern' with reflection to implement an exception handling mechanism that is independent of using the application. I do feel fun to play with patterns, though I know much less about them.