Instructions for the Implementation of Data Validation

From OpenPetra Wiki
Jump to navigation Jump to search

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.

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

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:

  1. The content of the first line is always TVerificationResultCollection VerificationResultCollection = FPetraUtilsObject.VerificationResultCollection;
  2. A single empty line follows.
  3. 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.


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:

  1. There is a Variable declaration section. This is 'boiler plate' code.
  2. 1..n 'Data Validation Code Blocks' follow.

Each 'Data Validation Code Block' follows a standard pattern:

  1. The DataColumn that is to be validated is stored in the ValidationColumn Variable.
  2. 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.
  3. If so,
    1. the actual data validation code is run,
    2. 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 local VerificationResult Variable;
  • If the data validation succeeds, the local VerificationResult Variable must not be assigned any value. Alternatively, null can be assigned to the local VerificationResult Variable (it is null 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!

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 '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).
      • 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.

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 can 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.
    • Advantage 1: Data validation cannot be forgotten about in such scenarios.
    • Advantage 2: Data validation will run as if it was called from the OpenPetra client.
    • Advantage 3: Data validation does not need to be written again specifically for such scenarios. That would be error-prone and would lead to maintenance issues (separate data validation code sections would need to be kept in sync when changes are made to the data validation of a Form/UserControl).

Achieving 'Optional-ness' of Data Validation

Data Validation is something that 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:

#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 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)!