Passing additional data to Custom Validators

Topics: Validation Application Block
Mar 13, 2009 at 4:21 PM
Edited Mar 13, 2009 at 4:24 PM
Hi All,

I am using Validation Application Block for Windows Forms integrated validation.
I have created custom validators and used Attributes to associate them to respective Properties of the Business Object. Now while overriding the DoValidate method and writing custom code for validation that method, the value that is to be validated is passed smoothly in ObjectToValidate parameter. The sample looks like below -

public class UserNameUnique : Validator<string>

 

{
    ...
    ...
    protected
override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults)
    {
    }
    ...
    ...
}

I have also created a UserNameUniqueAttribute class to associate with the UserName property in the Business Class (to create validators through property Attributes) as below.

public
class UserNameUniqueAttribute : ValidatorAttribute
{
    protected override Validator DoCreateValidator(Type targetType)
    {
        
return new UserNameUnique();
    }
}

Now everything is running fine and windows forms integrated validation is also performing as desired. My problem is to validate the UserName, I need the UserId also along with the UserName for validation pruposes. Consider an SQL like this "SELECT COUNT(Id) FROM  APPUSERS WHERE username = <usernametovalidate> AND Id != <currentuserid>". The currentuserid prevents validating against the same user name in Modify modes. Anyway, in this case and may be in many other cases as well, I would need some more Business Object data to be passed to the DoValidate method. How can I do that? The currentTarget is set to a ValidatedItem object when validation is fired from username textbox control in the form. Is there any way, I can pass some more Business Object data to the Validator. My Form controls are all databound to the Business object properties. For example txtUsername is bound to objUser.Name.

I loved the Validation Application Block but I am stuck at this critical point. I think this should be a pretty common requirement. Can anybody help?

 

 

 

Mar 14, 2009 at 4:15 AM
Edited Mar 14, 2009 at 10:04 AM
You could wrap the business object you want to validate in another class, making it a property of that class. 

public BusinessObjectWrapper
{
    public BusinessObject ObjectToValidate
    {
        get;
        set;
    }
}

You can then associate your custom validator to the ObjectToValidate property.


Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@avanade.com
Mar 16, 2009 at 5:25 PM
Hi,

Thanks for your suggestion but I could not get it fully.

Could you please explain it a little more, perhaps with an example where I have a business class called 'User' with say two properties 'Id' and 'Name' and I want to attach some default validators (e.g. StringLengthValidator) and one custom validator (UserNameUniqueValidator). In the DoValidate implementation of my custom validator I need to use the 'Id' property value as well apart from the 'Name' property value that is supplied through the ObjectToValidate parameter. The 'currentTarget' parameter of DoValidate method is suplying a 'ValidatedControlItem' type of object.

Is there any way I can achieve this? I do not think I am demanding too much. I just need to access more than one property of an object while validating a boud control. I can very well check all the property values if I apply a Class level Custom validator. But I need to fire the User Name Unique check when Name is entered in the UserName text box also. However, a form level validation is also there (uses a Validation.Validate method).

My controls are bound to my custom business object using SetDataBinding method called from Form_Load. So I was just thinking whether I can get any refernce to my business object, to which the form controls are bound, from the 'currentTarget' parameter and thereby obtaining other property values from that reference?
Mar 16, 2009 at 5:37 PM
Edited Mar 16, 2009 at 5:39 PM

Hi Sarah,

Just wanted to draw your attention towards a Visual Studio magazine code which takes care of validation through attributes as well as validation through custom handlers implemented as delegates in the Class itself so that it can use all other Class properties (check the [CustomValidator("Credit card is invalid.", "CheckValidCreditCard")] part. Since I am relatively new to this .NET world I am not able to clearly figure out what exactly is needed to implement the same (if at all possible) in Enterprise Library. For obvious reasons, I want to stick to Enterprise Library 4.1 released in 2008 rather than using a somewhat similar framework released in June 2006 as I am not well equipped to judge the differences and their effects.

http://visualstudiomagazine.com/features/article.aspx?editorialsid=1317

Mar 18, 2009 at 4:03 AM
Sorry for not making it clear.  In my suggestion, I forgot to mention that you should create a custom validator.  Example, in the case of a User with two properties "Id" and "Name", you could create a class that wraps the User object.

public class UserWrapper
{
    public UserWrapper(User user)
    {
        Value = user;
    }

    public User Value
    {
        get;
        set;
    }
}

You can now create a custom validator of type User.(you can check out the quickstart for validationi for a sample custom validator)  Associate that validator to the Value property of the UserWrapper object. 

I have also another suggestion if you don't want to create your own validator.  You can use SelfValidation by creating a method within the User class marked with the [SelfValidation] attribute
accepting a single parameter of type ValidationResults.  SelfValidation methods will be triggered everytime you perform validation on its class.  You also need to mark the class with the [HasSelfValidation] attribute.  This is also documented in entlib.


Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@avanade.com
Mar 18, 2009 at 7:56 AM
Hi Sarah,

Thanks for your reply.

Unfortunately, I am still unable to get the thing right. First let me clarify a couple of points to note below - 

My Validation Strategy Objectives
----------------------------------------------
1. Validate at field level if user visits fields, enters data and tries to leave the field. Show error icons (ErrorProvider) if field is invalid.
2. Not to restrict user from leaving an invalid field
3. Validate everything (all properties, dependent fields) from Save button click so that User cannot persist invalid data anyway even if some entry fields are not visited.

What have I done
---------------------------------------------
1. I have read and implemented custom validators that target properties (e.g. Name) and are used as attributes for Property declaration (e.g. [MyCustomPropertyValidator])
2. I have read and implemeted custom validators that target classes and are used as attributes for the Class declaration (e.g. [MyClassValidator])
3. I have read about Self Validation.

What I am trying to do
---------------------------------------------
1. Implement a property level custom validator that will validate the Uniqueness of the Name property
2. The DoValidate implementation of the above validator should have to check something like "SELECT COUNT(*) FROM AppUser WHERE ID != <currentIdProperty> AND NAME = <currentNameProperty>". The Id comes from a different property, assuming that the validator is attached to the Name property.
3. Attach this custom validator to the Name property as an attribute (e.g. [UniqueValidator]).
4. No code to be written in the Form by the UI developer

Effect
------------------------------------------------------
1. The unique validator is automatically fired as soon as user enters any name and tries to leave the field. Shows an error icon if not unique.
2. Even if the user does not visit the Name field, it is automatically trapped in the Save button click where a call is implemented like Validation.Validate(objUser) which will internally fire all property validations, whether of not the user has visited those fields.

My Problem Area
---------------------------------------------
1. Class level custom validator - All properties available (as ObjectToValidate is the business object itself). But fires only on Validation.Validate(obj) call. Not called by ValidationProvider automatically.
2. Self validation - All properties available (as validation code is inside the business class itself). But fires only on Validation.Validate(obj) call. Not called by ValidationProvider automatically.
3. Property level custom validator - Only current property available (passed as ObjectToValidate).

I am not sure whether you suggestion of wrapping my business object can ensure an automatic call to my custom validator in WinForm integrated manner, i.e. as soon as user enters any text in the name field as the Name field is bound to objUser.Name and not objUserWrapper.

One Line Requirement (if this helps)
--------------------------------------------
Use multiple properties from a business object in a property level custom validator that can be attached to the business class property as an attribute and allows automatic validation by ValidationProvider control without any code for calling Validator being written in the Form (except from Save button where Validation.Validate(objUser) will be called once).

Hope I have made myself clear.

Thanks for taking the time for reading this long post.

Rajarshi


Mar 18, 2009 at 8:26 AM
Hi Sarah,

Just an update. I have managed a workaround like this in the DoValidate implementation for the Property level validator for Name.

using

 

Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms;
using System.Windows.Forms;
...
...
protected override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults)
{

    UserData u;
    // Get a reference to my business object from both Property and Class level validators
    // ------------------------------------------------------------------------------------------------------
    if (currentTarget.GetType().ToString().Contains("ValidatedControlItem"))
    {
        // This block is fired when validating from data entry in text box
        // To get a reference to the data bound business object we need to extract the business object
        // from the currentTarget control reference
        ValidatedControlItem c = (ValidatedControlItem)currentTarget;
        Control ctl = (Control)c.Control;
        u = (
UserData)ctl.DataBindings["Text"].DataSource;
    }
    else
    {
        // This block is fired when validating from Save button as 'Validation.Validate(objUser)'
        u = (UserData)currentTarget;
    }
    // ------------------------------------------------------------------------------------------------------
    // At this point I have my business object in 'u'
    if (!usersBLL.CheckUnique(objectToValidate, u.Id))
    {
        LogValidationResult(validationResults, GetMessage(objectToValidate, key), currentTarget, key);
    }
}

The above code is working. Just wanted to know about any possible demerits you can see. If theoritical/conceptual demerits as "Strong coupling with Windows Form Controls (as I am using 'Control ctl')" are spared and evaluated from a practical business application usage where a team of 10-15 closely knit developers are working, how does it fare?

 

Mar 18, 2009 at 8:54 AM
It is tightly coupled with winforms, but as you said, we'll just accept it as it is.  It would probably better if you could make the usersBll an interface, and make both parameters of CheckUnique of type object (probably change also the name of CheckUnique to a more generic name).  I'm just suggesting this for the purpose of reusability.  Making it an interface would allow other types to reuse that validator and implement their own method for cross property validation.  The objectToValidate/UserData would also have to be an interface.  The whole logic would have to be clearly communicated to all other developers.


Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@avanade.com
Mar 18, 2009 at 1:51 PM
I believe your best and simplest solution would be to use SelfValidation (that suggestion was thrown out there by Sarah on Tuesday in the 11:03 post).  A complete solution (minus the actual implement of the uniqueness check) would look like this:

[HasSelfValidation]
class User
{
    public int UserId { get; set; }

    [StringLengthValidator(20, MessageTemplate="UserName cannot exceed 20 characters.")]
    public string UserName { get; set; }

    [SelfValidation]
    private void CheckUserNameUnique(ValidationResults results)
    {
        // Put your logic here to check for uniqueness.
        // If you discover it's not unique then log to validation like this:
        results.AddResult(new ValidationResult("UserName is not unique.", this, "UniqueUserNameCheck", null, null));
    } 
}

Mar 18, 2009 at 3:04 PM

Hi Steve,

Thanks for taking time to read the thread.

I can definitely implement the class the way you are suggesting, but I have two questions -

1. How can I make the Self Validation fire while user enters data (for Username) in a WinForm text box (bound to the object property being validated) without writing any code in the UI events (e.g. Control_OnValidating)?

2. How is Self Validation different from a custom validator that targets the Class other than the actual location of validation code and does it provide any other benefits?
(By class level validator I mean a custom validator that is used as an attribute for the Class declaration rather than a property delcaration.)

Thanks,

Rajarshi

Mar 18, 2009 at 3:43 PM
Ohhh, *now* I see what you're trying to do.  Yeah, since you're tying it to windows forms, the SelfValidation will not be an ideal solution because it won't fire when the user changes the value in the textbox.  You can implement a custom validator (similar to my article you referenced in your Monday post at 12:39) like this:

public class User
{
    public int UserId { get; set; }

    [StringLengthValidator(20, MessageTemplate="UserName cannot exceed 20 characters.")]
    [CustomValidator("CheckUserNameUnique", MessageTemplate = "UserName Unique check failed.")]
    public string UserName { get; set; }

    private void CheckUserNameUnique(ValidationResults results)
    {
        // Put your logic here to check for uniqueness.
        // If you discover it's not unique then log to validation like this:
        results.AddResult(new ValidationResult("UserName is not unique.", this, "UniqueUserNameCheck", null, null));
    } 
}

public class CustomValidator : Validator
{
    private delegate void CustomValidatorDelegate(ValidationResults results);
    private string methodToInvoke;

    public CustomValidator(string methodToInvoke, string messageTempalte, bool negated) : base(messageTempalte, null)
    {
        this.methodToInvoke = methodToInvoke;
    }

    protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
    {
        CustomValidatorDelegate customMethod = Delegate.CreateDelegate(typeof(CustomValidatorDelegate), currentTarget, this.methodToInvoke) as CustomValidatorDelegate;
        customMethod(validationResults);
    }

    protected override string DefaultMessageTemplate
    {
        get
        {
            return "Custom validation failed.";
        }
    }
}

public class CustomValidatorAttribute : ValidatorAttribute
{
    private string methodToInvoke;

    public CustomValidatorAttribute(string methodToInvoke)
    {
        this.methodToInvoke = methodToInvoke;
    }

    protected override Validator DoCreateValidator(Type targetType)
    {
        return new CustomValidator(this.methodToInvoke, this.MessageTemplate, false);
    }
}

The key here is that the DoValidate method of the CustomValidator will invoke the CustomValidatorDelegate delegate.  You simply specify the method name (as a string) in the CustomValidator attribute that you apply to your username property.  Notice the code in the DoValidate method acts upon the currentTarget parameter which is the object itself (not the  objectToValidate property which is the specific property).

I hope this will do it for you.

Steve
Mar 19, 2009 at 12:19 AM

Hi Steve,

Thank you very much for showing me the way. I guess we are almost there but just a little doubt.

In a WinForm integrated validation scenario, while DoValidate is automatically called on entering data in the Username text box, the 'currentTarget' parameter that is passed will contain a reference to a 'Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms.ValidatedControlItem' and not the business object.

I guess for the above reason the Delegate.CreateDelegate Method (Type, Object, String) call would not be able to associate the instance method 'void CheckUserNameUnique(ValidationResults results)' by using currentTarget as a parameter. However, when validating from any call like 'Validation.Validate(objItem)' (that I intend to use on Save button click for double check), the business object instance will be passed and your method should work.

I haven't got a chance to test your solution, just replied by looking at the code, so I may be wrong.

While looking at the docs, I found an overload of CreateDelegate as Delegate.CreateDelegate Method (Type, Type, String) that creates a delegate of the specified type that represents the specified static method of the specified class. Using this overload, if we change the instance method CheckUserNameUnique to a static method, shall the solution work as -

Delegate.CreateDelegate(typeof(CustomValidatorDelegate), typeOf(User), this.methodToInvoke) as CustomValidatorDelegate

If the alternate approach, using a static method works, then I have the following queries -

1. Though this will not be used in a multi-threaded scenario, still, just for enquiry, will it be thread safe to implement unique check through a static method. I guess two threads can enter the same block at the same time and since both will be checking with the database, both will return the names as 'valid' while on saving they will generate duplicate names.

2. If the above point can be ignored (since multithreaded environment is out of question in my case), do you feel that the Delegate-Static method approach has any other potential concern areas ? Since you are much more experienced than me, you can visualize better.

Thanks,

Rajarshi

Mar 19, 2009 at 5:54 AM

Hi Steve,

In my last post I mentioned about your Delegate based solution that currentTarget is a ValidatedControlItem reference and hence possibly cannot be used while validating entries being made in a text box by the user (auto firing of validation from Validation Provider).

I had suggested to create a Static method in the business class for validation and use that as a delegate target guessing that would eliminate the need of a business object instance reference, but I completely forgot that my "Id" value was an instance member which I would not be able to access from a static method hence my suggestion is trash.

But then the problem of validating (using VAB and ValidationProvider for WinForm) while entry being made in the text box remains, in case of validators that need to access multiple values from the business object.

Though I have posted a crude solution of extracting the business object from the currentTarget reference and walking up the databindings collection for the bound control, I am not quite sure if that has any potential performance or any other demerits.

Thanks,

Rajarshi