Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Using Attributes for Validations

delphi attribute language feature custom attribute validation object validation

  • Please log in to reply
No replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 28 September 2013 - 05:33 AM

Previously I have introduced a Delphi language feature called Attribute. This tutorial shows you one sample of real use of attributes, i.e. to easily store and retrieve object information. Now let me show you another use of attributes, validations. Actually I have shown example of this in my introduction to attributes. However this time we will use attributes library that I actually used in several actual projects.
 
 
ValidationAttribute Class
 
The validation library itself contained in a unit named DAttributes.Validations, and the most important attribute class here is ValidationAttribute. It is the base class of all our validation attribute classes.
 
ValidationAttribute declared like this.

  ValidationAttribute=class(TCustomAttribute)
  private
    FErrMsg: string;
  protected
    function IsVarTypeSupported(const AVar: Variant): Boolean; virtual;
    function VarToStr(const AValue: Variant): string; virtual;
    function GetIsValid(const AValue: Variant): Boolean; virtual;

    property ErrMsg: string read FErrMsg;
  public
    procedure Validate(AObj: TObject; AProp: TRttiProperty); virtual;

    function IsValid(AObj: TObject; AProp: TRttiProperty): Boolean; overload;
    function IsValid(AValue: Rtti.TValue): Boolean; overload;
    function IsValid(const AValue: Variant): Boolean; overload;

    constructor Create(AErrMsg: string=''); virtual;
  end;

The main method of this class would be the Validate. It's implemented like this.

procedure ValidationAttribute.Validate(AObj: TObject; AProp: TRttiProperty);
var
  vValue: Variant;
  vPropValue: Rtti.TValue;
begin
  vPropValue := AProp.GetValue(AObj);
  if not (vPropValue.Kind in SUPPORTED_PROPERTY_VALUE_TYPES) then
    raise EValidationNotSupported.Create('This property kind is not supported', AObj, AProp.Name);

  vValue := vPropValue.AsVariant;
  if not IsVarTypeSupported(vValue) then
    raise EValidationNotSupported.Create('This property value type is not supported', AObj, AProp.Name);

  if not GetIsValid(vValue) then
  begin
    if FErrMsg='' then
      FErrMsg := 'Validation failed';

    raise EValidationError.CreateFmt(FErrMsg, [VarToStr(vValue)], AObj, AProp.Name);
  end;
end;

From that implementation, we can see that in its original form (remember that this method was declared virtual, so we can override it in any descendant of ValidationAttribute class), this method depends on IsVarTypeSupported and GetIsValid methods. Both method must be overriden by ValidationAttribute descendant to make sure the validation process is done as designed.
 
In current form, both methods simply return false, making all validations will be failed in the original form.

function ValidationAttribute.IsVarTypeSupported(const AVar: Variant): Boolean;
begin
  Result := False;
end;
function ValidationAttribute.GetIsValid(const AValue: Variant): Boolean;
begin
  Result := False;
end;

EValidationError Class
 
Another important class in the DAttributes.Validations unit is EValidationError. This exception class carries information on which object that caused the validation error, property name that violated the validation rule, along with the error message. These information will make it easier to decide what to do when handling this exception. Sample of handle this kind of exception is included in the demo project.
 
It's declared like this.


  EValidationError=class(Exception)
  private
    FObj: TObject;
    FPropName: string;
  public
    constructor Create(const AMsg: string; AObj: TObject; const APropName: string);
    constructor CreateFmt(const AMsg: string; const Args: array of const; AObj: TObject; const APropName: string);

    property Obj: TObject read FObj;
    property PropName: string read FPropName;
  end;

Some Actual Validation Classes
 
Here are some actual validation classes in DAttributes.Validations unit. Some will be used in the following demo project.

  • MinValue
  • MaxValue
  • MustNotEmptyString
  • FolderMustExists
  • FolderMustNotExists
  • FileMustExists
  • FileMustNotExists
  • NumbersOnly
  • LimitToTheseChars

And the following routines were there in the DAttributes.Validations interface unit for easy checking if an object is valid or not.

  • procedure Validate(AObj: TObject)
  • function IsValid(AObj: TObject): Boolean

Google Code SVN Repository
 
I decided to share the attribute classes and supporting routines as open source project DAttributes. This project is hosted in Google Code. Here is SVN repository for newest version of DAttribute: <a data-ipb="nomediaparse" data-cke-saved-href="http://dattributes.g...t's home page: https://code.google.com/p/dattributes/ .
 
Feel free to download or contribute.
 
 
Demo Project
 
For the demo project, I am using the same demo project from the Easily Store and Retrieve Object from Ini Files Using Attributes. We are just going to add a little update.
 
And here is the TMyConfig class (in the SimpleConfig.pas unit) in this demo project. Irrelevant parts were omitted, to save space.


type
  [IniSection('Gen001')]
  TMyConfig=class
  public
    procedure Validate;
  published
    [IniMember('WorkingDir', 'please specify working dir')]
    [MustNotEmptyString]
    [FolderMustExists]
    property WorkingDir: string read GetWorkingDir write SetWorkingDir;

    [IniMemberAttribute('Section02', 'DefDay', '100')]
    [MaxValue(7)]
    [MinValue(1)]
    property DefaultDay: Integer read GetDefaultDay write SetDefaultDay;

    [IniMember('Section01', 'DefCity', 'Jakarta')]
    [MustNotEmptyString]
    [LimitToTheseChars('xyz')]
    property DefaultCity: string read GetDefaultCity write SetDefaultCity;
  end;

Just to clarify, the validation attributes that we implemented there mean:

  • Property WorkingDir should not contain empty string.
  • Property WorkingDir must be path to an existing folder.
  • Property DefaultDay's value must not exceed 7.
  • Property DefaultDay's value must not less than 1.
  • Property DefaultCity's value must not contain any character other than xy, and z.

The method Validate was only a wrapper to a call to Validate in DAttributes.Validations unit.

procedure TMyConfig.Validate;
begin
  DAttributes.Validations.Validate(Self);
end;

Let's See How The Demo Project Runs

 

Upon running, you will find a dull display like this.

 

DemoValidation_Run000.png

 

Now click either Save Config or Validate! button. Both of them will do the validations. Since there is no way you have a "please specify working dir" folder in your computer, the corresponding FolderMustExists attribute will protest and generate the following exception.

 

DemoValidation_Run010_FolderNotExistError.png

 

Now let's select an existing folder for the "Working directory" text box. Click again either Save Config or Validate! button. And you will get...

 

DemoValidation_Run020_MaxValueError.png

 

I leave the explanation of the last exception as an exercise.

 

And here is complete source code of DAttributes along with the demo project: Attached File  DAttributes.zip   998.19KB   365 downloads. Have fun with it, you can do anything with it, but I will appreciate if you could share what you have been doing with it.


  • 0





Also tagged with one or more of these keywords: delphi, attribute, language feature, custom attribute, validation, object validation

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download