Error and Exception Handling Policy: Difference between revisions

From OpenPetra Wiki
Jump to navigation Jump to search
No edit summary
 
(27 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==DRAFT DOCUMENT==
==Exception Handling==
'''TODO:''' This document is a DRAFT and needs to be revised and extended, especially in the areas of Client and Server error handling.
===Overall===
'''Exceptions should be thrown...'''
* '''whenever exceptional conditions occur in a program (e.g. a Method failure or unexpected states of Objects) and the program cannot continue because of this;'''
* '''when other error states (determined by program logic) are detected in code''' and the handling for those is outside the current program scope (e.g. Method, Property Getter/Setter, etc.).
* '''for the [[Error and Exception Handling Policy#OpenPetra_Exception_for_signalling_of_Data_Verification_Errors | signalling data verification errors]]'''.
 
'''Exceptions ''must not be thrown...'''''
* '''simply to change the flow of a program''' Use other control logic (e.g. <code>if</code> or <code>switch</code> statements) for such purposes.
 
 
Raising of Exceptions is not considered costly nowadays. We are happy to throw Exceptions across .NET Remoting boundaries even if this costs some resources because the Exceptions need to be Serialized/Deserialized - Exceptions should be thrown in exceptional circumstances only and therefore this should not be a problem.
 
''The page [[DB Access Exception Handling Policy]] is an extension of this policy!''
 
===Practices That Need to be Adhered to===
====General====
=====<code>System.Exception</code> or <code>System.SystemException</code> - *** DON'T RAISE *** =====
* ''Do not'' raise an Exception of Type <code>System.Exception</code>.
** Reasoning: Use one of [[Error and Exception Handling Policy#Use_of_OpenPetra_Custom_Exceptions | OpenPetras' Custom Exceptions]] or Custom Exception Base Classes (<code>EOPAppException</code> and <code>EOPDBException</code>) to make it clear that an Exception is raised on purpose by the application and to disambiguate them from other Exceptions that might get raised by .NET or one of the Third-party libraries we use.
* ''Do not'' raise an Exception of Type <code>System.SystemException</code>.
** Reasoning #1: That particular Exception is reserved to be thrown from the .NET Runtime.
** Reasoning #2: See reasoning why <code>System.Exception</code> should not be used.
 
====='Swallowing' Exceptions - *** DON'T DO IT! ***=====
'''Exceptions must never be 'swallowed'''', that is, get caught and not get re-raised again! Would this be done, one would let the program carry on as if everything is fine, while in reality an exceptional condition has occurred that would need attention. As a result, the program would likely become unstable and would be left in a non-deterministic state.
 
=====Catching Exceptions That Result From Programming Errors - *** DON'T DO IT! ***=====
Exceptions which are a result of a programming error shall never be caught, e.g. NullPointerException. This would mask the original error, therefore making it hard to find it.
 
=====Catching an Exception and Re-raising the Same Exception=====
'''An Exception shall not be caught and then the same Exception be thrown again.'''


Input should be given on [[Inconsistent Error and Exception Handling|this]] page.
There are three notable ''exceptions to this rule'', though:
# Logging of the Exception should take place in that very Exception Handler.
# Resources need to be freed in case of an Exception, e.g. DB transaction rolled backed, handles freed, ...
# User Interface update needs to happen that can not be achieved otherwise, e.g. wait cursor resetted, status bar text set, ...


==Overall==
''If an Exception is caught and the same exception should be re-thrown then it is important to do this in the right manner:''
'''Exceptions should be thrown...'''
* Correct: <code>throw;</code>
* '''whenever genuine Exceptions occur;'''
* Wrong: <code>throw Exc;</code> (where <code>Exc</code> is the instance of the Exception that got caught). Reasoning: The Stack Trace is lost through this!
* '''when other error states (determined by program logic) are detected in code''' and the handling for those is outside the current program scope (e.g. Method, Property Getter/Setter, etc.).  
* Wrong: <code>throw new TheExceptionType();</code> (where <code>TheExceptionType</code> is the Type of the Exception that got caught). Reasoning: The Stack Trace is lost through this!
 
=====Raising Another Exception Than The Original Exception (Translating Exceptions)=====
Catching of a ''specific'' Exception (''not System.Exception!!!'') and throwing of another Exception (possibly being a more precise one) instead in that Exception Handler is allowed. ''' ''HOWEVER'', throwing different Exceptions should generally be ''avoided''''', as it makes it harder to see what has originally happened. (An exception to that rule is the throwing of an [[Error and Exception Handling Policy#Use of OpenPetra Custom Exceptions | OpenPetra Custom Exception]] [such as <code>EOPAppException</code> or one of the Exceptions that derive from it] --> see below!)
Use of the 'InnerException' Property (see below) helps in mitigating this, but it can still be tricky to find the root of a problem if Exceptions are 'translated' --- if a different Exception is thrown, the reason for doing so ''should be carefully assessed'' and the pros and cons need to be considered, as it must not cause confusion in ''any situation'' in which it might be handled by an Exception Handler in ''any'' position in the Call Stack.
 
The new Exception may be ''a different Exception altogether'' if this adds meaning or allows adding more information (in the exception message, or in Fields of Custom Exceptions) but it ''needs careful assessment'' as to whether this is appropriate (see previous paragraph!).
 
Throwing of a new Exception which is a ''generalisation'' of the original Exception (e.g. catching a <code>System.NullReferenceException</code> and raising a <code>System.Exception</code> instead) is '''strongly discouraged''', as valuable Type information is lost in doing so! (While this could be mitigated by putting the original Exception in the InnerException Property of the new Exception [see below], a generalisation is still making it harder to find the root of a problem as InnerExceptions are not usually evaluated programmatically, but only by a programmer when (s)he is debugging.)
 
'''Special Case: Throwing an [[Error and Exception Handling Policy#Use of OpenPetra Custom Exceptions | OpenPetra Custom Exception]]''': Expected (System-)Exceptions shall be caught and be put into an <code>EOPAppException</code> or into one of the Exceptions that derive from it. This helps to ''differentiate between program errors and expected Exception conditions''. Always add the original Exception as the 'InnerException' of the OpenPetra-specific Exception (see below)!
* Put an Exception into an <code>EOPDBException</code> rather than into an <code>EOPAppException</code> if the source for the Exception is Database-related, Database Access-Layer related (TTypedDataAccess Class), or Database Abstraction-Layer related (Classes contained in Project Ict.Common.DB).
 
'''Use of InnerException Property:''' When another Exception is raised than the original Exception, the ''original Exception '''must''' be added as the ''''InnerException'''' of the newly raised Exception''. In doing so, the original Exception information isn't lost (incl. the code line where the original Exception occurred [if the Assembly is built in the Debug rather than the Release configuration]!).
* This is done by adding the instance of the original Exception as the second Argument when creating the instance of the new Exception.
** Example: <code>throw new EOPAppException("Error caused by trying ThrowInner.", Exp);</code>, where <code>Exp</code> is the instance of the original Exception.
** Full Example: see [http://msdn.microsoft.com/en-us/library/system.exception.innerexception(v=VS.80).aspx Exception.InnerException Property]
 
=====Use of OpenPetra Custom Exceptions=====
.NET allows the creation of Custom Exceptions and OpenPetra utilises this feature heavily. '''In most cases it is more appropriate to raise an OpenPetra Custom Exception rather than a .NET-built-in Exception. See also [[Error and Exception Handling Policy#Do not raise System.Exception or System.SystemException | Do not raise System.Exception or System.SystemException]]!''' (It is perfectly appropriate to raise <code>ArgumentException</code>, <code>ArgumentNullException</code> etc, however - do not raise custom OpenPetra Exceptions for such purposes!).
 
Custom Exceptions are useful for several reasons:
* Their Name can tell much more about an Exception situation than a built-in Exception of the .NET Framework;
* They can contain arbitrary additional, typed data;
* It is possible to create custom Exceptions that themselves derive from another Custom Exception. Through that a hierarchy of Custom Exceptions can be created. [[Error and Exception Handling Policy#OpenPetra_Exception_Hierarchy | OpenPetra has such hierarchies in place]].
 
Type Naming conventions: See [[Coding Standard and Guidelines#Exception Naming Guidelines | Exception Naming Guidelines]]
 
In many cases the creation of a new Custom Exception that has no additional information is sufficient as the Type Name of the new Exception already conveys enough. For Exception handling situations where additional information should be passed to Exception Handlers higher up in the Call Stack, extra data should be added to the custom Exception that is thrown. This helps in handling the Exception because it is possible to process the additional information in Exception Handlers that know about this information.
 
Examples in <code>\csharp\ICT\Petra\Shared\RemotedExceptions.cs</code>:
* Simple Custom Exception: Class '<code>EPagedTableNoRecordsException</code>'
* Custom Exception that holds additional data: Class '<code>ESecurityPartnerAccessDeniedException</code>' (This Exception is derived from another Custom Exception, <code>ESecurityAccessDeniedException</code>. The latter forms the 'bottom' of an additional Exception hierarchy.)
 
See also: [[A Use Case for a Custom Exception]]
 
======OpenPetra Exception Hierarchy======
OpenPetra has several hierarchies of Custom Exceptions in place.
 
The Base Classes for all Custom Exceptions in OpenPetra are:
* '''<code>EOPAppException</code>''': Base Class for all Custom Exceptions in OpenPetra, except for the cases where <code>EOPDBException</code> ought to be used (see next bullet point).
* <code>EOPDBException</code>: Only if the Exception is Database-related, Database Access-Layer related (<code>TTypedDataAccess</code> Class), or Database Abstraction-Layer related (Classes contained in Project <code>Ict.Common.DB</code>).
 
''Exceptions of these Base Class Types may be raised in OpenPetra program code'' - it is of course not necessary to create derived Custom Exceptions for 'each and every case'. Derived Custom Exceptions do make sense when Exceptions should be caught based on the Type of a Derived Custom Exception, or when an additional Exception hierarchy is to be established.
 
======OpenPetra Exception for signalling of Data Verification Errors======
The Custom Exception <code>EVerificationResultsException</code> exists for the purposes of for signalling of Data Verification Errors.
 
To signalise a Data Verification Error to a caller of a Method:
* create (a) <code>TVerificationResult</code> instance(s) and an <code>TVerificationResultCollection</code> instance for this;
* add the <code>TVerificationResult</code> instance(s) to the <code>TVerificationResultCollection</code> instance;
* add the <code>TVerificationResultCollection</code> instance to the <code>EVerificationResultsException</code> by setting its '<code>VerificationResults</code>' Property (that can be conveniently be done in a Constructor overload).
* throw that <code>EVerificationResultsException</code> instance.
 
''Important'': Callers of the Method that can throw <code>EVerificationResultsException</code>(s) must be able to handle that Exception, otherwise an 'Unhandled Exception' would occur in the application!


======Custom Exceptions crossing .NET Remoting Boundaries======
''All our Custom Exceptions need to be able to cross .NET Remoting boundaries and need to keep their specific data in doing so''.
All the information that is encapsulated in an Exception is transported without loss of fidelity across .NET Remoting boundaries (this works fine from Linux/mono to Windows/MS .NET as well!).


Raising of Exceptions is not considered costly nowadays. We are happy to throw Exceptions across .NET Remoting boundaries as well, as long this occurs infrequently (see [[Error and Exception Handling Policy#Exception Handling across .NET Remoting Boundaries .28Server_-.3E_Client.29|below]]).  
To ensure that Exception-specific data is kept when crossing .NET Remoting boundaries, ''care needs to be taken that any data that is specific to a Custom Exception is not lost during Serialization/Deserialization''! Crucial for that is the correct implementation of the Constructor that has the Arguments 'SerializationInfo' and 'StreamingContext' and of the Method '<code>GetObjectData</code>'.
* Example: <code>\csharp\ICT\Petra\Shared\RemotedExceptions.cs</code>, Class '<code>ESecurityPartnerAccessDeniedException</code>'


==Practices That Need to be Adhered to==
======Custom Exceptions Must Be Specified In Shared Assemblies or in certain Ict.Common.* Namespaces======
===General===
All Custom Exceptions must be specified in DLL's that are accessible to both Client and Server in order for them to be passed from Server to Client (their Type information needs to be available to the Client as .NET Remoting needs to be able to deserialize the exact Type whenever they are thrown on the server side). Therefore Custom Exceptions need to be specified in the Namespace <code>Ict.Petra.Shared</code> or one of its Subnamespaces (or in the Namespaces <code>Ict.Common.Exceptions</code> and <code>Ict.Common.''xx''.Exceptions</code>, which are also accessible from both Client and Server side).
* '''Exceptions must never be 'swallowed'''', that is, get caught and not get re-raised again!
** What ''is allowed'' is catching of a certain Exception and throwing of another Exception instead (in that Exception handler). However, the new Exception must never be a generalisation. Rather it needs to be a ''specialisation'' of the Exception that gets handled. Alternatively it can be ''a different Exception altogether'', if this is meaningful. The second option needs to be carefully assessed and the pros and cons need to be considered - it must not cause confusion in ''any situation'' where it might be handled by a caller.
*** Example - ALLOWED: catching a <code>System.Exception</code> and raising e.g. a <code>System.IO.IOException</code> or a custom openPETRA Exception (both result in a specialisation, which is fine).
*** Example - NOT ALLOWED: catching a <code>System.NullReferenceException</code> and raising a <code>System.Exception</code> instead (that would be a generalisation, in which valuable information is lost!).
** When another Exception is raised than the original Exception, the ''original Exception '''must''' be added as the 'InnerException' of the newly raised Exception''. In doing so the original Exception information isn't lost (incl. the code line where the original Exception occured [if the Assembly is built in the Debug rather than the Release configuration]!).
*** This is done by adding the instance of the original Exception as the second Argument when creating the instance of the new Exception.
**** Example: <code>throw new MyAppException("Error caused by trying ThrowInner.", Exp);</code>, where <code>Exp</code> is the instance of the original Exception.
**** Full Example: see [http://msdn.microsoft.com/en-us/library/system.exception.innerexception(v=VS.80).aspx Exception.InnerException Property]
* '''TODO'''


===File Operations===
====File Operations====
'''TODO'''
'''TODO'''


===Server-specific Considerations===
'''TODO'''


====DB Access====
====Server-specific Considerations====
'''TODO'''
====== DB Exception Handling ======
See [[DB Access Exception Handling Policy]].


===Client-specific Considerations===
=====Typed Data Access Classes ('DataStore')=====
'''TODO'''
====== '<code>...SubmitChanges</code>' Methods ======
The auto-generated '<code>...SubmitChanges</code>' Methods in OpenPetras' Typed Data Access Classes ('DataStore') throw a EVerificationResultsException in case
there was at least one Row in the passed-in DataTable whose RowState is DataViewRowState.Deleted ''which cannot be deleted'' because at least one record in at least one other DB Table references that record/those records (by ways of DB Referential Integrity).


'''Returned EVerificationResultsException Instance'''
The Exception Message is 'speaking' and the instance holds a TVerificationResultCollection. This contains detailed information about the data that references this/those Rows (this can be accessed using the '<code>VerificationResults</code>' Property of the EVerificationResultsException instance).


==Exception Handling across .NET Remoting Boundaries (Server -> Client)==
====Client-specific Considerations====
All the information that is encapsulated in an Exception is transported without loss of fidelity across .NET Remoting boundaries (this works fine from Linux/mono to Windows/MS .NET as well!).
=====Aborting a Data Saving Operation=====
'''TODO'''


We are happy to throw Exceptions across .NET Remoting boundaries. However, this this should be done infrequently as throwing Exceptions across .NET Remoting boundaries is more costly then throwing Exceptions in the same Operating System process.  
===Future Improvements===
Be sure to ''never'' throw Exceptions across .NET Remoting boundaries ''in a loop'' of some sort because this could result in a lot of Exceptions which would all need to be remoted, and this would be quite costly!
* We could add support for I18N to the Base Class <code>EOPAppException</code>: The goal of that would be that error message could be shown to the user in various languages.
** It should be possible to show an 'internationalised' exception with any 'embedded' data pieces. Placeholders like {0} would need to be accommodated in order for that to work.
** Exceptions should always be logged in English to the log file (no I18N)!


===Custom Exceptions crossing .NET Remoting Boundaries===
==Error Handling==
If a custom Exception needs to be able to cross .NET Remoting boundaries and keep its specific data in doing so, care needs to be taken that any custom Exception data is not lost!
===Error Codes: OpenPetra===
* In OpenPetra errors that are raised to the attention of the user always have an error code. This includes data verification errors and warnings.
* Error Codes and the related messages are coded as C# Constants with C# Attributes.


Of special importance is the implementation of the Method '<code>GetObjectData</code>': check out some of our custom Exceptions in the Namespace <code>Ict.Petra.Shared.RemotedExceptions</code> to see examples on how this is done properly!
For details refer to [[Error Codes in Openpetra]].
Central list of already existing error codes in OpenPetra: [https://ci.openpetra.org/job/OpenPetraCodeDoc/doclinks/1/].


====Obligatory Namespace for Remoted Exceptions====
===Error Codes: Implementation in Petra 2.x===
All custom Exceptions which can cross .NET Remoting boundaries must be specified in the Namespace <code>Ict.Petra.Shared.RemotedExceptions</code>.
* In Petra 2.x errors that are raised to the attention of the user always have an error code. This includes data verification errors and warnings.
* A DB Table, 's_error_message', holds error codes and the associated error messages centrally.  
* A central function exists which displays these errors when the software developer supplies the error code.

Latest revision as of 14:01, 14 Ocak 2015

Exception Handling

Overall

Exceptions should be thrown...

  • whenever exceptional conditions occur in a program (e.g. a Method failure or unexpected states of Objects) and the program cannot continue because of this;
  • when other error states (determined by program logic) are detected in code and the handling for those is outside the current program scope (e.g. Method, Property Getter/Setter, etc.).
  • for the signalling data verification errors.

Exceptions must not be thrown...

  • simply to change the flow of a program Use other control logic (e.g. if or switch statements) for such purposes.


Raising of Exceptions is not considered costly nowadays. We are happy to throw Exceptions across .NET Remoting boundaries even if this costs some resources because the Exceptions need to be Serialized/Deserialized - Exceptions should be thrown in exceptional circumstances only and therefore this should not be a problem.

The page DB Access Exception Handling Policy is an extension of this policy!

Practices That Need to be Adhered to

General

System.Exception or System.SystemException - *** DON'T RAISE ***
  • Do not raise an Exception of Type System.Exception.
    • Reasoning: Use one of OpenPetras' Custom Exceptions or Custom Exception Base Classes (EOPAppException and EOPDBException) to make it clear that an Exception is raised on purpose by the application and to disambiguate them from other Exceptions that might get raised by .NET or one of the Third-party libraries we use.
  • Do not raise an Exception of Type System.SystemException.
    • Reasoning #1: That particular Exception is reserved to be thrown from the .NET Runtime.
    • Reasoning #2: See reasoning why System.Exception should not be used.
'Swallowing' Exceptions - *** DON'T DO IT! ***

Exceptions must never be 'swallowed', that is, get caught and not get re-raised again! Would this be done, one would let the program carry on as if everything is fine, while in reality an exceptional condition has occurred that would need attention. As a result, the program would likely become unstable and would be left in a non-deterministic state.

Catching Exceptions That Result From Programming Errors - *** DON'T DO IT! ***

Exceptions which are a result of a programming error shall never be caught, e.g. NullPointerException. This would mask the original error, therefore making it hard to find it.

Catching an Exception and Re-raising the Same Exception

An Exception shall not be caught and then the same Exception be thrown again.

There are three notable exceptions to this rule, though:

  1. Logging of the Exception should take place in that very Exception Handler.
  2. Resources need to be freed in case of an Exception, e.g. DB transaction rolled backed, handles freed, ...
  3. User Interface update needs to happen that can not be achieved otherwise, e.g. wait cursor resetted, status bar text set, ...

If an Exception is caught and the same exception should be re-thrown then it is important to do this in the right manner:

  • Correct: throw;
  • Wrong: throw Exc; (where Exc is the instance of the Exception that got caught). Reasoning: The Stack Trace is lost through this!
  • Wrong: throw new TheExceptionType(); (where TheExceptionType is the Type of the Exception that got caught). Reasoning: The Stack Trace is lost through this!
Raising Another Exception Than The Original Exception (Translating Exceptions)

Catching of a specific Exception (not System.Exception!!!) and throwing of another Exception (possibly being a more precise one) instead in that Exception Handler is allowed. HOWEVER, throwing different Exceptions should generally be avoided, as it makes it harder to see what has originally happened. (An exception to that rule is the throwing of an OpenPetra Custom Exception [such as EOPAppException or one of the Exceptions that derive from it] --> see below!) Use of the 'InnerException' Property (see below) helps in mitigating this, but it can still be tricky to find the root of a problem if Exceptions are 'translated' --- if a different Exception is thrown, the reason for doing so should be carefully assessed and the pros and cons need to be considered, as it must not cause confusion in any situation in which it might be handled by an Exception Handler in any position in the Call Stack.

The new Exception may be a different Exception altogether if this adds meaning or allows adding more information (in the exception message, or in Fields of Custom Exceptions) but it needs careful assessment as to whether this is appropriate (see previous paragraph!).

Throwing of a new Exception which is a generalisation of the original Exception (e.g. catching a System.NullReferenceException and raising a System.Exception instead) is strongly discouraged, as valuable Type information is lost in doing so! (While this could be mitigated by putting the original Exception in the InnerException Property of the new Exception [see below], a generalisation is still making it harder to find the root of a problem as InnerExceptions are not usually evaluated programmatically, but only by a programmer when (s)he is debugging.)

Special Case: Throwing an OpenPetra Custom Exception: Expected (System-)Exceptions shall be caught and be put into an EOPAppException or into one of the Exceptions that derive from it. This helps to differentiate between program errors and expected Exception conditions. Always add the original Exception as the 'InnerException' of the OpenPetra-specific Exception (see below)!

  • Put an Exception into an EOPDBException rather than into an EOPAppException if the source for the Exception is Database-related, Database Access-Layer related (TTypedDataAccess Class), or Database Abstraction-Layer related (Classes contained in Project Ict.Common.DB).

Use of InnerException Property: When another Exception is raised than the original Exception, the original Exception must be added as the 'InnerException' of the newly raised Exception. In doing so, the original Exception information isn't lost (incl. the code line where the original Exception occurred [if the Assembly is built in the Debug rather than the Release configuration]!).

  • This is done by adding the instance of the original Exception as the second Argument when creating the instance of the new Exception.
    • Example: throw new EOPAppException("Error caused by trying ThrowInner.", Exp);, where Exp is the instance of the original Exception.
    • Full Example: see Exception.InnerException Property
Use of OpenPetra Custom Exceptions

.NET allows the creation of Custom Exceptions and OpenPetra utilises this feature heavily. In most cases it is more appropriate to raise an OpenPetra Custom Exception rather than a .NET-built-in Exception. See also Do not raise System.Exception or System.SystemException! (It is perfectly appropriate to raise ArgumentException, ArgumentNullException etc, however - do not raise custom OpenPetra Exceptions for such purposes!).

Custom Exceptions are useful for several reasons:

  • Their Name can tell much more about an Exception situation than a built-in Exception of the .NET Framework;
  • They can contain arbitrary additional, typed data;
  • It is possible to create custom Exceptions that themselves derive from another Custom Exception. Through that a hierarchy of Custom Exceptions can be created. OpenPetra has such hierarchies in place.

Type Naming conventions: See Exception Naming Guidelines

In many cases the creation of a new Custom Exception that has no additional information is sufficient as the Type Name of the new Exception already conveys enough. For Exception handling situations where additional information should be passed to Exception Handlers higher up in the Call Stack, extra data should be added to the custom Exception that is thrown. This helps in handling the Exception because it is possible to process the additional information in Exception Handlers that know about this information.

Examples in \csharp\ICT\Petra\Shared\RemotedExceptions.cs:

  • Simple Custom Exception: Class 'EPagedTableNoRecordsException'
  • Custom Exception that holds additional data: Class 'ESecurityPartnerAccessDeniedException' (This Exception is derived from another Custom Exception, ESecurityAccessDeniedException. The latter forms the 'bottom' of an additional Exception hierarchy.)

See also: A Use Case for a Custom Exception

OpenPetra Exception Hierarchy

OpenPetra has several hierarchies of Custom Exceptions in place.

The Base Classes for all Custom Exceptions in OpenPetra are:

  • EOPAppException: Base Class for all Custom Exceptions in OpenPetra, except for the cases where EOPDBException ought to be used (see next bullet point).
  • EOPDBException: Only if the Exception is Database-related, Database Access-Layer related (TTypedDataAccess Class), or Database Abstraction-Layer related (Classes contained in Project Ict.Common.DB).

Exceptions of these Base Class Types may be raised in OpenPetra program code - it is of course not necessary to create derived Custom Exceptions for 'each and every case'. Derived Custom Exceptions do make sense when Exceptions should be caught based on the Type of a Derived Custom Exception, or when an additional Exception hierarchy is to be established.

OpenPetra Exception for signalling of Data Verification Errors

The Custom Exception EVerificationResultsException exists for the purposes of for signalling of Data Verification Errors.

To signalise a Data Verification Error to a caller of a Method:

  • create (a) TVerificationResult instance(s) and an TVerificationResultCollection instance for this;
  • add the TVerificationResult instance(s) to the TVerificationResultCollection instance;
  • add the TVerificationResultCollection instance to the EVerificationResultsException by setting its 'VerificationResults' Property (that can be conveniently be done in a Constructor overload).
  • throw that EVerificationResultsException instance.

Important: Callers of the Method that can throw EVerificationResultsException(s) must be able to handle that Exception, otherwise an 'Unhandled Exception' would occur in the application!

Custom Exceptions crossing .NET Remoting Boundaries

All our Custom Exceptions need to be able to cross .NET Remoting boundaries and need to keep their specific data in doing so. All the information that is encapsulated in an Exception is transported without loss of fidelity across .NET Remoting boundaries (this works fine from Linux/mono to Windows/MS .NET as well!).

To ensure that Exception-specific data is kept when crossing .NET Remoting boundaries, care needs to be taken that any data that is specific to a Custom Exception is not lost during Serialization/Deserialization! Crucial for that is the correct implementation of the Constructor that has the Arguments 'SerializationInfo' and 'StreamingContext' and of the Method 'GetObjectData'.

  • Example: \csharp\ICT\Petra\Shared\RemotedExceptions.cs, Class 'ESecurityPartnerAccessDeniedException'
Custom Exceptions Must Be Specified In Shared Assemblies or in certain Ict.Common.* Namespaces

All Custom Exceptions must be specified in DLL's that are accessible to both Client and Server in order for them to be passed from Server to Client (their Type information needs to be available to the Client as .NET Remoting needs to be able to deserialize the exact Type whenever they are thrown on the server side). Therefore Custom Exceptions need to be specified in the Namespace Ict.Petra.Shared or one of its Subnamespaces (or in the Namespaces Ict.Common.Exceptions and Ict.Common.xx.Exceptions, which are also accessible from both Client and Server side).

File Operations

TODO


Server-specific Considerations

DB Exception Handling

See DB Access Exception Handling Policy.

Typed Data Access Classes ('DataStore')
'...SubmitChanges' Methods

The auto-generated '...SubmitChanges' Methods in OpenPetras' Typed Data Access Classes ('DataStore') throw a EVerificationResultsException in case there was at least one Row in the passed-in DataTable whose RowState is DataViewRowState.Deleted which cannot be deleted because at least one record in at least one other DB Table references that record/those records (by ways of DB Referential Integrity).

Returned EVerificationResultsException Instance The Exception Message is 'speaking' and the instance holds a TVerificationResultCollection. This contains detailed information about the data that references this/those Rows (this can be accessed using the 'VerificationResults' Property of the EVerificationResultsException instance).

Client-specific Considerations

Aborting a Data Saving Operation

TODO

Future Improvements

  • We could add support for I18N to the Base Class EOPAppException: The goal of that would be that error message could be shown to the user in various languages.
    • It should be possible to show an 'internationalised' exception with any 'embedded' data pieces. Placeholders like {0} would need to be accommodated in order for that to work.
    • Exceptions should always be logged in English to the log file (no I18N)!

Error Handling

Error Codes: OpenPetra

  • In OpenPetra errors that are raised to the attention of the user always have an error code. This includes data verification errors and warnings.
  • Error Codes and the related messages are coded as C# Constants with C# Attributes.

For details refer to Error Codes in Openpetra. Central list of already existing error codes in OpenPetra: [1].

Error Codes: Implementation in Petra 2.x

  • In Petra 2.x errors that are raised to the attention of the user always have an error code. This includes data verification errors and warnings.
  • A DB Table, 's_error_message', holds error codes and the associated error messages centrally.
  • A central function exists which displays these errors when the software developer supplies the error code.