Skip to content

ASNA/global-asp-net-exception-handling

Repository files navigation

An AVR ASP.NET example for global error handling and logging

This example shows how to add global exception handling to your ASNA Visual RPG ASP.NET Web app.

If an unhandled exception occurs an error page is displayed. This page is shown with ASP.NET's Server.Transfer method so that url continues to reflect the page that caused the error.

Running the example code

Click "Cause exception" to cause an unhandled divide by zero error:

https://asna-assets.nyc3.cdn.digitaloceanspaces.com/scratch/global-error-handling-01-15-46-02-93.webp

Without global exception error handling, the user sees the ASP.NET yellow screen of death:

https://asna-assets.nyc3.cdn.digitaloceanspaces.com/scratch/global-error-handling-02-15-49-02-79.webp

With global error handling, the user sees a screen like:

https://asna-assets.nyc3.cdn.digitaloceanspaces.com/scratch/global-error-handling-04-15-52-44-14.webp

If you are testing this in Visual Studio, the exception will is raised in Visual Studio. If this occurs, press F5 to show the error page.

https://asna-assets.nyc3.cdn.digitaloceanspaces.com/scratch/global-error-handling-03-15-51-43-90.webp

This example uses the Nuget NLog package to log errors. An example of the log is shown below. See this GitHub repo for configuring NLog.

2026-03-23 13:22:31.7949 | ERROR | Error | An error occurred. Error details (all exceptions): 

Error level 1
Error message: Attempted to divide by zero.

Stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.op_Division(Decimal d1, Decimal d2)
   at Index.Button1_Click(Object sender, EventArgs e) in C:\Users\thumb\Documents\projects\avr\web\global-asp-net-exception-handling\Index.aspx.vr:line 46
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

-----------------------------------------------------------------------------------------------------------------------------------
Error level 0
Error message: Exception of type 'System.Web.HttpUnhandledException' was thrown.

Stack trace:
   at System.Web.UI.Page.HandleError(Exception e)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest()
   at System.Web.UI.Page.ProcessRequest(HttpContext context)
   at ASP.index_aspx.ProcessRequest(HttpContext context) in C:\Users\thumb\AppData\Local\Temp\Temporary ASP.NET Files\vs\71c959ba\6c587e5e\App_Web_nmto2m0n.0.vr:line 345
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Implementation code

The global.asax's Application_Error provides the jumping-off place to globally handle any otherwise unhandled application exceptions.

To persist the unhandled exception (so its details can be shown on the error.aspx page) that exception is stored in a session variable.

If the unhandled error is an HTTP exception that throws a page not found 404 error, control is passed to the 404.aspx page. Otherwise, the error.aspx page is shown.

BegSr Application_Error
    DclSrParm sender Type(*Object)
    DclSrParm e Type(EventArgs)

    // Code that runs when an unhandled error occurs
    DclFld Error Type(System.Exception)
    DclFld HttpStatusCode Type(*Integer4) 
    DclFld PERSIST_CONTEXT Type(*Boolean) Inz(*True)

    Error = Server.GetLastError()
    Server.ClearError()

    If Error *Is HttpException
        HttpStatusCode = (Error *As HttpException).GetHttpCode()
        If (HttpStatusCode = 404)
            Server.TransferRequest('/404.aspx', PERSIST_CONTEXT)
        EndIf 
    EndIf 

    Session['LastError'] = Error
    Server.TransferRequest('/Error.aspx', PERSIST_CONTEXT)
EndSr

The error.aspx page

The error.aspx markup is shown below. It uses the free FontAwesome 'skull-crossbones' icon. error.aspx is in the project's root.

This page assumes an error folder is your project's root. This page doesn't include any links for the user to recover control of the application. You may want to consider adding such links. It's possible back button would let the user recover control, but an explicit link is probably better.

The error.aspx.vr code behind is shown below:

Using System
Using System.Collections.Specialized    
Using System.IO 

BegClass Error Partial(*Yes) Access(*Public) Extends(System.Web.UI.Page)

    DclConst CRLF  Value( U'000D000A' )  Access( *Public ) 

    BegSr Page_Load Access(*Private) Event(*This.Load)
        DclSrParm sender Type(*Object)
        DclSrParm e Type(System.EventArgs)
       
        DclFld Error   Type( System.Exception ) 
        DclFld ErrorKey Type(*String) 
        DclFld ErrorText Type(*String)

		DclFld Log Type(NLog.Logger)

        Log = NLog.LogManager.GetCurrentClassLogger()
       
        *This.Title = "An error occurred"

        If (NOT Page.IsPostBack)
            // Fetch last error from Session object.
            Error = Session['LastError'] *As System.Exception 
                    
            // Clean up session
            Session.Remove(ErrorKey)
            
            If Error <> *Nothing  
                // Show error details.
                ErrorText = GetErrorDetails(Error, CRLF)
                literalErrorDetail.Text = ErrorText             

                Log.Error(Error, "An error occurred. Error details (all exceptions): {0}", CRLF + CRLF + ErrorText)
                //Log.Error(Error, "An error occurred. Error details:(top-level exception only) {0}", CRLF + CRLF + Error.Message)
            EndIf             
        EndIf
    EndSr

   BegFunc GetErrorDetails Type( *String )  Access( *Public ) 
        //
        // Get all error message and stack trace info for a given error.
        //
        DclSrParm Error    Type( System.Exception ) 
        DclSrParm NewLine  Type( *String  ) 
        
        // NewLine provides the way new lines should be formed. When the
        // text is used inside a multi-line textbox, a CRLF should be used 
        // as a newline. When the text is sent as a formatted email (as 
        // an HTML document) the <br> tag should be used.

        DclFld ErrorDetails Type( System.Text.StringBuilder ) New( 512 ) 
        DclFld NestedDetails Type(StringCollection) New()
        DclFld Counter Type(*Integer4) 
        
        DclFld Level        Type( *Integer4 ) 
        DclFld Now          Type( DateTime ) 
        DclFld MsgMask Type(*String) 
        DclFld Index Type(*Integer4) 

        MsgMask = 'This error occured on {0} at {1}M{2}' 
                
        Now = DateTime.Now 
        labelErrorHeading.Text = String.Format(MsgMask, Now.ToString( "dddd, MMMM dd, yyyy" ), Now.ToString( "hh:mm:ss t" ), NewLine + NewLine)

        CollectNestedErrorDetail(Error, NestedDetails, Level, NewLine )         

        Error = Error.InnerException
        DoWhile ( Error <> *Nothing ) 
            Level = Level + 1
            CollectNestedErrorDetail(Error, NestedDetails, Level, NewLine ) 
            Error = Error.InnerException
        EndDo 
       
        For Index(Counter = NestedDetails.Count -1) DownTo(0) 
            ErrorDetails.Append(NestedDetails[Counter])
            If Counter <> 0 
                ErrorDetails.Append(*New System.String('-' *As *OneChar, 150) + NewLine)
            EndIf 
        EndFor 

        //LogError(ErrorDetails.ToString())

        LeaveSr ErrorDetails.ToString() 
    EndFunc 
    
    BegSr CollectNestedErrorDetail
        DclSrParm Error         Type( System.Exception ) 
        DclSrParm NestedDetails Type(StringCollection) 
        DclSrParm Level         Type( *Integer4 )
        DclSrParm NewLine       Type( *String  ) 
        
        DclFld ErrorDetails  Type( System.Text.StringBuilder ) New()

        ErrorDetails.Append( "Error level " + Level.ToString() + NewLine )              
        ErrorDetails.Append( "Error message: " )
        ErrorDetails.Append( Error.Message + NewLine + NewLine ) 
        ErrorDetails.Append( "Stack trace:" + NewLine ) 
        ErrorDetails.Append( Error.StackTrace + NewLine + NewLine + NewLine + NewLine ) 

        NestedDetails.Add(ErrorDetails.ToString()) 
    EndSr

    BegSr LogError
        DclSrParm ErrorDetails Type(*String) 

        DclFld ErrorsFolderPath Type(*String) 
        DclFld Filename Type(*String) 
        DclFld FullFilename Type(*String) 

        ErrorsFolderPath  = Server.MapPath('/errors') 
        FileName = DateTime.Now.ToString('yyyy-MM-dd-HH-mm-ss')
        FullFilename = String.Format('{0}\{1}.log', ErrorsFolderPath, Filename)
        
        File.WriteAllText(FullFilename, ErrorDetails)   
    EndSr 

EndClass

The code above collects the top level exception and its chain of inner exceptions to create a list of error messages. The error is also logged to NLog.

About

Global ASP.NET exception handling and a way to manage DataGate DB connections in ASP.NET

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors