Instructions for the Implementation of Data Validation
DOCUMENTATION IS WORK IN PROGRESS
This wiki page is under construction. Information contained on it should not be relied on until this message is no longer present!!!
Overview
In OpenPetra, different aspects of Data Validation need to be implemented in several places to make the whole 'Data Validation Framework' work - on Client Side and Server Side.
The sections below detail what needs to be done and in which places to make Data Validation work. Following the way that is presented in those sections will help you in creating manual Data Validation and should help the whole development team to code Data Validation in a common and standardised way.
Data Validation in OpenPetra needs to be done on both client and server side. You can read more about this in the section 'Server Side'.
Automatic Data Validation is also in place in OpenPetra. Read this wiki page to learn about it.
Client Side
YAML File of a Form or UserControl
- For every Control to which manual Data Validation should be added, the Attribute Validation=true needs to be added.
- The 'Validation' Attribute is not available for Controls that group other controls, such as GroupBoxes, Panels, TabPages, UserControls, etc. - It is available only for individual Controls.
Examples
dtpDetailMailingDate: {Label=Mailing Date, Validation=True}
(found in \csharp\Petra\Client\MPartner\Gui\Setup\MailingSetup.yaml)
Once the YAML file gets (re-)generated with the WinForms Generator (using the nant generateWinforms
command), support for Data Validation will be added to the auto-generated C# code file of the Form/UserControl if the 'Validation' property has been set to 'true' for at least one Control. (If automatically generated Data Validation code for Controls is present in the auto-generated C# code file of the Form/UserControl, support for Data Validation will be added/will have been added to that file even if no manual Data Validation is specified.)
Multiple Controls involved in a single Data Validation
TODO
*.ManualCode.cs File
In order for the manual Data Validation to be doing something useful, a Method with one of the following Method names needs to be present in the *.ManualCode.cs file of the Form/UserControl:
private void ValidateDataManual(RowType ARow) private void ValidateDataDetailsManual(RowType ARow)
(where RowType is the Type of the Typed DataRow that is to be validated in this screen, e.g. 'PMailingRow' for a screen that edits 'p_mailing' DB Table records)
The name of the Method depends on whether the Data Validation of a Form/UserControl should validate data in a 'Details Section' (=a pnlDetails Panel).
- If it should not validate data in a 'Details Section', the name of the Method needs to be '
ValidateDataManual
' in order to validate the Form's/UserControl's data. - If it should validate data in a 'Details Section' then the name of the Method needs to be '
ValidateDataDetailsManual
' in order to validate the data in a 'Details Section'.- If data of the Form/UserContol outside of the 'Details Section' should be validated as well, then the Method named '
ValidateDataManual
' needs to be present in addition to the 'ValidateDataDetailsManual
' Method in order to validate the data outside of the 'Details Section'. An example of this can be found in\csharp\ICT\Petra\Client\MFinance\Gui\AP\APEditDocument.ManualCode.cs
- If data of the Form/UserContol outside of the 'Details Section' should be validated as well, then the Method named '
After adding this Method/these Methods, nant generateWinForms
needs to be run for the corresponding YAML file. This ensures that this Method/these Methods is/are called from the generated code of the Form/UserControl and that through that the manual Data Validation code will be run in all circumstances where it needs to be run.
The content of either of the Methods follows a standard pattern:
- The content of the first line is always
TVerificationResultCollection VerificationResultCollection = FPetraUtilsObject.VerificationResultCollection;
- A single empty line follows.
- A single call to a Method that is to be defined in a Shared DLL follows. That Method performs the actual Data Validation.
NOTE: Although the called Method could reside in a client-side DLL, doing so would make it impossible to perform the Data Validation from the server side! The same would be true if any other code would be added to the Method described in this section - it would be inaccessible from the server side. Since we want Data Validation to work both on client and on server side, the called Method (that contains the Data Validation code) needs to be placed in shared DLL's!
Sample code: (found in file \csharp\ICT\Petra\Client\MPartner\Gui\Setup\MailingSetup.ManualCode.cs)
private void ValidateDataDetailsManual(PMailingRow ARow) { TVerificationResultCollection VerificationResultCollection = FPetraUtilsObject.VerificationResultCollection; TSharedPartnerValidation_Partner.ValidateMailingSetup(this, ARow, ref VerificationResultCollection, FPetraUtilsObject.ValidationControlsDict); }
For information about the called Method that is to be defined in a Shared DLL and which performs the actual Data Validation please see the section Shared Libraries.
The Data Validation Method
The ValidateDataManual
/ ValidateDataDetailsManual
Method situated in the *.ManualCode.cs file of the Form/UserContro needs to call a Method in a shared DLL.
That called Method performs the actual Data Validation:
- If a Data Validation occurs, a TVerificationResult object is created
- the TVerificationResult object gets appended to a Collection that is held in the Form where the Data Validation is run.
- Once a Data Validation error got rectified by the user, the corresponding TVerificationResult object is removed from the Collection.
NOTE: Although this Method could reside in a client-side DLL, doing so will make it impossible to perform the Data Validation from the server side! Since we want Data Validation to work both on client and on server side, the Method that contains the Data Validation code needs to be placed in shared DLL's!
Typical Method signature:
public static void ValidateMailingSetup(object AContext, PMailingRow ARow, ref TVerificationResultCollection AVerificationResultCollection, TValidationControlsDict AValidationControlsDict)
The Method needs to have a ref VerificationResultCollection
Argument, among other Arguments. By modifying that Collection, this Method can add or remove specific TVerificationResult objects to/from the Form's Collection of TVerificationResult objects (which is always held in the FPetraUtilsObject.VerificationResultCollection object).
(The Method can optionally inspect that Collection for other TVerificationResult objects and evaluate those [that is needed only for complex data validation scenarios, though]. As the Collection will hold all TVerificationResult objects of the Form, TVerificationResult objects from other parts of the screen [namely those created by another UserControl, perhaps situated even on a different TabPage of the same Form] can be inspected!)
The content of the Method follows a standard pattern:
- There is a Variable declaration section. This is 'boiler plate' code.
- 1..n 'Data Validation Code Blocks' follow.
Each 'Data Validation Code Block' follows a standard pattern:
- The DataColumn that is to be validated is stored in the ValidationColumn Variable.
- A check is done whether that DataColumn should be validated at the current execution of the Method. This is done with a single line of 'boiler plate' code.
- If so,
- the actual data validation code is run,
- the addition/removal of the corresponding TVerificationResult is handled. Again, this is done with a single line of 'boiler plate' code.
The check whether the DataColumn should be validated seems strange, but that makes it possible to check for a different set of DataColumns, depending if the Method got called from the client-side or the server-side of OpenPetra.
Sample code: (found in \csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs, Method 'ValidateMailingSetup')
// 'MailingDate' must not be empty ValidationColumn = ARow.Table.Columns[PMailingTable.ColumnMailingDateId]; if (AValidationControlsDict.TryGetValue(ValidationColumn, out ValidationControlsData)) { // actual Data Validation code goes here // Handle addition to/removal from TVerificationResultCollection AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn); }
Data Validation Code for a single Data Validation Code Block
The data validation code uses a pattern that is presented here. It is easy enough and can be followed in most data validation scenarios. For scenarios that require a different methodology, code that has the same net effect needs to be written.
Net effect of the data validation code:
- If the data validation fails, a
TVerificationResult
instance is to be created and it is to be assigned to the localVerificationResult
Variable; - If the data validation succeeds, the local
VerificationResult
Variable must not be assigned any value. Alternatively,null
can be assigned to the localVerificationResult
Variable (it isnull
by default).
Sample code: (found in file \csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs, Method 'ValidateMailingSetup')
VerificationResult = TDateChecks.IsNotUndefinedDateTime(ARow.MailingDate, ValidationControlsData.ValidationControlLabel, true, AContext, ValidationColumn, ValidationControlsData.ValidationControl);
This sample code uses one of the many predefined Data Validation Routines (TDateChecks.IsNotUndefinedDateTime
). Most of these routines are found in the Ict.Common.Verification
Namespace. Available Classes in this Namespace: TDateChecks
, TNumericalChecks
, TStringChecks
. The Methods in these Classes are all static and all behave the same, therefore they are easy to use once one has used them a bit. Another Class is available as well, TGuiChecks
, which also contains static Methods, but they work differently and have different purposes. Simply have a look around these Classes to familiarise yourself with what they provide.
Sample code where no predefined Data Validation Routine is used: (found in \csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs, Method 'ValidateSubscriptionManual')
if (((!ARow.IsSubscriptionStatusNull()) && (ARow.SubscriptionStatus == String.Empty)) || (ARow.IsSubscriptionStatusNull())) { VerificationResult = new TScreenVerificationResult(new TVerificationResult(AContext, ErrorCodes.GetErrorInfo(PetraErrorCodes.ERR_SUBSCRIPTION_STATUSMANDATORY)), ValidationColumn, ValidationControlsData.ValidationControl); } else { VerificationResult = null; }
Utilising Special Functionality
Accessing a Cacheable DataTable
By using static Methods of the Ict.Petra.Shared.TSharedDataCache
Classes' Subclasses (MPartner
, MFinance
, etc.) one can retrieve the content of a Cacheable DataTable and perform Data Validation checks based on that. The calls to these static Methods will succeed no matter if the Data Validation code is run client-side or server-side - the respective Cache Manager (client-side or server-side) is used automatically for the retrieval of the Cacheable DataTable!
Sample code: (found in file '\csharp\ICT\Petra\Shared\lib\MPersonnel\validation\Personnel.Validation.cs', Method 'ValidateJobAssignmentManual')
TypeTable = (PtAssignmentTypeTable)TSharedDataCache.TMPersonnel.GetCacheableUnitsTable (TCacheableUnitTablesEnum.JobAssignmentTypeList); TypeRow = (PtAssignmentTypeRow)TypeTable.Rows.Find(ARow.AssignmentTypeCode); // 'Assignment Type' must not be unassignable if ((TypeRow != null) && TypeRow.UnassignableFlag && (TypeRow.IsUnassignableDateNull() || (TypeRow.UnassignableDate <= DateTime.Today))) { VerificationResult = new TScreenVerificationResult(new TVerificationResult(AContext, ErrorCodes.GetErrorInfo(PetraErrorCodes.ERR_VALUEUNASSIGNABLE_WARNING, new string[] { ARow.AssignmentTypeCode })), ValidationColumn, ValidationControlsData.ValidationControl); }
Accessing Server-side Functionality that is exposed to the Client
Some Data Validations need to access server-side functionality (e.g. to load data from the database that is not contained in a Cacheable DataTable).
Since all Data Validations need to be working in both the client- and server side Data Validation scenarios, a 'Helper' Method needs to be created that is called by the Data Validation code, and that Helper Method in turn needs to utilise a Delegate to perform the actual Method call:
- If the Data Validation code is run in the server-side context, the Delegate will need to be set up to simply call the server-side Method.
- If the Data Validation code is run in the client-side context, the Delegate will need to be set up to call a client-side Method that in turn will need to call the server-side Method.
Example: Calling server-side Method 'TServerLookup.TMPartner.VerifyPartner'
- Helper Class:
\csharp\ICT\Petra\Shared\lib\MPartner\validation\Helper.cs
, Class 'TSharedPartnerValidationHelper
'public static TVerifyPartner VerifyPartnerDelegate
: The Delegate that needs to be set up at Client startup time (client side)/at Client AppDomain creation time (server side)\csharp\ICT\Petra\Client\app\MainWindow\PetraClientMain.cs, InitialiseClasses
Method:TSharedPartnerValidationHelper.VerifyPartnerDelegate = @TServerLookup.TMPartner.VerifyPartner;
\csharp\ICT\Petra\Server\lib\CallForwarding\CallForwarding.cs, TCallForwarding Class
, static Constructor:TSharedPartnerValidationHelper.VerifyPartnerDelegate = @TPartnerServerLookups.VerifyPartner;
public static bool VerifyPartner
: Method that can be called by Data Validation Methods. It transparently executes the Delegate on behalf of the calling Method.
- Data Validation:
\csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs, Method 'IsValidPartner'
- Calls Method '
VerifyPartner
'. This works no matter if the Data Validation is run in the client or server context!
- Calls Method '
Note: Although setting up a Helper Class and initialising the Delegate means some work, we foresee that many Helper Classes should be able to be re-used. Therefore we should aim to make the Helper classes as universal as possible.
Data Validation Methods need to be placed in shared DLL's according to the following rules:
- All Data Validation Methods need to be placed in OpenPetra-Module-specific DLL's:
- Ict.Petra.Shared.lib.MCommon.validation.DLL, Ict.Petra.Shared.lib.MFinance.validation.DLL, Ict.Petra.Shared.lib.MPartner.validation.DLL, Ict.Petra.Shared.lib.MPersonnel.validation.DLL, Ict.Petra.Shared.lib.MSysMan.validation.DLL
- The filename and the Namespace of the Class that contains the Data Validation Methods needs to correspond to the Namespace that the Data Validation pertains to.
- The Classes need to be Partial Classes. This is because automatically generated code augments the manually written code in the same Class.
- Filenames: There is a differentiation between the files that are manually created and the files that are automatically generated.
- Examples:
- Validation of MCommon DataTables takes place in the (partial) Class '
TSharedCommonValidation
' in file\csharp\ICT\Petra\Shared\lib\MCommon\validation\Common.Validation.cs
(manually created file); 'Common.Validation-generated.cs' (automatically generated file). - Validation of MFinance Gift DataTables takes place in the (partial) Class '
TSharedFinanceValidation_Gift
' in file\csharp\ICT\Petra\Shared\lib\MFinance\validation\Gift.Validation.cs
(manually created file); 'Gift.Validation-generated.cs' (automatically generated file). - Validation of MPartner Partner DataTables takes place in the (partial) Class '
TSharedPartnerValidation_Partner
' in file\csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs
(manually created file); 'Partner.Validation-generated.cs' (automatically generated file).
- Validation of MCommon DataTables takes place in the (partial) Class '
- Examples:
- Special case: Data Validation Methods that validate data that is contained in Cacheable DataTables are all to be placed in Ict.Petra.Shared.lib.MCommon.validation.DLL, (partial) Class '
TSharedValidation_CacheableDataTables
' in File '\csharp\ICT\Petra\Shared\lib\MCommon\validation\Cacheable.Validation.cs
' - regardless of the OpenPetra-Module they pertain to. Automatically generated code is all placed in the file 'Cacheable.Validation-generated.cs' in the same Namespace and DLL.
- The filename and the Namespace of the Class that contains the Data Validation Methods needs to correspond to the Namespace that the Data Validation pertains to.
- Ict.Petra.Shared.lib.MCommon.validation.DLL, Ict.Petra.Shared.lib.MFinance.validation.DLL, Ict.Petra.Shared.lib.MPartner.validation.DLL, Ict.Petra.Shared.lib.MPersonnel.validation.DLL, Ict.Petra.Shared.lib.MSysMan.validation.DLL
Server Side
Because all Data Validation code that performs the actual validation of data is to be put into Shared DLL's that code can be called from the server side of OpenPetra as well as from the client side of OpenPetra.
Data Validation in OpenPetra needs to be done on both client and server side. While this might look like a duplication of effort, this allows...
- ... server-side code to run different (or more checks) than the client side;
- Client-side validation needs to validate things quickly and must not transfer much data to the client for this as the data validation is quite interactive.
- Server-side validation can do more thorough checks which may well take longer and/or involve the loading of more data as no data needs to be sent to the client for this.
- ... server-side code to be called from something else than the OpenPetra Client (e.g. Unit Tests, Web Application) while still allowing the data validation to run.
Advantages
- Data validation cannot be forgotten about on the client side.
- Should it be forgotten about in on the client side, the server-side validation will still run and thus the saving of data will not be allowed to go ahead from the Client side; all data validation errors will be presented in the user interface in a way that is similar to what it would look like if they would have been done on the client side.
- Data validation will run as if it was called from the client side.
- Data that is submitted directly to the server side (e.g. from a Unit Test) is subject to the same validation checks than data that comes from the client side.
- Data validation does not need to be written again specifically for various calling scenarios.
- That would be error-prone and would lead to maintenance issues (separate data validation code sections [e.g. Client side, Unit Test, Web Form] would need to be kept in sync when changes are made to the data validation of a single Form/UserControl).
Achieving 'Optional-ness' of Data Validation
Data Validation is something that is optional for a screen or server-side code.
- On the client side, this fact is dealt with the presence or absence of a data validation Method that needs to have a certain name and which needs to be present in the *.ManualCode.cs file of a Form/UserControl. The WinForms Generator looks for a Method of that name in that file and if it is present it inserts calls to that Method in all the appropriate places in the auto-generated Form/UserControl file.
- On the server side, this fact is dealt with by using 'Partial Methods'. This is the more logical (and up-to-date) way of how to deal with the fact that some Methods are optional. However, on the client side we decided to not go this way with the data validation framework as the rest of the WinForms Generator behaviour regarding optional manually written Methods is working in the way that is described in the above bullet point. (Because we use Partial Methods on the server side, you need to understand how Partial Methods work in order to understand how the server-side data validation works.)
Declaration of Partial Data Validation Methods
The .cs file/Class in which a particular data validation should be called needs to have a section that contains the declaration of all (partial) data validation Methods that should be able to be called in that C# file/Class.
Example (found in file \csharp\ICT\Petra\Server\lib\MFinance\setup\GL.Setup.cs):
#region Data Validation static partial void ValidateAAnalysisType(TValidationControlsDict ValidationControlsDict, ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable); static partial void ValidateAAnalysisTypeManual(TValidationControlsDict ValidationControlsDict, ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable); #endregion Data Validation
Notes:
- The Method whose name does not end in 'Manual' is the Method that will be implemented by the Code Generators if automatic data validation is created for the scenario.
- The Method whose name does end in 'Manual' is a Method that the programmer can write, should he choose to implement manual data validation for the scenario.
- The fact that a Partial Method is declared does not mean that there needs to be an implementation (that is the point in having Partial Methods). That comes in handy in scenarios where server-side code is auto-generated, e.g. for the Cacheable DataTables and where the data validation code might be written by hand - or not.
- A declared but not implemented Method may still be called in C# program code. In that case the C# compiler will simply omit the call in the emitted IL code (and consequently nothing will happen at run time)!
- A Class that declares Partial Methods needs to be a Partial Class. If the Class to which you are adding Partial Method declarations isn't a partial Class yet, simply add the keyword
partial
after thepublic
keyword.
Calling Partial Data Validation Methods
A call to a Partial Method needs to be inserted at the point in server-side code where a particular data validation Method needs to be called. Such Method calls are no different than non-partial Method calls.
Typically, data validation Methods will be called in a server-side method either early in the processing (similar to argument checks), or before calling a Method that will submit data to the DB.
Example incl. Context (found in file \csharp\ICT\Petra\Server\lib\MFinance\setup\GL.Setup.cs, Method 'SaveGLSetupTDS'):
// In the Variable Declaration Section: TValidationControlsDict ValidationControlsDict = new TValidationControlsDict(); . . . if (AInspectDS.AAnalysisType != null) { if (AInspectDS.AAnalysisType.Rows.Count > 0) { ValidateAAnalysisType(ValidationControlsDict, ref AVerificationResult, AInspectDS.AAnalysisType); ValidateAAnalysisTypeManual(ValidationControlsDict, ref AVerificationResult, AInspectDS.AAnalysisType); if (AVerificationResult.Count != 0) { ReturnValue = TSubmitChangesResult.scrError; } } } if (ReturnValue != TSubmitChangesResult.scrError) { ReturnValue = GLSetupTDSAccess.SubmitChanges(AInspectDS, out AVerificationResult); }
Regarding the placement of such calls a few recommendations are given (they are observed in the code example above):
- Implement checks for 'null' on the DataTable and DataRow objects before calling data validation Methods;
- Change the flow of the Method in which context you are calling the data validation in case at least one data validation fails.
Necessary Extra Code When Calling Data Validation Methods
Any server-side Method that calls a data validation method in a shared DLL needs to have the following code section at the very end of the Method:
if (AVerificationResult.Count > 0) { // Downgrade TScreenVerificationResults to TVerificationResults in order to allow // Serialisation (needed for .NET Remoting). TVerificationResultCollection.DowngradeScreenVerificationResults(AVerificationResult); }
This code section is needed for .NET Remoting, which would not be able to 'Serialize' TScreenVerificationResults.
If this code section is omitted or forgotten, a .NET Serialization Exception is thrown in case data validation results are to be sent back to the Client!
Partial Data Validation Methods
On the client side only a single record needs to be validated in each data validation scope as the user is either only editing a single record, or if he/she is editing records in a Grid, the data validation code is always called only with the currently edited record. However, on the server-side data validation always needs to be able to deal with multiple submitted records in a single DataTable - even if the User Interface that would normally submit the data would not permit that. The reason for that is that Unit Tests, Web Forms, scripted calls (e.g. data import), etc. may well submit multiple records per DataTable in a single call to the server-side data validation!
For the data validation of multiple submitted records,
- each record needs to be validated separately;
- data validation needs to be run on every record in the inspected DataTable, even if data validation Errors/Warnings were created for a previously inspected record;
- all Data Validation Errors/Warnings need to be appended to a single TVerificationResults Collection.
In that way multiple Data Validation Errors/Warnings may be recorded per DataRow for 1..n DataRows contained in the DataTable that is to be inspected.
The looping over all records of the submitted DataTable is shown in the following sample code. Note that the appending of the data validation Errors/Warnings of individual records to AVerificationResult
is taken care of in the Shared DLL code!
Sample code (found in '\csharp\ICT\Petra\Server\lib\MFinance\setup\GL.Setup.Validation.cs'):
static partial void ValidateAAnalysisTypeManual(TValidationControlsDict AValidationControlsDict, ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable) { TValidationControlsDict ValidationControlsDict = new TValidationControlsDict(); ValidationControlsDict.Add(ASubmitTable.Columns[AAnalysisTypeTable.ColumnAnalysisTypeDescriptionId], new TValidationControlsData(null, AAnalysisTypeTable.GetAnalysisTypeDescriptionDBName())); for (int Counter = 0; Counter < ASubmitTable.Rows.Count; Counter++) { TSharedFinanceValidation_GLSetup.ValidateAnalysisTypesSetupManual("TGLSetupWebConnector" + " (Error in Row #" + Counter.ToString() + ")", // No translation of message text since the server's messages should be all in English (AAnalysisTypeRow)ASubmitTable.Rows[Counter], ref AVerificationResult, ValidationControlsDict); } }
For each DataColumn that needs data validation a TValidationControlsDict.Add
call needs to be included. The format for these calls is always the same and is very similar to the client side. The differences to the client side are:
FMainDS.SomeDT
needs to be replaced withASubmitTable
;- '
null
' needs to be substituted for the Control reference (=first Argument of the -TValidationControlsData
Constructor) as the server side of OpenPetra has no references to Controls on the client GUI (and indeed there may not be a client GUI [e.g. in case of Unit Tests]).
Note: In case you are adding server-side data validation after data validation has been added to the corresponding Form/UserControl you can copy those calls from the 'BuildValidationControlsDict()
' Method found in the generated Form/UserControl file. In that case you only need to remove 'FPetraUtilsObject.
' from the beginning of each call, replace FMainDS.SomeDT
with ASubmitTable
and replace each Control reference with 'null
' in the server-side copy of the code.