Instructions for the Implementation of Data Validation: Difference between revisions
(Created page with '==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 S…') |
(ERR_VALUEUNASSIGNABLE_WARNING requires 2 arguments) |
||
(48 intermediate revisions by 2 users not shown) | |||
Line 2: | Line 2: | ||
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. | 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 Data Validation and should help the whole development team to code Data Validation in a common and standardised way. | 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 '[[Instructions for the Implementation of Data Validation#Server_Side|Server Side]]'. | |||
''Automatic'' Data Validation is also in place in OpenPetra. Read [[Automatic Data Validation|this]] wiki page to learn about it. | |||
===Client Side=== | ===Client Side=== | ||
====YAML File==== | ====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''' | |||
* <code>dtpDetailMailingDate: {Label=Mailing Date, ''Validation=True''}</code> (found in \csharp\Petra\Client\MPartner\Gui\Setup\MailingSetup.yaml) | |||
Once the YAML file gets (re-)generated with the WinForms Generator (using the <code>nant generateWinforms</code> 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''' | '''TODO''' | ||
====*.ManualCode.cs File==== | ====*.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 '<code>ValidateDataManual</code>' 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 '<code>ValidateDataDetailsManual</code>' 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 '<code>ValidateDataManual</code>' needs to be present ''in addition'' to the '<code>ValidateDataDetailsManual</code>' Method in order to validate the data outside of the 'Details Section'. An example of this can be found in <code>\csharp\ICT\Petra\Client\MFinance\Gui\AP\APEditDocument.ManualCode.cs</code> | |||
After adding this Method/these Methods, <code>nant generateWinForms</code> 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 <code>TVerificationResultCollection VerificationResultCollection = FPetraUtilsObject.VerificationResultCollection;</code> | |||
# 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 [[Instructions for the Implementation of Data Validation#Shared_Libraries|Shared Libraries]]. | |||
===Shared Libraries=== | ===Shared Libraries=== | ||
''' | ====The Data Validation Method==== | ||
The <code>ValidateDataManual</code> / <code>ValidateDataDetailsManual</code> 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 Error or Warning occurs, a TScreenVerificationResult object is created | |||
** The ''highly recommended'' way of creating a TScreenVerificationResult is by using the Constructor of TScreenVerificationResult that accepts a TVerificationResult as its first Argument. For the creation of that TVerificationResult the ''highly recommended'' way is to use the Constructor that contains an Argument of Type ErrCodeInfo. That Argument in turn should be created by calling the static 'ErrorCodes.GetErrorInfo' Method, supplying an ErrorCode. | |||
** The Data Validation Framework is built around the concept that a Data Validation Error or Warning pertains to a DataColumn. Therefore ''always'' specify the DataColum that the Data Validation Error or Warning pertains to - failing to do that may result in the Data Validation Error or Warning to be 'swallowed' or not be properly processed! | |||
** If ''multiple Data Validations in one run'' are to be performed on a ''single DataColumn'' then the Argument 'ADataValidationRunID' in the Constructor of a TScreenVerificationResult needs to be set to match the 'CurrentDataValidationRunID' of the screen that the Data Validation Error or Warning is pertaining to! | |||
*** To do this, pass the static 'FPetraUtilsObject.VerificationResultCollection.CurrentDataValidationRunID' for that Argument. | |||
* The TScreenVerificationResult instance then gets appended to a Collection that is held in the Form where the Data Validation is run. | |||
** The recommended way of doing this is using code like the following: <code>AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn);</code>. | |||
*** By doing this, once a Data Validation Error/Warning got rectified by the user, the corresponding TScreenVerificationResult object is removed from the Collection through that call! | |||
''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! | |||
'''Standardised Method signature:''' | |||
public static void ValidateMailingSetup(object AContext, PMailingRow ARow, | |||
''ref TVerificationResultCollection AVerificationResultCollection'', TValidationControlsDict AValidationControlsDict) | |||
The Method needs to have a <code>ref VerificationResultCollection</code> Argument, among other Arguments. By modifying that Collection, this Method can add or remove specific TScreenVerificationResult objects to/from the Form's Collection of TVerificationResult/TScreenVerificationResult objects (which is always held in the FPetraUtilsObject.VerificationResultCollection object if the Data Validation Method gets called from the Client side). | |||
(The Method can optionally inspect that Collection for other TVerificationResult/TScreenVerificationResult objects and evaluate those [that is needed only for complex data validation scenarios, though]. As the Collection will hold all TVerificationResult/TScreenVerificationResult objects of the Form, TVerificationResult/TScreenVerificationResult 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!) <br /> | |||
The caller of the Shared Data Validation Method (a Form, a UserControl or some server-side code) is responsible for the evaluation of the TVerificationResultCollection that is returned by that Method. Usually the caller would inspect that Collection and stop the processing it was doing in case there was at least one 'critical error' found in the collection. The easiest way of doing this is by inspecting the 'HasCriticalErrors' Property of the TVerificationResultCollection. | |||
The Data Validation Method can contain 0..n Data Validations for a certain Typed DataRow. Each validation is based on a certain DataColumn, and each of the validations is run only if the DataColumn in question is passed in through the instance of the TValidationControlsDict Class. | |||
The Argument '<code>AValidationControlsDict</code>' therefore needs to hold information about which DataColumns should be validated - one Control+DataColumn instance needs to be added to it per DataColumn that should be validated. (The Control is only necessary if the Data Validation Method is called from the Client - it is set to <code>null</code> if the Data Validation Method gets called from the Server side.)<br /> | |||
The reason why this done like this is this: 1..n screens on Client side could contain 1..n Controls that would need Data Validation from the ''same'' Shared Data Validation Method. ''However'', 'Screen 1' might contain only one Control (that is bound to one DataColumn) that needs Data Validation - for example, the Shared Data Validation Method could validate up to four Controls/DataColumns, e.g. performing a NOT NULL check on each of the DataColumns. 'Screen 1' would fail the NOT NULL Data Validation for the three Controls/Columns combinations that it doesn't hold, whereas 'Screen 2' that might hold the other three Control/DataColumn combinations would fail the Data Validation of the Control that is only placed on 'Screen 1'. To get around that problem each screen passes the Control/Column combinations it ''needs'' to be validated into the Shared Data Validation Method, and that method will only run the e.g. NOT NULL Data Validation that is coded up against ''those'' Controls/DataColumn combination(s), but not the other ones. (The same applies if the Shared Data Validation Method is called from the Server side, except that all Control references can be left NULL.) That flexible concept allows us to share Data Validation code across many screens and Server side methods without re-coding data validation each and every time. | |||
The content of the Method follows a standard pattern: | |||
# There is a Variable declaration section. This is 'boiler plate' code. | |||
# This is followed by an if-block that starts with the comment '<code>// Don't validate deleted DataRows</code>'. Again, 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 TScreenVerificationResult 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 <code>TScreenVerificationResult</code> instance is to be created and it is to be assigned to the local <code>VerificationResult</code> Variable; | |||
* If the data validation succeeds, the local <code>VerificationResult</code> Variable must not be assigned any value. Alternatively, <code>null</code> can be assigned to the local <code>VerificationResult</code> Variable (it is <code>null</code> 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 (<code>TDateChecks.IsNotUndefinedDateTime</code>). Most of these routines are found in the <code>Ict.Common.Verification</code> Namespace. Available Classes in this Namespace: <code>TDateChecks</code>, <code>TNumericalChecks</code>, <code>TStringChecks</code>. 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, <code>TGuiChecks</code>, 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; | |||
} | |||
=====Special Functionality===== | =====Utilising Special Functionality===== | ||
======Accessing a Cacheable DataTable====== | ======Accessing a Cacheable DataTable====== | ||
By using static Methods of the <code>Ict.Petra.Shared.TSharedDataCache</code> Classes' Subclasses (<code>MPartner</code>, <code>MFinance</code>, 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[] { ValidationControlsData.ValidationControlLabel, ARow.AssignmentTypeCode })), | |||
ValidationColumn, ValidationControlsData.ValidationControl); | |||
} | |||
======Accessing Server-side Functionality that is exposed to the Client====== | ======Accessing Server-side Functionality that is exposed to the Client====== | ||
Example: Calling | 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). | ||
* PetraClientMain.cs, InitialiseClasses: TSharedPartnerValidationHelper.VerifyPartnerDelegate = @TServerLookup.TMPartner.VerifyPartner; | |||
* | 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: | ||
** public static | * 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:'' <code>\csharp\ICT\Petra\Shared\lib\MPartner\validation\Helper.cs</code>, Class '<code>TSharedPartnerValidationHelper</code>' | |||
** <code>public static TVerifyPartner VerifyPartnerDelegate</code>: The Delegate that needs to be set up at Client startup time (client side)/at Client AppDomain creation time (server side) | |||
*** <code>\csharp\ICT\Petra\Client\app\MainWindow\PetraClientMain.cs, InitialiseClasses</code> Method: <code>TSharedPartnerValidationHelper.VerifyPartnerDelegate = @TServerLookup.TMPartner.VerifyPartner;</code> | |||
*** <code>\csharp\ICT\Petra\Server\lib\CallForwarding\CallForwarding.cs, TCallForwarding Class</code>, static Constructor: <code>TSharedPartnerValidationHelper.VerifyPartnerDelegate = @TPartnerServerLookups.VerifyPartner;</code> | |||
** <code>public static bool VerifyPartner</code>: Method that can be called by Data Validation Methods. It transparently executes the Delegate on behalf of the calling Method. | |||
* ''Data Validation:'' <code>\csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs, Method 'IsValidPartner'</code> | |||
** Calls Method '<code>VerifyPartner</code>'. This works no matter if the Data Validation is run in the client or server context! | |||
''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.'' | |||
''' | ====Choosing the right Shared DLL for the Data Validation Methods==== | ||
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 '<code>TSharedCommonValidation</code>' in file <code>\csharp\ICT\Petra\Shared\lib\MCommon\validation\Common.Validation.cs</code> (manually created file); 'Common.Validation-generated.cs' (automatically generated file). | |||
****** Validation of MFinance Gift DataTables takes place in the (partial) Class '<code>TSharedFinanceValidation_Gift</code>' in file <code>\csharp\ICT\Petra\Shared\lib\MFinance\validation\Gift.Validation.cs</code> (manually created file); 'Gift.Validation-generated.cs' (automatically generated file). | |||
****** Validation of MPartner Partner DataTables takes place in the (partial) Class '<code>TSharedPartnerValidation_Partner</code>' in file <code>\csharp\ICT\Petra\Shared\lib\MPartner\validation\Partner.Validation.cs</code> (manually created file); 'Partner.Validation-generated.cs' (automatically generated file). | |||
*** 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 '<code>TSharedValidation_CacheableDataTables</code>' in File '<code>\csharp\ICT\Petra\Shared\lib\MCommon\validation\Cacheable.Validation.cs</code>' - ''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. | |||
===Server Side=== | ===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 if data validation errors occur (if there are only data validation ''warnings'', saving of data can go ahead); 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 run 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 '[[New CSharp Language features 3.0 and 4.0#Partial_Methods|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(ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable); | |||
static partial void ValidateAAnalysisTypeManual(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 <code>partial</code> after the <code>public</code> 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'):''' | |||
if (AInspectDS.AAnalysisType != null) | |||
{ | |||
if (AInspectDS.AAnalysisType.Rows.Count > 0) | |||
{ | |||
'''ValidateAAnalysisType(ref AVerificationResult, AInspectDS.AAnalysisType);''' | |||
'''ValidateAAnalysisTypeManual(ref AVerificationResult, AInspectDS.AAnalysisType);''' | |||
if (!TVerificationHelper.IsNullOrOnlyNonCritical(AVerificationResult)) | |||
{ | |||
ReturnValue = TSubmitChangesResult.scrError; | |||
} | |||
} | |||
} | |||
. | |||
. | |||
. | |||
if (ReturnValue != TSubmitChangesResult.scrError) | |||
{ | |||
GLSetupTDSAccess.SubmitChanges(AInspectDS); | |||
ReturnValue = TSubmitChangesResult.scrOK; | |||
} | |||
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 results in a Data Validation Error (if there are only Data Validation Warnings the flow of the program will usually continue unchanged). | |||
** Inspect the Property <code>HasCriticalErrors</code> of a TVerificationResultCollection for that (as in the code example above). | |||
=====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 <code>AVerificationResult</code> is taken care of in the Shared DLL code! | |||
'''Sample code (found in '\csharp\ICT\Petra\Server\lib\MPartner\Cacheable.Validation.cs'):''' | |||
partial void ValidateMaritalStatusListManual(ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable) | |||
{ | |||
TValidationControlsDict ValidationControlsDict = new TValidationControlsDict(); | |||
ValidationControlsDict.Add(ASubmitTable.Columns[PtMaritalStatusTable.ColumnAssignableDateId], | |||
new TValidationControlsData(null, PtMaritalStatusTable.GetAssignableDateDBName())); | |||
for (int Counter = 0; Counter < ASubmitTable.Rows.Count; Counter++) | |||
{ | |||
if (ASubmitTable.Rows[Counter].RowState != DataRowState.Deleted) | |||
{ | |||
TSharedValidation_CacheableDataTables.ValidateMaritalStatus(this.GetType().Name + | |||
" (Error in Row #" + Counter.ToString() + ")", // No translation of message text since the server's messages should be all in English | |||
(PtMaritalStatusRow)ASubmitTable.Rows[Counter], ref AVerificationResult, | |||
ValidationControlsDict); | |||
} | |||
} | |||
} | |||
For each DataColumn that needs data validation a <code>ValidationControlsDict.Add</code> 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: | |||
* <code>FMainDS.''SomeDT''</code> needs to be replaced with <code>ASubmitTable</code>; | |||
* '<code>null</code>' needs to be substituted for the Control reference (=first Argument of the -<code>TValidationControlsData</code> 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 that calls the server-side Method [e.g. in case of Unit Tests]); | |||
* for the second Argument of the <code>TValidationControlsData</code> Constructor the call to <code>Catalog.GetString("blahblah")</code> needs to be replaced with a call that retrieves the DB Columns' DB Name (<code>PtMaritalStatusTable.GetAssignableDateDBName()</code> in the example above). | |||
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 '<code>BuildValidationControlsDict()</code>' Method found in the generated Form/UserControl file (only calls that don't have a comment starting with '// Automatic Data Validation ...'). In that case you only need to remove '<code>FPetraUtilsObject.</code>' from the beginning of each call, replace <code>FMainDS.SomeDT</code> with <code>ASubmitTable</code>, replace each Control reference with '<code>null</code>' and the call to <code>Catalog.GetString("blahblah")</code> in the server-side copy of the code with a call to the Typed DataTables' Type.GetXXXDBName() Method (e.g. <code>PtMaritalStatusTable.GetAssignableDateDBName()</code>)... | |||
====Consideration of Special Cases==== | |||
Many forms consist of a list and a detail part but are often defined in the same class, as can be seen on the Personnel Data Tab (e.g. Passport Details or Personal Documents). The dictionary of controls to be validated (normally called FValidationControlsDict) is automatically built for such an object. There are special cases though as can be seen on the Subscription tab where List and Detail exist in two different classes (UC_Subscriptions and UC_Subscription). In this case the dictionary is built in the detail class but the actual validation is triggered in the list class. Therefore it is necessary to refer to the dictionary of the detail class when validation is run in the list class. This is achieved by assigning the correct dictionary of the detail class to the variable in the list class. | |||
'''Sample code (found in '\csharp\ICT\Petra\Client\MPartner\Gui\UC_Subscriptions.ManualCode.cs'):''' | |||
public void SpecialInitUserControl() | |||
{ | |||
... | |||
// use dictionary of details control as validation is called for dictionary of this class | |||
FValidationControlsDict = ucoDetails.ValidationControlsDict; | |||
... | |||
} |
Latest revision as of 15:06, 27 September 2018
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 Error or Warning occurs, a TScreenVerificationResult object is created
- The highly recommended way of creating a TScreenVerificationResult is by using the Constructor of TScreenVerificationResult that accepts a TVerificationResult as its first Argument. For the creation of that TVerificationResult the highly recommended way is to use the Constructor that contains an Argument of Type ErrCodeInfo. That Argument in turn should be created by calling the static 'ErrorCodes.GetErrorInfo' Method, supplying an ErrorCode.
- The Data Validation Framework is built around the concept that a Data Validation Error or Warning pertains to a DataColumn. Therefore always specify the DataColum that the Data Validation Error or Warning pertains to - failing to do that may result in the Data Validation Error or Warning to be 'swallowed' or not be properly processed!
- If multiple Data Validations in one run are to be performed on a single DataColumn then the Argument 'ADataValidationRunID' in the Constructor of a TScreenVerificationResult needs to be set to match the 'CurrentDataValidationRunID' of the screen that the Data Validation Error or Warning is pertaining to!
- To do this, pass the static 'FPetraUtilsObject.VerificationResultCollection.CurrentDataValidationRunID' for that Argument.
- The TScreenVerificationResult instance then gets appended to a Collection that is held in the Form where the Data Validation is run.
- The recommended way of doing this is using code like the following:
AVerificationResultCollection.Auto_Add_Or_AddOrRemove(AContext, VerificationResult, ValidationColumn);
.- By doing this, once a Data Validation Error/Warning got rectified by the user, the corresponding TScreenVerificationResult object is removed from the Collection through that call!
- The recommended way of doing this is using code like the following:
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!
Standardised 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 TScreenVerificationResult objects to/from the Form's Collection of TVerificationResult/TScreenVerificationResult objects (which is always held in the FPetraUtilsObject.VerificationResultCollection object if the Data Validation Method gets called from the Client side).
(The Method can optionally inspect that Collection for other TVerificationResult/TScreenVerificationResult objects and evaluate those [that is needed only for complex data validation scenarios, though]. As the Collection will hold all TVerificationResult/TScreenVerificationResult objects of the Form, TVerificationResult/TScreenVerificationResult 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 caller of the Shared Data Validation Method (a Form, a UserControl or some server-side code) is responsible for the evaluation of the TVerificationResultCollection that is returned by that Method. Usually the caller would inspect that Collection and stop the processing it was doing in case there was at least one 'critical error' found in the collection. The easiest way of doing this is by inspecting the 'HasCriticalErrors' Property of the TVerificationResultCollection.
The Data Validation Method can contain 0..n Data Validations for a certain Typed DataRow. Each validation is based on a certain DataColumn, and each of the validations is run only if the DataColumn in question is passed in through the instance of the TValidationControlsDict Class.
The Argument 'AValidationControlsDict
' therefore needs to hold information about which DataColumns should be validated - one Control+DataColumn instance needs to be added to it per DataColumn that should be validated. (The Control is only necessary if the Data Validation Method is called from the Client - it is set to null
if the Data Validation Method gets called from the Server side.)
The reason why this done like this is this: 1..n screens on Client side could contain 1..n Controls that would need Data Validation from the same Shared Data Validation Method. However, 'Screen 1' might contain only one Control (that is bound to one DataColumn) that needs Data Validation - for example, the Shared Data Validation Method could validate up to four Controls/DataColumns, e.g. performing a NOT NULL check on each of the DataColumns. 'Screen 1' would fail the NOT NULL Data Validation for the three Controls/Columns combinations that it doesn't hold, whereas 'Screen 2' that might hold the other three Control/DataColumn combinations would fail the Data Validation of the Control that is only placed on 'Screen 1'. To get around that problem each screen passes the Control/Column combinations it needs to be validated into the Shared Data Validation Method, and that method will only run the e.g. NOT NULL Data Validation that is coded up against those Controls/DataColumn combination(s), but not the other ones. (The same applies if the Shared Data Validation Method is called from the Server side, except that all Control references can be left NULL.) That flexible concept allows us to share Data Validation code across many screens and Server side methods without re-coding data validation each and every time.
The content of the Method follows a standard pattern:
- There is a Variable declaration section. This is 'boiler plate' code.
- This is followed by an if-block that starts with the comment '
// Don't validate deleted DataRows
'. Again, 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 TScreenVerificationResult 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
TScreenVerificationResult
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[] { ValidationControlsData.ValidationControlLabel, 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 if data validation errors occur (if there are only data validation warnings, saving of data can go ahead); 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 run 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(ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable); static partial void ValidateAAnalysisTypeManual(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'):
if (AInspectDS.AAnalysisType != null) { if (AInspectDS.AAnalysisType.Rows.Count > 0) { ValidateAAnalysisType(ref AVerificationResult, AInspectDS.AAnalysisType); ValidateAAnalysisTypeManual(ref AVerificationResult, AInspectDS.AAnalysisType); if (!TVerificationHelper.IsNullOrOnlyNonCritical(AVerificationResult)) { ReturnValue = TSubmitChangesResult.scrError; } } } . . . if (ReturnValue != TSubmitChangesResult.scrError) { GLSetupTDSAccess.SubmitChanges(AInspectDS); ReturnValue = TSubmitChangesResult.scrOK; }
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 results in a Data Validation Error (if there are only Data Validation Warnings the flow of the program will usually continue unchanged).
- Inspect the Property
HasCriticalErrors
of a TVerificationResultCollection for that (as in the code example above).
- Inspect the Property
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\MPartner\Cacheable.Validation.cs'):
partial void ValidateMaritalStatusListManual(ref TVerificationResultCollection AVerificationResult, TTypedDataTable ASubmitTable) { TValidationControlsDict ValidationControlsDict = new TValidationControlsDict(); ValidationControlsDict.Add(ASubmitTable.Columns[PtMaritalStatusTable.ColumnAssignableDateId], new TValidationControlsData(null, PtMaritalStatusTable.GetAssignableDateDBName())); for (int Counter = 0; Counter < ASubmitTable.Rows.Count; Counter++) { if (ASubmitTable.Rows[Counter].RowState != DataRowState.Deleted) { TSharedValidation_CacheableDataTables.ValidateMaritalStatus(this.GetType().Name + " (Error in Row #" + Counter.ToString() + ")", // No translation of message text since the server's messages should be all in English (PtMaritalStatusRow)ASubmitTable.Rows[Counter], ref AVerificationResult, ValidationControlsDict); } } }
For each DataColumn that needs data validation a ValidationControlsDict.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 that calls the server-side Method [e.g. in case of Unit Tests]); - for the second Argument of the
TValidationControlsData
Constructor the call toCatalog.GetString("blahblah")
needs to be replaced with a call that retrieves the DB Columns' DB Name (PtMaritalStatusTable.GetAssignableDateDBName()
in the example above).
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 (only calls that don't have a comment starting with '// Automatic Data Validation ...'). In that case you only need to remove 'FPetraUtilsObject.
' from the beginning of each call, replace FMainDS.SomeDT
with ASubmitTable
, replace each Control reference with 'null
' and the call to Catalog.GetString("blahblah")
in the server-side copy of the code with a call to the Typed DataTables' Type.GetXXXDBName() Method (e.g. PtMaritalStatusTable.GetAssignableDateDBName()
)...
Consideration of Special Cases
Many forms consist of a list and a detail part but are often defined in the same class, as can be seen on the Personnel Data Tab (e.g. Passport Details or Personal Documents). The dictionary of controls to be validated (normally called FValidationControlsDict) is automatically built for such an object. There are special cases though as can be seen on the Subscription tab where List and Detail exist in two different classes (UC_Subscriptions and UC_Subscription). In this case the dictionary is built in the detail class but the actual validation is triggered in the list class. Therefore it is necessary to refer to the dictionary of the detail class when validation is run in the list class. This is achieved by assigning the correct dictionary of the detail class to the variable in the list class.
Sample code (found in '\csharp\ICT\Petra\Client\MPartner\Gui\UC_Subscriptions.ManualCode.cs'):
public void SpecialInitUserControl() { ... // use dictionary of details control as validation is called for dictionary of this class FValidationControlsDict = ucoDetails.ValidationControlsDict; ... }