IIS – TroubleShooting – High CPU Utilization – Debug Diagnostic Tools v2.1 – Day 1

Background

Needing to do a deep drive into troubleshooting IIS Servers.  As part of our troubleshooting exercise we will cover one of Microsoft’s own tool, Debug Diagnostic Tools.

 

Debug Diagnostic Tools

Version

The tool’s team blog is here.

And, the current version is 2.1 and it is available here.

Details

The version was availed on 2015-Nov-13.

Requirements

Link

We are running MS Windows 2012 R2.

And, the bitness is 64-bit.

 

Install

Image

Welcome

End-User License Agreement

End-User License Agreement – Initial

End-User License Agreement – Completed

Custom Setup

 

Ready to install

Installing….

Completed

Debug File

Before we actually use the debug tool, let us go generate dump files from a working w3wp process.

Identify Process

Code


c:\windows\system32\inetsrv\appcmd list wp

 

Output

Dump file

Task Manager

On the IIS machine, launch Task Manager, access the Processes tab, select the “IIS Worker Process”.

Right click on your selection and choose “Create dump file“.

 

Use Debug Diagnostic Tool

Start Menu

Here is the Start Menu,

We will choose the menu option “Debug Diagnostics Tool 2” \ “Debug Diag2 Analysis”.

 

Data Files

We will click the “Add Data Files” button and navigate to the folder where we kept the dump file that was created earlier.

Here is the screenshot once we have added our dump file.

 

Analysis Rules

From the list of rules, we chose “Default Analysis” “CrashHangAnalysis“.

Start Analysis

Please click the “Start Analysis” button.

Reports

Analysis Report – Dashboard

Here we noted 3 Warnings.

 

Sections

Previous .Net Exceptions Reports ( Exception in all .Net Heaps )

Image

Tabulated

 

Exception Count Message Stack Trace
System.Exception 1 <none>  ;
System.OutOfMemoryException 1 <none>  ;
 

System.StackOverflowException

1 <none>  ;
System.ExecutionEngineException 1 <none>  ;
System.Threading.ThreadAbortException 2 <none>  ;
System.NullReferenceException 3 Object reference not set to an instance of an object.  garage.Models.Articulation.GeneratedPrefixAgreement+<>c__DisplayClass1e.<Generate>b__b(garage.Models.Articulation.Courses.CourseToCourseArticulation)
System.Linq.EnumerableSorter`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].ComputeKeys(System.__Canon[], Int32)
System.Linq.EnumerableSorter`1[[System.__Canon, mscorlib]].Sort(System.__Canon[], Int32)
System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.__Canon, mscorlib]].MoveNext()
garage.Models.Articulation.GeneratedPrefixAgreement.Generate(CodeFirstMembershipSharp.DataContext)
LinqKit.Extensions.ForEach[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>, System.Action`1<System.__Canon>)
garage.Infrastructure.GeneratedAgreementFactory.Generate(garage.Models.Articulation.GeneratedAgreement)
 

System.Reflection.TargetInvocationException

3 Exception has been thrown by the target of an invocation.  System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean)
System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[])
System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
Hangfire.Server.CoreBackgroundJobPerformer.InvokeMethod(System.Reflection.MethodInfo, System.Object, System.Object[])
 

Hangfire.Server.JobPerformanceException

3  

An exception occurred during performance of the job.

  Hangfire.Server.CoreBackgroundJobPerformer.InvokeMethod(System.Reflection.MethodInfo, System.Object, System.Object[])
Hangfire.Server.CoreBackgroundJobPerformer.Perform(Hangfire.Server.PerformContext)
Hangfire.Server.BackgroundJobPerformer+<>c__DisplayClass8_0.<PerformJobWithFilters>b__0()
Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(Hangfire.Server.IServerFilter, Hangfire.Server.PerformingContext, System.Func`1<Hangfire.Server.PerformedContext>)
Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(Hangfire.Server.PerformContext, System.Collections.Generic.IEnumerable`1<Hangfire.Server.IServerFilter>)
Hangfire.Server.BackgroundJobPerformer.Perform(Hangfire.Server.PerformContext)
Hangfire.Server.Worker.PerformJob(Hangfire.Server.BackgroundProcessContext, Hangfire.Storage.IStorageConnection, System.String)

 

Thread Report

Image

Explanation
  1. 54 Threads
    • 32% of all threads have this same call stack

 

clr!Thread::intermediateThreadProc ( 54 Threads )

Image

 

Explanation

mscorlib_ni!System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].Insert(System.__Canon, System.__Canon, Boolean)+bf
garage.Models.garageSystem.Menu.AddItemForRoles(CodeFirstMembershipSharp.User, System.String, System.String, Boolean, System.String[], Boolean, System.String)+d5
garage.Controllers.HomeController.BuildMenu()+6c
garage.Controllers.BaseController.Initialize(System.Web.Routing.RequestContext)+1956
System.Web.Mvc.Controller.BeginExecute(System.Web.Routing.RequestContext, System.AsyncCallback, System.Object)+179
System.Web.Mvc.MvcHandler.b__4(System.AsyncCallback, System.Object, ProcessRequestState)+37
System.Web.Mvc.Async.AsyncResultWrapper+WrappedAsyncVoid`1[[System.Web.Mvc.MvcHandler+ProcessRequestState, System.Web.Mvc]].CallBeginDelegate(System.AsyncCallback, System.Object)+41
System.Web.Mvc.Async.AsyncResultWrapper+WrappedAsyncResultBase`1[[System.Web.Mvc.Async.AsyncVoid, System.Web.Mvc]].Begin(System.AsyncCallback, System.Object, Int32)+aa
System.Web.Mvc.MvcHandler.BeginProcessRequest(System.Web.HttpContextBase, System.AsyncCallback, System.Object)+23b
System_Web_ni!System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()+132
System_Web_ni!System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)+9d
System_Web_ni!System.Web.HttpApplication+PipelineStepManager.ResumeSteps(System.Exception)+5dc
System_Web_ni!System.Web.HttpApplication.BeginProcessRequestNotification(System.Web.HttpContext, System.AsyncCallback)+79
System_Web_ni!System.Web.HttpRuntime.ProcessRequestNotificationPrivate(System.Web.Hosting.IIS7WorkerRequest, System.Web.HttpContext)+e0
System_Web_ni!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr, IntPtr, IntPtr, Int32)+407
System_Web_ni!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr, IntPtr, IntPtr, Int32)+14
System_Web_ni!DomainNeutralILStubClass.IL_STUB_ReversePInvoke(Int64, Int64, Int64, Int32)+5b
System_Web_ni!DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, System.Web.RequestNotificationStatus ByRef)+7e
[[InlinedCallFrame] (System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion)] System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr, System.Web.RequestNotificationStatusByRef)
System_Web_ni!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr, IntPtr, IntPtr, Int32)+5e6
System_Web_ni!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr, IntPtr, IntPtr, Int32)+14
System_Web_ni!DomainNeutralILStubClass.IL_STUB_ReversePInvoke(Int64, Int64, Int64, Int32)+5b
[[ContextTransitionFrame]] 

 

clr!Thread::intermediateThreadProc ( 16 Threads – 9% of all threads )

Image

Explanation
  1. 16 Threads
    • Thread is waiting in a waitOne

 

clr!Thread::intermediateThreadProc ( 8 Threads – 4% of all threads )

Image

Textual

Entry point   clr!Thread::intermediateThreadProc
Create time   8/2/2017 3:30:18 PM
Time spent in user mode   0 Days 00:00:00.093
Time spent in kernel mode   0 Days 00:00:00.031 

This thread is waiting for .net garbage collection to finish.
The current set of scripts were not able to determine which thread induced GC.
The garbage collector thread wont start doing its work till the time the threads which have pre-emptive GC disabled have finished executing.
The following threads have pre-emptive GC disabled 28,51,52,53,54,55,56,57,66,67,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,155,156,157,158,159,160,161,162,163,165,166,167,

Call Stack

ntdll!NtWaitForSingleObject+a
KERNELBASE!WaitForSingleObjectEx+94
clr!SVR::gc_heap::wait_for_gc_done+134
clr!SVR::gc_heap::wait_for_gc_done+cb
clr!CLREventBase::WaitEx+7c
clr!SVR::gc_heap::bgc_thread_function+a7
clr!Thread::intermediateThreadProc+86
kernel32!BaseThreadInitThunk+22
ntdll!RtlUserThreadStart+34 

Explanation

This is a very important group:

  1. This thread is waiting for .net garbage collection to finish.
  2. The garbage collector thread wont start doing its work till the time the threads which have pre-emptive GC disabled have finished executing.
  3. The following threads have pre-emptive GC disabled…..

 

Summary

Here is a quick compilation of what the tool is informing us of:

  1. We have exceptions that are not gracefully handled
    • System.NullReferenceException
      • Entity Framework calls
        • Uncompleted DB Calls that likely timed out
        • .Net code should review return code before trying to access returned dataset
    • System.Reflection.TargetInvocationException
    • Hangfire.Server.JobPerformanceException
      • Hangfire background process when accessing persistent backend
  2. Seeming contention issue accessing Dictionary Collection?
    • Stack Trace :-
      • mscorlib_ni!System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].Insert(System.__Canon, System.__Canon, Boolean)+bf
        garage.Models.garageSystem.Menu.AddItemForRoles(CodeFirstMembershipSharp.User, System.String, System.String, Boolean, System.String[], Boolean, System.String)+d5
        garage.Controllers.HomeController.BuildMenu()+6c
  3. Garbage Collection Issues
    • Stack Trace :-
      • This thread is waiting for .net garbage collection to finish.The current set of scripts were not able to determine which thread induced GC.  The garbage collector thread wont start doing its work till the time the threads which have pre-emptive GC disabled have finished executing. The following threads have pre-emptive GC disabled

 

References

  1. Developer Network
    • Mourad Lagdas
      • How to Use the Debug Diagnostic Tool v1.1 (DebugDiag) to Debug User Mode Processes
        Link
  2. Microsoft Developer
    • Michael Friis ( friis[at]microsoft.com )
      • Which w3wp.exe PID corresponds to which application pool ?
        Link
  3. Hangfire
    • Documentation \ Background processing \ Dealing with exceptions
      Link
  4. WhiteSites.com
    • Debugging Faulting Application w3wp.exe Crashes
      Link

.Net – ASPX – Avoid Parser Error when commenting out code

Background

While trying out different code snippets as I worked on tweaking an ASPX app, I ran into a parser error.

BTW, the enhancement is detailed here.

Let us quickly go over the parser error.

 

Parser Error

 

Code


<asp:TemplateColumn HeaderText="Link" >

	<ItemTemplate>		

	<!--
		
		 <asp:HyperLinkColumn 
				HeaderText="Link" 		
				Text="Link" 
				NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "eventLink") %>'
				Visible="True"
				Target="_blank"
				runat="server"												
				> 	
				
		</asp:HyperLinkColumn>   		    

	-->
	
	 <asp:HyperLink
			HeaderText="Link" 		
			Text="Link" 
			NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "eventLink") %>'
			Visible="True"
			Target="_blank"
			runat="server"
			> 	
	 </asp:HyperLink>   		    		                         																				
	
	</ItemTemplate>
	
</asp:TemplateColumn>


 

Parser Error

Image

ParserError-20160730-0731AM

Textual


Parser Error

Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately. 

Parser Error Message: Databinding expressions are only supported on objects that have a DataBinding event. System.Web.UI.WebControls.HyperLinkColumn does not have a DataBinding event.

Source Error: 

Correct

Here is the change.

Basically, it is to replace “<!–” and “–>” with “<%–” and “–%>“.


<asp:TemplateColumn HeaderText="Link" >

	<ItemTemplate>		

	<%--
		
		 <asp:HyperLinkColumn 
				HeaderText="Link" 		
				Text="Link" 
				NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "eventLink") %>'
				Visible="True"
				Target="_blank"
				runat="server"												
				> 	
				
		</asp:HyperLinkColumn>   		    

	--%>
	
	 <asp:HyperLink
			HeaderText="Link" 		
			Text="Link" 
			NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "eventLink") %>'
			Visible="True"
			Target="_blank"
			runat="server"
			> 	
	 </asp:HyperLink>   		    		                         																				
	
	</ItemTemplate>
	
</asp:TemplateColumn>


 

Acknowledge

Crediting Scott Guthrie

Tip/Trick: Using Server Side Comments with ASP.NET 2.0
Link

ASP.Net – DataGrid – Adding Hyperlink Column

 

Background

There is a dated Black-book web site that I wrote to keep up with people and appointments years ago.

Here is what the Appointments page looks like:

List Appointments

BeforeGrid

Edit Appointment

BeforeEdit

 

Enhancement

Lately, I have found more and more the need to isolate the corresponding web link into its own entry field and not just mushroom it into the comments multi-edit box.

Edit Appointment

AfterEdit

List Appointments

AfterGrid

 

Code

I had a little problem and had to have my brother help me with .Net code to add an hyperlink to a Data Grid.

And, so wanted to place it online for anyone else rusty with ASP.Net

Code line


<asp:DataGrid id="gridContactEventInfo" BorderColor="black" BorderWidth="1" CellPadding="3" AutoGenerateColumns="false" runat="server" font-size="8" width='100%'>

 <HeaderStyle BackColor="#9FBCE3">
 </HeaderStyle>

 <ItemStyle BackColor="#EEF2F7">
 </ItemStyle>
			 
 <AlternatingItemStyle BackColor="#EEEEEE">                                 
 </AlternatingItemStyle>                                 
								  
 <columns>
 
	 <asp:BoundColumn HeaderText="Event" DataField="eventName" /> 


		<asp:TemplateColumn HeaderText="Link" >

			<ItemTemplate>		

			 <asp:HyperLink
					HeaderText="Link" 		
					Text="Link" 
					NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "eventLink") %>'
					Visible="True"
					Target="_blank"
					runat="server"
					> 	
			 </asp:HyperLink>   		    		                         																				
			
			</ItemTemplate>
			
		</asp:TemplateColumn>

	</columns>		

</asp:DataGrid>	

 

Quick Explanation

  1. Added asp:TemplateColumn
  2. Added ItemTemplate
  3. Added asp:HyperLink
    • Added NavigateUrl='<%# DataBinder.Eval(Container.DataItem, “eventLink”) %>’

 

ASP.Net – DataGrid – Sorting

Background

Yearns ago I developed an electronic’s black-book. It is very basic and rudimentary.  It allows me to jot down names and contact details.

Here is what the screen looks like:

defaultSorting

A couple of days ago, I wanted to find someone I met a few months ago. Unfortunately, I could not remember the person’s name, but I remembered how I met the person and noted that it is a very recent acquaintance.

And, so I know sorting by name is the way to go, but I also know that the sorted by column never quite worked.

So this is a good opportunity to clear that up.

Forward

When adding the ability to sort a DataGrid via exposing individual Column sorting, there are a few things to keep in mind.

  1. Datagrid
    • Set AllowSorting to true i.e. AllowSorting=”True”
    • Set OnSortCommand to an event function i.e. OnSortCommand = “gridSortEvent”
    • On each sort-able column, be sure to set
      • HeaderText
      • DataField
      • SortExpression
  2. In the code behind
    • From Database, feed in SQL Query and get unsorted RecordSet
      • Persist Query Result in DataSet
      • Capture DataSet’s default view in a variable ( objDefaultView )
      • Save default view in a Session Variable
    • Have a helper function that we called getSortDirection. It receives the sortColumn and compares that sortColumn with the previous callback’s sortColumn.
      • Determine Sort Direction
        • If previous sortColumn is empty, then set to default of ASC
        • If previous sortColumn is the same as current, then toggles sortDirection
        • If previous sortColumn is different than current, then assumes ASC
      • Preserve Sort Column and Sort Direction
        • We used viewState to preserve the passed-in sortColumn and derived sortDirection
      • Return sortDirection
    • Have the event handling function ( gridContactSortEvent )
      • It gets passed the DataGridSortCommandEventArgs argument
      • We extract the sortExpression from the argument
      • We call the helper function mentioned above ( getSortDirection )
      • We concatenate our two sort arguments (sortColumn and sortDirection) and create a new variable sortColumnAndDir
      • Retrieve the session variable gridContact and casts it as a DataView; saved as dv
      • Set the Sort property of the dataview (dv) to the variable sortColumnAndDir
      • Set the grid DataSource to the saved variable dv
      • Invoke grid’s DataBind method

 

Code

ASPX Code


   <form id='frmCover'
         method='post'
         runat='server'
         action='Cover.aspx'
         defaultFocus='txtContactSearchTag'
         defaultButton='idAnchorContactSearch'
    >

   <asp:dataGrid
      id='gridContact'
      BorderColor='black'
      BorderWidth='1'
      CellPadding='3'
      AutoGenerateColumns='false'
      runat='server'
      width='100%'
      AllowSorting='True'
      onSortCommand='gridContactSortEvent'
>

   <HeaderStyle BackColor='#9FBCE3'>
   </HeaderStyle>

   <ItemStyle BackColor='#EEF2F7'>
   </ItemStyle>

   <AlternatingItemStyle BackColor='#F5F9FD'>
   </AlternatingItemStyle>;

   <Columns>

     <asp:HyperLinkColumn>

         HeaderText='Contact'
         Text='Contact'
         DataTextField='Contact'
         DataNavigateUrlField='ContactIdentifier'
         DataNavigateUrlFormatString='ContactBrowse.aspx?ContactID={0}'
	 SortExpression='Contact'
      >       

      </asp:HyperLinkColumn>   		                        

      <asp:BoundColumn
          HeaderText='Affiliate'
          DataField='affiliate'
          SortExpression='affiliate'
       > 		

       </asp:BoundColumn>

       <asp:BoundColumn
	  HeaderText='Profession'
	  DataField='Profession'
	  SortExpression='Profession'>; 	

      </asp:BoundColumn>		                            

      <asp:BoundColumn
          HeaderText='Created On'
          DataField='dateCreatedAsDay'
          SortExpression='dateCreated'>

      </asp:BoundColumn>

      <asp:TemplateColumn
	  HeaderText='Created On'
	  SortExpression='dateCreated'
	  Visible='False'
      >

        <ItemTemplate>

         <asp:label 

             id='dateCreatedFormattedAsDay'
             runat='server'
             text='<%# Eval('dateCreated', '{0:d}') %>' 

          >

        </ItemTemplate>

      </asp:TemplateColumn>									

   </Columns>                                                   		

  </asp:DataGrid>

 </form>

Code Behind – C#


namespace addressBook
{

    using System;
    using System.Collections;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.ComponentModel;
    using System.Data;
    using System.Data.OleDb;
    using System.Web.Security;
    using System.Text;    //StringBuilder
    using System.Configuration;    

    public class frmCover : System.Web.UI.Page
    {

       private DataView getRecordSetNames
       (
	      String strContactFilter
       )
       {

	   if (
                    (strMemberID == null)
                 || (strMemberID == string.Empty)
              )
	    {
		return null;
	    }

            OleDbDataAdapter objDbAdapter = null;
	    DataSet objDataSet;
	    DataView objDataView;
	    OleDbCommand objOleDBCommand;

	    strSQLQuery = &quot;dbo.usp_GetContactsForMember&quot;;

	    objOleDBCommand = new OleDbCommand();
	    objOleDBCommand.Connection = objConnection;
	    objOleDBCommand.CommandType
                 = CommandType.StoredProcedure;
	    objOleDBCommand.CommandText = strSQLQuery;

            OleDbParameter objDBParametemItemMemberID =
                             new OleDbParameter();
            objDBParametemItemMemberID.OleDbType
                             = OleDbType.VarChar;
            objDBParametemItemMemberID.Size = 88;
            objDBParametemItemMemberID.Value =
                 strMemberID;
            objOleDBCommand.Parameters.Add(
                 objDBParametemItemMemberID);			

            OleDbParameter objDBParametemItemContactFP =
                            new OleDbParameter();
            objDBParametemItemContactFP.OleDbType
                           = OleDbType.VarChar;
            objDBParametemItemContactFP.Size = 88;
            objDBParametemItemContactFP.Value =
                   strContactFilter;

            objOleDBCommand.Parameters.Add(
                   objDBParametemItemContactFP);			            

            objDbAdapter = new OleDbDataAdapter();
            objDbAdapter.SelectCommand = objOleDBCommand;

            objDataSet = new DataSet(&quot;namedetails&quot;);

            objDbAdapter.Fill(objDataSet);						

	    objDataView =
               objDataSet.Tables[0].DefaultView;

	    objDataView.RowFilter = String.Empty;

	    return objDataView;

	}              

	private void loadNamesGrid(
                                      String strContactFillter
                                  )
	   {

		//ICollection objNames;
		DataView    objNames;

		//get Names
		objNames = getRecordSetNames
                       (
                          strContactFilter
                       );

		//set data source
		gridContact.DataSource = objNames;

		//data Bind
		gridContact.DataBind();

                //save Grid's default view as a
                //session var
		Session[&quot;gridContact&quot;] = objNames;

	   }       

	private string getSortDirection(string column)
	{

	   // By default, set the sort direction to asc
	   string sortDirection = &quot;ASC&quot;;

	   // Retrieve the last column that was sorted.
	   string sortExpression = (string)
               ViewState[&quot;gridContactSortExpression&quot;];

           string lastDirection = null;

	   if (sortExpression != null)
	   {
	      // Check if the same column is being sorted
	      // Otherwise, the default value can be
	      if (sortExpression == column)
	      {
		lastDirection = (string)
                   ViewState[&quot;gridContactSortDirection&quot;];

		if ((lastDirection != null) &amp;&amp; (lastDirection == &quot;ASC&quot;))
		{
		   sortDirection = &quot;DESC&quot;;
		}

	      } // if sortExpression == column

	  } // if sortExpression is not null

	  // Save new values in ViewState.
	  ViewState[&quot;gridContactSortDirection&quot;] = sortDirection;
	  ViewState[&quot;gridContactSortExpression&quot;] = column;

	  return sortDirection;

	} // getSortDirection

       protected void gridContactSortEvent(
                                             Object sender
					   , DataGridSortCommandEventArgs e
					   )
       { 

 	   DataView dv = (DataView)Session[&quot;gridContact&quot;];
	   String strSortColumn = e.SortExpression;

	   String strSortDir = getSortDirection(strSortColumn);

	   String strSortColumnANDDir = strSortColumn + &quot; &quot; + strSortDir;

	   // The DataView provides an easy way to sort.
	   // Simply set the Sort property with
	   // the name of the field to sort by.
	   // dv.Sort = e.SortExpression;
	   dv.Sort = strSortColumnANDDir;

	   // Re-bind the data source and specify that it should be sorted
	   // by the field specified in the SortExpression property.
	   gridContact.DataSource = dv;
	   gridContact.DataBind();

 	} // gridContactSortEvent

    } // public class frmCover : System.Web.UI.Page

} //namespace

 

Post Code Changes

Our code changes bore dividend, as here is the result:

Grid Sorted by Name – Ascending

SortByNameAscending

Grid Sorted by Name – Descending

SortByNameDescending

 

Summary

Couple of things.  We increased session state usage by maintaining the results of the original query in a session variable.

As that data was saved in a session variable, we did not have to pass along the user’s intended sort column and direction to the database.

We needed a bit of code to determine sort direction as the DataGrid does not appear to keep that as part of it’s session state.

Please be sure to maintain database column’s datatype fidelity as much as possible. That is, keep data and formatting different.  This is achievable by exposing two data columns, or using the core column in the DB and using TemplateColumn and ItemTemplate in the ASPX code to perform formatting.

The big realization for me is that I actually need to handle the event for sorting, as it is not handled intrinsically by setting “AllowSorting“.

References

DataGrid

Microsoft Reference

 

GridView

Microsoft Reference

Q/A

 

IIS – ASP & ASP.Net – Session State Management – In Process

Background

I have a few ASP.Net applications running on a MS Windows 2003 box.

Occasionally, the applications error out.

 

Error

Textual:


Procedure or function 'listDatesWithEventsForMonth' expects parameter '@memberIdentifier', which was not supplied.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.Data.OleDb.OleDbException: Procedure or function 'listDatesWithEventsForMonth' expects parameter '@memberIdentifier', which was not supplied.

Source Error: 

Line 613:
Line 614:
Line 615:            objDBReader = objDBCommand.ExecuteReader();
Line 616:
Line 617:            while (objDBReader.Read())

 

Thoughts …

There is nothing more disconcerting than when an application wrote yearns ago, crash out so ungracefully on the original developer.

Yes, there are problems, but I am too lazy to fix them.

The problems include:

  1. Architectural – In Process Management
    • Should not be using in-process state management, but more so out of process.
      • In using out of process State Management, worker process recycling will not null out session states
      • Same with errant “Worker Process” that occasionally die
  2. Architectural – Web Garden
    • Insufficient consideration of multi worker processes
  3. Coding Issue
    • Why am I not sanitizing my variables; that is, when I read state data via session[x], I should make sure it is not null, before passing them it off to the DB
      • When null, display an error message and return to the most relevant screen depending on what is missing

Code

Here is the code snippet:


        protected void getUserInfo()
        {

            System.Security.Principal.IIdentity objIdentity;
            Boolean bAuthenticated;

            //get currently logged on user
            objIdentity = User.Identity;

            bAuthenticated = objIdentity.IsAuthenticated;

            strMemberName = objIdentity.Name;

            //get currently selected member
            if (Session["memberID"] != null)
            {
            	strMemberID = Session["memberID"].ToString();
            }	            

        }     

To sanitize the read in session variable, please change it a bit to something like this:


  protected void getUserInfo()
  {

     System.Security.Principal.IIdentity objIdentity;
     Boolean bAuthenticated;          

     objIdentity = User.Identity;

     bAuthenticated = objIdentity.IsAuthenticated;

     strMemberName = objIdentity.Name;

     if (Session["memberID"] != null)
     {
      	strMemberID = Session["memberID"].ToString();
     }	            

     //On 2015-08-10, dadeniji
     //sanitize
    if (     ( strMemberID == null)
          || ( strMemberID == String.Empty) )
    {

	String strURLHome = "login.aspx";
	Response.Redirect(strURLHome,true);

    }		

 }     

 

 

Bandage

Let us take to Google and see what type of bandage we can wool over this self induced wound.

Google Hits

 

Apply

Web.Config

  1. ASP.Net Configuration Settings – State Management
    • Session timeout ( in minutes ):
      • Change from 20 minutes to 300 minutes

 

 

ASP.Net Configuration Settings – State Management ( Before )

sessionStateSettings (before)

After

sessionStateSettings (after)

 

Application Pool

  1. Recycling
    • Recycle working process ( in minutes ):
      • Keep at 1740
  2. Performance
    • Idle Timeout
      • Shutdown Worker processes after being idle for ( time in minutes )
        • Changed from 20 minutes to 300 minutes
    • Web Garden
      • Maximum Number of Worker Processes
        • Left as 1

 

Recycling ( Before )

Application – Performance [ Before ]

Performance (Before)

 

Application – Performance [ After ]

Performance (After)

 

Summary

In summary, we are modifying the session state timeout on the web site, via the web.config file.

The change is to increase the idle time out from 20 minutes to 5 hours.  2 to 3 hours will likely have been sufficient, as this is not a heavily used web server and so we are likely not competing with other web sites nor users.

On the worker processes, which is fronted through the Application Pool configuration, we modified the idle timeout setting in similar fashion; changed from 20 minutes to 5 hours.

It goes without saying that the Application itself needs to be significantly cleaned-up. We need to possibly capture timeout notifications, and also sanitize data read in and not simply assume they are always valid.

 

References

ASP

 

ASP.Net

Mining Application Logs – 2015/Feb

Background

A few days a 3rd party web Application that I am working on started getting this error message:

PleaseSelectACity

 

Diagnostic Steps

Vendor

Spoke to the onsite Vendor Support person and he blamed it on customization we are doing on our side.

And, so again, we are on our own.

As the Application is Microsoft; .Net, Classic ASP, C#, and Classic ASP, I am on safe grounds.

Launched Command Shell

Launched Command Shell and changed folder to application’s Inetpub folder.

Find Text

Tool

While one can use GUI tools to search, I personally find the built-in Windows Search tool hard to navigate.  And, so we will use findstr for our exercise.

For our current needs, here are the relevant parameters:

Argument Description  Usage
 /m Prints only the file name if a file contains a match. This allows us to print out matches and not worry about their contextual usage in corresponding file
 /p Skips files with non-printable characters. In our case, we have exes and dlls.  These are binary files and their contents is not available via normal textual parsing means nor editors.And, so for now we will skip them.
 /s Searches for matching files in the current directory and all sub-directories. Delve into sub-folders \ sub-directories, as well.
 /c Uses specified text as a literal search string. Findstr supports both literal and regular expression (RE) searches.In our case, we will edge towards literal as it is a bit simpler and we will avoid the relative complexity of RE; especially as we do not need it for our current simple need.

 

 

 

Let us use findstr to look for the text.

   findstr /p /s /c:"Please select a city"

 

Output

Textual:


   _translate\english.inc:t5078 = "Please select a country"
   _translate\englishus.inc:t5078 = "Please select a country"

 

pleaseSelectACountry

 

Match ID

From the matches above, we appear to be dealing with an App that has a bit of Internationalization.  That is, it supports various languages through IDs and “culture” specific resource files.

This time, I do not want to print out the specific matching lines, just the actual file names.

   findstr /M /p /s /c:"t5078" *.* | more

 

Output

Textual:


   GridCache\js\jQuery.VC.js
   GridCache\js\jQuery.VC.min.js
   FuncLinks\metaAdmin.asp
   _translate\dutch.inc
   _translate\english.inc
   _translate\englishus.inc
   _translate\french.inc
   _translate\german.inc
   _translate\hungarian.inc
   _translate\indonesian.inc
   _translate\italian.inc
   _translate\norwegian.inc
   _translate\portuguese.inc
   _translate\portuguesebr.inc
   _translate\spanish.inc
   _translate\swedish.inc

 

Review HTML\jQuery File

In the GridCache\js\jQuery.VC.js file, we find matches for t5078 and t5079.

 


     var cz = 0;
	e.find("#selCountry option:selected").each(function() {
		if (cz == 0) {
			cz = b(this).attr("value")
		}

	if (cz <= 0) {
		alert(getTextValue("t5078"));
		e.find("#selCountry").focus();
		return
	}

	var y = 0;
	e.find("#selRegion option:selected").each(function() {
		if (y == 0) {
			y = b(this).attr("value")
		}
	});
	if (y <= 0) {
		alert(getTextValue("t5079"));
		e.find("#selRegion").focus();
		return
	}

 

 

Review ASP File

Next was to review some of the Server-side ASP files:

                     

  <strong><%= t1 %>:</strong>
  <span id="summary_location">
    <% if isArray(arr_locationDetails) and int_serviceOnly = 0 then response.write arr_locationDetails(1,0) end if %>
  </span>

 

Invoke Stored Procedure

Let us invoked our Stored Procedure and pass in the arguments we are most interested in.

  

  set oRS_getForm = server.createObject("ADODB.recordset")
  oRS_getForm.CursorLocation = 3


  on error resume next

  oRS_getForm.open "{call dbo.sp_displayForm ("&userDefinedLocation&", "&int_roomID&","&Session("LanguageID")&", "&Session("UserID")&", "&userDefinedBusinessUnit&", "&Session("countryID")&", "&int_bookingID&", '"&userDefinedFloor&"', "&session("filterGrid")&", "&int_managedBooking&", "& int_editSeries&", '"& dt_currentDateAndTime &"', "&session("outlook")&", "&int_serviceOnly&", "&int_deliveryPoint&")}", oConn

  set colErrs = oConn.Errors

  if oConn.Errors.Count <> 0 then 

    response.Write t1499
    For Each objError In colErrs 
        call logErrorData(objError.Number, objError.Description, "Form", "sp_displayForm")
    Next
    oConn.Errors.Clear
    oConn.close
    set oConn = nothing
    response.end

 end if

 

Retrieve Database Record Set

In the code below, we can see that on the third getRows we bring back the recordset that we are most interested in (arr_locationDetails).

                     

 if oRS_getForm.recordcount <> 0 then arr_userDetails = oRS_getForm.getRows() end if
 set oRS_getForm = oRS_getForm.nextRecordset()

 if oRS_getForm.recordcount <> 0 then arr_siteSetup = oRS_getForm.getRows() end if
 set oRS_getForm = oRS_getForm.nextRecordset()

 if oRS_getForm.recordcount <> 0 then arr_locationDetails = oRS_getForm.getRows() end if
 set oRS_getForm = oRS_getForm.nextRecordset()

 if oRS_getForm.recordcount <> 0 then arr_resourceNamingDetails = oRS_getForm.getRows() end if
 set oRS_getForm = oRS_getForm.nextRecordset()

 if oRS_getForm.recordcount <> 0 then arr_colours = oRS_getForm.getRows() end if
 set oRS_getForm = oRS_getForm.nextRecordset()

 if oRS_getForm.recordcount <> 0 then arr_messages = oRS_getForm.getRows() end if

 

Stored Procedure Code

Get Location & Business Unit


   SELECT 
           l.pkLocationID, l.LocationName, l.fkCountryID, isNull(l.att, 0), isNull(CostCentreMandatory, 1) AS CostCentreMandatory    
         , l.fkRegionID as regionID ,l.CCPaymentCostCodeEnabled CCPaymentCostCodeEnabled 
  FROM dbo.tblLocationBusinessUnit lbu     
        INNER JOIN dbo.tblLocation l 
         ON l.pkLocationID = lbu.fkLocationID    
  WHERE fkBusinessUnitID = @businessUnit    
  

 

Database Error Log

We went through some more files and finally reviewed the Application’s Error Log Tables.

 


set nocount on
go

set dateformat dmy
go

declare @dateCutoff datetime
declare @VENDOR_TAG varchar(60)

set @VENDOR_TAG = 'BR'
set @dateCutoff = '19/3/2015'

declare @errorListIgnore TABLE
(
    [errorDescription] varchar(60)
)

declare @errorListInclude TABLE
(
    [errorDescription] varchar(60)
)

insert into @errorListIgnore
([errorDescription])
values
      ('Invalid')
    , ('GridCache:InsertCache')
    , ('Cache removed reason: Removed')
    , ('found file')
    , ('Host')

insert into @errorListInclude
([errorDescription])
values
      ('Sequence contains no elements')

select top 1500
          'using ignore list' as 
        , replace(tblCE.errorNumber, @VENDOR_TAG, '') as [errorNumber]
        , errorDescription
        , replace(tblCE.[filename], @VENDOR_TAG, '') as [filename]
        , errorDescription
        , storedProcedure
        , replace(tblCE.datetime, @VENDOR_TAG, '') as [datetime]
from   dbo.tblBRError tblCE
where  errorDescription not in ( select tblL.[errorDescription] from @errorListIgnore tblL)
and    [datetime] < @dateCutoff
order by pkErrorID desc

select top 1500
          'using include list' as 
        , replace(tblCE.errorNumber, @VENDOR_TAG, '') as [errorNumber]
        , errorDescription
        , replace(tblCE.[filename], @VENDOR_TAG, '') as [filename]
        , errorDescription
        , storedProcedure
        , replace(tblCE.datetime, @VENDOR_TAG, '') as [datetime]
from   dbo.tblBRError tblCE
where  errorDescription in ( select tblL.[errorDescription] from @errorListInclude tblL)
and    [datetime] < @dateCutoff
order by pkErrorID desc

 

Thank goodness for Application’s Logging, we found entries such as:

 

Textual:

Error Entries
   at RNM..DAL.User.UserDAL.GetUserdefaultSettingsDB(Int32 userID)
at RNM..DAL.User.UserDAL.GetUserDefaultSettingsByID(Int32 userID)
at RNM..BLL.User.UserBLL.GetUserDefaultSettingsByID(Int32 userID)
  at RNM..Utilities.ExceptionManager.HandleException(Exception hExp, String dataToLog, Boolean throwException)
at RNM..BLL.User.UserBLL.GetUserDefaultSettingsByID(Int32 userID)
at RNM..BLL.User.UserBLL.GetUserDefaultSettingsByResourceID(Int32 userID, EnumResource resource)
 Timezone name not found

 

 

 Image:

errorTable

 

Fixes

There are a couple of Database support tables that are pertinent to us.

  • dbo.tblBusinessUnit
  • dbo.tblLocationBusinessUnit
  • dbo.tblLocation
  • dbo.tblUserdefaultView

Summary

I have intentionally obfuscated error messages and simplified our problem solving passageway for several reasons:

  • The Application is only used by a few companies
  • The Vendor does not have a broad support web site
  • I am too old to have the Vendor close some of our tool-sets and have to rely on them ever more; or worse still resort to more rudimentary techniques

 

Listening

It has been a taxing week.  And, so I have to listen and share

Patrick Anthony – No Pretense (Official Music Video)
https://www.youtube.com/watch?v=7g4_6Y_gCqw

Link

 

Microsoft – Classic ASP – Error Suppressed

Background

The last few weeks I have been battling with a Vendor application that uses a mix of Classic ASP and ASP.Net.  Not to keep the beating to myself, I will like to share some of my bruised ego.

 

Code

Classic ASP

We will focus on the Classic ASP code.  There are many reasons for this singular focus; and inclusive:

  • Historical
    • Classic ASP was introduced in 1996
    • It was Microsoft’s first server side scripting engine for generating web pages
  • Tooling
    • It is based on VBScript and JScript
    • ASP 2.0 provided six built-in objects: Application, ASPError, Request, Response, Server, and Session
    • It has being in support for accessing COM and Dlls

 

As a tool developed in 1996,  almost 20 years ago, it is increasingly difficult to elegantly support it.

 

Sample Code

Let us create a sample app

 


<html>
<head>
<title>
Err Suppressed
</title>
</head
<@% Page Language=VBScript 	Explicit=True Debug=True %>
<% Response.Buffer = True %>
<!--#include file="errorHandler/errorHandler.inc"-->

<%

	Const ERR_HANDLER_CUSTOMIZED = "/errorHandler/errorHandler500Customized.asp"

	Const ERR_TYPE_DIVIDEBYZERO = 1
	Const ERR_TYPE_OBJECT_INVALIDMETHOD = 2
	Const ON_ERROR_RESUME_NEXT = false
	Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = false

	dim strDate
	dim iNumberofEntries
	dim objDictModule
	dim allKeys
	dim allItems
	dim dictKey
	dim dictItem
	Dim i
	Dim iMax	

	dim objNullObject
	dim objErr

	dim numerator
	dim divisor
	dim result

	dim strItem
	dim iErrType

        dim strPagename

        strPagename = Request.ServerVariables("SCRIPT_NAME") 

        if (ON_ERROR_RESUME_NEXT) then

		On error resume next

	end if

	iErrType = ERR_TYPE_OBJECT_INVALIDMETHOD
	iErrType = ERR_TYPE_DIVIDEBYZERO

	if (iErrType = ERR_TYPE_DIVIDEBYZERO) then

		numerator = 1000
		divisor = 0
		result = a / b

	elseif (iErrType = ERR_TYPE_OBJECT_INVALIDMETHOD) then

		objNullObject.Sleep()

	end if

	set objErr = Err

	set objDictModule = getErrorObjectAsDictionary(objErr)

	set Session(ERR_OBJECT_CUSTOM) = objDictModule

	if ( (Err.Number <> 0) and (ON_ERROR_PROCEED_ERRORHANDLERPAGE = true) ) Then

           'store calling page name
           Session(ERR_OBJECT_CUSTOM).add "Err.Referrer", strPagename

           Response.Redirect ERR_HANDLER_CUSTOMIZED

	end if

%>

 

In the code above, we have intentionally added the usual suspects; that is things we know will cause trouble:

  • Divide by 0
  • Reference a null object ( objNullObject.Sleep )

 

IIS Configuration – ASP

Let us make sure that IIS is sufficiently for debugging ASP.

  • Via “Control Panel” \ “Administrative Tools” \ Internet Information Services (IIS) Manager
    • In the left panel, Under Sites, Select the Web Site
    • In the right panel, make sure that the Features View is active
    • In the Features view, within the IIS panel, select ASP

 

Features View

FeaturesView

 

ASP Configuration

ASP

 

The important configurable items are:

  • Debugging Properties – Set to True
    • Calculate Line Numbers
    • Catch COM Component Exceptions
    • Enable Client-side debugging
    • Enable Log Error Requests
    • Enable Server-side Debugging
    • Log Errors to NT Log
    • Send Errors to Browser
  • Script Language
    • VBScript

 

Error Message

Upon launching a Web Browser, IE in this case, we run into a ditch.

Here is what we get:

HTTP500

 

Not a very intuitive error message as we had “Show Friendly HTTP error messages“.

Current Setting:

ShowFriendlyHTTPErrorMessages

 

More Intuitive Message

Via “Internet Options”, we turned off “Show friendly HTTP error messages”.

And, we now have a good error number,  error message, and source file name & offending line number.

Error-Overflow

 

 

 IIS Log

IIS Logs also contain helpful data.

IISLog-Overflow

 

Our IIS Log contains definite and helpful error; here they are:

  • We have .Net CLR2 and CLR3 installed
  • Our error line is 57
  • Our error code is 800a006
  • Our error description is Overflow

 

 

Sample Code – “On Error Resume Next”

Let us change the code a bit by setting “On error resume next

 


    'Const ON_ERROR_RESUME_NEXT = false
     Const ON_ERROR_RESUME_NEXT = true

Once we set “On error resume next”, our error is no longer visible …

Browser – IE

Error-OnErrorResumeNext

Browser - IISLog

Checked IISLog, and no errors registered

IISLog-ErrSurpressed 

Error Handling

It is obvious that silencing our errors is a bit problematic; as we still have them, but we are not aware of them. Furthermore, the work we greatly want to do, is yet undone. Let us handle our error, and send the user to an informative error page.

 


    'Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = false
    Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = true

 

As we have now indicated that we want to handle our error, though it is silent, we wrote additional modules:

ErrSuppressed.asp

Classic ASP supports a couple of ways to organize code, source code modules & Com components.  In this post, we will take the quick and simple path of having the code in an included file (errorhandler/errorHandler.inc).

Though not required, we placed in its own folder, as well.

 


  <!--#include file="errorHandler/errorHandler.inc"-->

 

errorHandler/errorHandler.inc

Here is our included file.


<%

Const ERR_OBJECT_CUSTOM = "customErrorCollection"

Function getErrorObjectAsDictionary (objErr)

     Dim objList
     Dim objASPError

     Set objList = CreateObject("Scripting.Dictionary")
     objList.add "Err.Number", objErr.Number
     objList.add "Err.Description", objErr.Description

     set objASPError = Server.GetLastError()

     objList.add "ASPError.ASPCode", objASPError.ASPCode
     objList.add "ASPError.ASPDescription", objASPError.ASPDescription
     objList.add "ASPError.Description", objASPError.Description
     objList.add "ASPError.Source", objASPError.Source
     objList.add "ASPError.Number", objASPError.Number
     objList.add "ASPError.File", objASPError.File
     objList.add "ASPError.Line", objASPError.Line

     rem we used set as objList is an object and not a simple datatype (int\string)
     Set getErrorObjectAsDictionary = objList

End Function

%>

 

The errorHandler.inc is an interesting code-line:

  • We are a generic collection object (Scripting.Dictionary)
  • We captured both the err.number and err.description properties into our collection
  • We also performed a Server.GetLastError call and captured the resultant object’s property, as well
  • Noticed that we used set at the end of the function to return a complex object

 

 

errorHandler/errorHandler500Customized.inc

 


<html>
<head>
<title>
Err Handler - 500 - Customized
</title>
</head>

<@% Page Language=VBScript 	Explicit=True Debug=True	EnableSessionState=True	%>
<!--#include file="errorHandler.inc"-->

<body>

<%

	 Dim objError
	 Dim objDictModule
	 Dim errNumber
	 Dim errDescription

	 Dim objErr
	 Dim objErrDict

	 Dim strBuffer
	 Response.Clear

%>
<table border="1">
<tbody>
<tr>
<td>
<table border="0" width="653">
<tbody>
<tr style="background-color: #ff9900;">
<td colspan=3 col align='center'><strong>Error Items</strong></td>
</tr>
<tr style="background-color: Gainsboro;">
<td>&nbsp;</td>
<td><strong>Item</strong></td>
<td><strong>Value</strong></td>
</tr>
<!--
	Access Err Object
-->
<tr style="background-color: beige;">
<td>&nbsp;</td>
<td><strong>Err.Number</strong></td>
<td><strong><%= CSTR(Err.Number) %></strong></td>
</tr>
<tr style="background-color: beige;">
<td>&nbsp;</td>
<td><strong>Err.Description</strong></td>
<td><strong><%= Err.Description %></strong></td>
</tr>
<%

	strBuffer = ""
	CONST COLOR_ROW = "beige"
	CONST COLOR_ROW_ALTERNATE = "blanchedalmond"

	Dim strRowColor
	Dim strItem

	If isObject(Session(ERR_OBJECT_CUSTOM)) = false then

	else

		set objDictModule = Session(ERR_OBJECT_CUSTOM)

		if (objDictModule is Nothing) Then

		else

			'retrieve all the keys and items from the Dictionary and print them out
			allKeys = objDictModule.Keys   'Get all the keys into an array
			allItems = objDictModule.Items 'Get all the items into an array 

			For i = 0 To objDictModule.Count - 1 'Iterate through the array

			  dictKey = allKeys(i)   'This is the key value
			  dictItem = allItems(i) 'This is the item value

			  if ( (i mod 2) = 0) then
				strRowColor = COLOR_ROW
			  else
			    strRowColor = COLOR_ROW_ALTERNATE
			  end if	

			  strItem = "
<tr style='background-color: " & strRowColor & "';>"
			  strBuffer = strBuffer & strItem

			  strItem = "
<td>" & cstr(i+1) & "</td>
"
			  strBuffer = strBuffer & strItem

			  strItem = "
<td>" & dictKey & "</td>
"
			  strBuffer = strBuffer & strItem

			  strItem = "
<td>" & dictItem & "</td>
"
			  strBuffer = strBuffer & strItem

			  strItem = "</tr>
"
			  strBuffer = strBuffer & strItem

			Next

		end if

	end if

	Response.Write strBuffer

%></tbody>
</table>
&nbsp;</td>
</tr>
</tbody>
</table>
&nbsp;

</body>
</html>

%>

 

 Explanation:

Here is a quick explanation

  • We checked Err.Number and Err.Description
  • We checked the Session Object that was “set” in the previous page
  • Notice the use of IsObject to ensure that our session variable is previously set

 

 

Browser Output:

 

errorHandlerCustomizedOutput

 

 

From the screen above, we will notice a few things:

  • We lost our original error; that is why when the new page calls err.number and err.description we have 0 and empty
  • Thankfully, our session data was preserved

 

Summary:

It appears that when we use “on error resume next” and post to another page, our err object ( Err.Number and Err.Description ) is automatically reset.

 

Application Level

Configuration

Web.config

From the example above, we can see that the “on error resume next” statement, prevents error trapping through code.

What about error trapping that is implicitly handled in web.config

 

GUI

Edit Custom Error Page:

Here is how to configure Error Page via “IIS Management Console”

customErrorPage

 

 

Error Message

In our case, we tweaked our website’s configuration quite a bit. We reviewed and played around with the Application Pool and the actual web site.

Error Text:

Error Message: This site is in an application pool that is running in Classic mode. When running in this mode, custom errors apply to all content except ASP.Net content.

Error Image:

customErrorsAndApplicationPool

 

The error message hints that we need to change our Application Pool’s managed pipeline from Classic to Integrated.

 

Application Pool

ApplicationPool - MangedPipelineMode - Integrated

 

Error Pages – Feature Settings

It is also important that we set/review the default “Error Pages” settings.

 

CustomErrorPage - Edit Features

 

We want to set “Error Page Settings” / “Custom Error Pages” to “Custom error pages”.

If set to :

  • Detailed errors (everyone sees the detailed errors)
  • Detailed errors for local requests and custom error pages for remote requests ( when viewed locally on the web server one sees the detailed error message; and when viewed away from the web server, one sees our custom error page)

 

Source Code

Here is what our web.config looks like:

 


<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <system.webServer>

		<defaultDocument>
            <files>
                <add value="ErrSupressed.asp" />
            </files>
        </defaultDocument>

		<tracing>
            <traceFailedRequests>
                <remove path="*" />
                <add path="*">
                    <traceAreas>
                        <add provider="ASP" verbosity="Verbose" />
                        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
                        <add provider="ISAPI Extension" verbosity="Verbose" />
                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions timeTaken="00:00:00" statusCodes="100-999" />
                </add>
            </traceFailedRequests>
        </tracing>

        <httpErrors errorMode="Custom">
            <remove statusCode="500" subStatusCode="-1" />
            <error
		statusCode="500"
		prefixLanguageFilePath=""
		path="/errorHandler/errorHandler500Customized.asp"
		responseMode="ExecuteURL"
		/>
        </httpErrors> 

    </system.webServer>

</configuration>

 

Explanation:

There are a few things going on in the web.config file indicated above; and those are:

  • We are using our great friend – traceFailedRequests; whenever an error occurs an XML file is generated
  • We are using trapping on HTTPErrors
    • We are using ErrorMode is Custom :- This is we have our our own custom code
    • HTTP Error = 500
      • We call /errorHandler/errorHandler500Customized.asp
      • responseMode = ExecuteURL

 

 

Once we add a web.config page, we will have instituted an Application wide error handling pathway.

The Application wide pathway will work for all cases, except when “on error resume next” is in-effect.

 

How do we test?

How do we investigate whether we can handle errors implicitly?

We will remove “on error resume next”.

Const ON_ERROR_RESUME_NEXT = false
rem Const ON_ERROR_RESUME_NEXT = true

 

Just-In-Time Debugger:

JustInDebugger

 

We will not trod down the Debugging trek; and so we will skip initiating the debugger by choosing “No, cancel debugging“.

 

Here is what we see when a code error and IIS itself triggers our error page

errorHandlerCustomizedOutput-Generic

 

Explanation

We can see that we have lost useful error data.  Earlier we preserved them by saving them to session variables and then transferring processing to the Error Page.

Spent all weekend trying to get to them when IIS auto-invokes the error page, but not smart enough.

 

On error goto 0

“On error goto 0” return us back to sanity.  I will suggest that for each “on error resume next”, a corresponding  “on error goto 0” be added as a complement.

 

Exception Handling

Modern languages idiom rely on try/catch/finally exception paradigm.

 

Source Control

Github

To make it easier to share and review our simple App, we have place it on Github.

https://github.com/DanielAdeniji/ClassicASPOnErrorResumeNext

 

Where did Classic ASP come from?

Accordingly to Wikipedia, Classic ASP was introduced as part of NT Option Pack in 1996.  And, discontinued 4 years later in 2000.

NT Option Pack is one of the most important product release from Microsoft.  Keeping in mind that it was a midterm release between Windows NT 4.0 and Windows 2000, and Microsoft was trying to stem the tide of competing products; and so everything along with the Kitchen Sink and the proverbial workman’s hamburger was included.

According to Windows NT Option Pack, here is what was bundled within it:

  • Certificate Server – Microsoft Certificate Server provides customization services for issuing and managing certificates used in software security systems employing public-key cryptography.
  • FrontPage Server Extensions
  • Index Server – It allows you to easily perform full-text searches and retrieve all types of information from any Web browser.
  • Internet Connection Services for RAS – Remote Access Service
  • Internet Information Server (IIS) version 4.0
  • Mail and News Services – Microsoft SMTP Service uses the standard Internet protocol Simple Mail Transfer Protocol (SMTP) to transport and deliver messages
  • The Microsoft NNTP Service – Newsgroup
  • The Microsoft Data Access Components (MDAC) – ActiveX Data Objects and the Microsoft Access driver
  • Microsoft Management Console (MMC) – Uniform interface for managing server application
  • Microsoft Message Queue Server (MSMQ) – communicate with other application programs quickly, reliably, and asynchronously by sending and receiving messages
  • Microsoft Transaction Server (MTS) – component-based transaction processing system for developing, deploying, and managing high-performance, scalable, and robust applications
  • Site Server – Comprehensive Web site environment for enhancing, deploying, and managing rich intranet and Internet Web sites
  • Microsoft SNA Server – comprehensive gateway and application integration platform that enables communications with midrange ( AS/40)0 and mainframe systems
  • Windows Script Host – a language-independent scripting host for ActiveX™ scripting engines

 

Dedicated

Once again, I will dedicate this post to a public commit-er, former Microsoft engineer, Eric Lippert. I referenced an important blog post by him in the Reference section below.

Eric blogs @ http://ericlippert.com/ and he has an interesting post @ “Eric Lippert Dissects CVE-2014-6332, a 19 year-old Microsoft bug” ( http://security.coverity.com/blog/2014/Nov/eric-lippert-dissects-cve-2014-6332-a-19-year-old-microsoft-bug.html )

 

Summary

For a product released in 1996 and deprecated in 2000; ASP has obvious staying power.

For the sake of the numerous companies and products that continue to rely on it, I wish keen attention, knowledge and courage.

For sustenance engineers, keep an eye out for “On error resume next“.

References

Configuration

Reference – httpErrors and customErrors

 

customErrors

Microsoft

Blog and Q/A

 

IIS – Custom Errors – Sample Code

Microsoft

ASP.Net

 

3rd Party Vendor

Q/A

 

Storing & Transfering Data

 

Collection Object

Dictionary Object

 

Session Variables

 

Context

 

ASP

Best Practices

ASP Error Handling – Reference

 

ASP Error Handling – Sample

 

Including File

 

Test Object

 

 

ASP.Net

Error Handling

 

Page Transition

Move to new page

Response.Redirect

Server.Transfer

 

 

“On Error Resume Next ” Stories