views:

173

answers:

2

I have a custom validation in the enterprise validation block. The DoValidate method is as shown below.

protected override void DoValidate(Double objectToValidate, object currentTarget, string key, ValidationResults validationResults) { if (!IsSalMoreThanMinWage(objectToValidate)) { //Here I need to mark this message as a "Warning" LogValidationResult(validationResults, "Salary is too low for this state", currentTarget, key); } }

I'd need to mark this validation failure as a "warning" message. In the front end, when I iterate through the ValidationResults collection and grab a ValidationResult object, I would need to identify and group different types of messages and render them differently.

My question is - how do I mark a failure as a warning?

A: 

Short answer:

Create an non-default ruleset and name it 'Warning'.

Long answer:

Validation Application block supports the notion of 'rulesets'. You can make a ruleset named 'Warning'. How to use this depends on your architecture and interface, but here is an idea.

It a bit depends whether you always want to show both errors and warnings, or only show errors when there are errors, and otherwise show the warnings or save. When there are warnings the user need to be able to say "hell yes I'm sure, save it!" and then you need to save the changes by suppressing the errors.

When you follow this approach you can throw an exception (say ValidationException) that holds the ValidationResult objects. Add a property to the exception that says whether the results are warnings or errors (you can do this when you decide to don't show any warnings when there are errors).

In your presentation layer you do the following:

Button_Save(object sender, EventArgs e)
{
    this.Save(ValidationMode.Default);
}

Button_HellYesSuppresWarningsAndSave(object sender, EventArgs e)
{
    this.Save(ValidationMode.SuppressWarnings);
}


private Save(ValidationMode mode)
{
    try
    {
        ServiceLayer.Save(mode);
    }
    catch (ValidationException ex)
    {
        if (ex.ResultType == ValidationResultType.Warnings)
        {
            ShowWarningsAndAskIfSure(ex.Errors);
        }
        else
        {
            ShowErrorsAndTellUserNeedsToFix(ex.Errors);
        }
    }
}

In the business layer you need to to something like this:

public void Save(ValidationMode mode)
{
    Validate(ValidationResultType.Errors);

    if (!mode == ValidationMode.SuppressWarnings)
    {
        Validate(ValidationResultType.Warnings);
    }
}

private void Validate(ValidationResultType type)
{
    var objectToValidate;
    var ruleset = type == ValidationResultType.Errors ? "default" : "warnings";
    var validator = ValidationFactory
       .CreateValidator(objectToValidate.GetType(), ruleset);

    var results = validator.Validate();
    if (!results.IsValid)
    {
        throw new ValidationException(results, type);
    }
}

I hope this makes sense.

Steven
@Steven - This is similar to what I was trying to achieve. However, I am already using rulesets to define other groupings based on business rules. Other than rulesets, Is there any other way to say - hey, the user has suppressed the warnings, so don't run any validations marked as warnings? The other issue I run into is that the code snippet I added is a subset of a custom validator I am working on. The one I am working on has 2 validations - 1 is a warning and 1 is an error. Both are contained within DoValidate method.It seems to me that this validator needs to be split into error and warning
@Steven - An unrelated question. Why do you throw an exception if the results are invalid? Although this works, I've always thrown exceptions in extreme situations from which the code cannot recover. One of the side effect is that if you are using any logging block in your app, all these exceptions would be logged cluttering your log.
@user102533: "Is there any other way to [...] don't run any validations marked as warnings?". In my knowledge there is no other way to mark validations as warning. I think rulesets are the only feasible approach.
Steven
@user102533: "Why do you throw an ex if the results are invalid?". The reason is simple, because an invalid result is something that must get noticed. The last thing you would want is that a developer calls Save and forgets to check whether your data is actually saved. There is no rule that says that exception can only be used in unrecoverable situations. Besides, the exception would normally only get logged when they are unhandled. In the `Save` method example in my answer, you see the exception gets handled. Your log will only get cluttered if you use profiling to log every thrown exception.
Steven
@user102533: As Tuzo pointed out, you can use Tag and filter on `Tag` after validating. However the tag can't be used to prevent running validations. So this can be a problem when you have performance intensive validations (i.e. go to database) and don't want to run when not needed. For that I think rulesets are your only hope.
Steven
+1  A: 

You can use the Tag property of the ValidationResult. "The meaning for a tag is determined by the client code consuming the ValidationResults."

If you are using configuration, then you can specify the tag in your configuration file:

<validator lowerBound="0" lowerBoundType="Inclusive" 
upperBound="255" upperBoundType="Inclusive" negated="false" messageTemplateResourceName="" messageTemplateResourceType="" 
messageTemplate="Oops a warning occurred" 
tag="Warning" type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
name="My Validator" />


Or set the Tag with a property:

[StringLengthValidator(5, 50, Ruleset = "RuleSetA", Tag="Warning")]


If you want to do it programmatically, then you will have to create a new validation result since the Tag property is readonly:

ValidationResults newResults = new ValidationResults();

foreach (ValidationResult vr in validationResults)
{
    newResults.AddResult( new ValidationResult( 
        vr.Message, vr.Target, vr.Key, "Warning", vr.Validator, vr.NestedValidationResults ) );
}


Then in the front end you can check the Tag property of the ValidationResult to see if it's a warning:

foreach (ValidationResult vr in validationResults)
{
    if (string.Compare(vr.Tag, "Warning") == 0)
    {
        DisplayWarning(vr.Message);
    }
    else
    {
        DisplayError(vr.Message);
    }
}


Obviously, you can abstract this much better, aggregate the errors and warnings etc.

UPDATE

We don't have identical requirements to yours but we do something similar. Unfortunately, the only way I know to execute the type of conditional validation you are talking about is to use RuleSets.

What we do is use a naming convention for the RuleSets and construct the RuleSet Names at runtime. If the RuleSet exists then we run the validator. You could do something similar for your warnings. So you could have two RuleSets:

  • RuleSet_Salary_Update
  • RuleSet_Salary_Update_Warning

And then retrieve a List of Validators based on whether you want to run the warning validation:

    public static List<Validator<T>> CreateValidators<T>(bool shoulIncludeWarning, RuleSetType rulesetType)
    {
        if (shouldIncludeWarning)
        {
            // Get warning validator if any
        }

        // Get Default validator (if any)
    }

RuleSetType is an enum with different types of rules (e.g. Select, Insert, Update, Delete, PrimaryKey, etc.).

Tuzo
@Tuzo - Yes, this is what I ended up doing. I would have hoped that the VAB makers used a different data type for tag. I wanted to use an enum but ended up having to do some dirty work of converting a string tag to an enum. Like Steven pointed out, once a user suppresses the warnings, I need a way to run the validation again but now ignore the warnings. I would like to do this without using rulesets (unless there's a way of specifying multiple rulesets for a single validator)
@Tuzo - Regarding your update - you say " use a naming convention for the RuleSets and construct them at runtime"How do you name rulesets at runtime?
Sorry, to be clear I was talking about constructing the RuleSet **names** at runtime using a naming convention. Not dynamically creating RuleSets themselves.
Tuzo
Using `Tag` is a nice one. I missed that.
Steven