Introduction
FluentValidation seems to be a very convenient way to handle ValidationRules in Wpf.
I really recommend to read the Original-Article by tetsushmz, which gives good introduction.
Application-Summary
There is a Viewmodel, named "Problem5" (a Math-Puzzle, see Problem5 on Project Euler..net), in a separate Project, where the user can input two texts, and then raise a command to calculate the result.
Validating theese Texts is not that trivial, and FluentValidation does a good job on it.
And the View demonstrates, how invalid textboxes automatically get marked, and the button automatically gets disabled in case of ocurance of errors.
Moreover the MainViewmodel provides a List of error-messages of all invalid properties of the Problem5
-Instance.
What I did different
In general I left the original Solution as it was, but I simplified the Usage, and I could remove an Interface and I reduced the number of Members which are needed to make FluentValidations work.
Here the usage-obligatorals, as shown in tetsushmz' approach
public class Problem5 : ValidatableBindableBase, INotifyDataErrorInfo {
private readonly IValidator<Problem5> validator;
public IEnumerable GetErrors(string propertyName) {
return this.validator.GetErrors(propertyName);
}
public IList<string> GetAllErrors() {
return this.validator.GetAllErrors();
}
public override void ValidateAllProperties() {
this.validator.Validate(this);
}
public Problem5(IValidator<Problem5> validator) {
this.validator = validator;
this.validator.ErrorsChanged += (s, e) => this.OnErrorsChanged(e);
- Inherit from
ValidatableBindableBase
, implement INotifyDataErrorInfo
- Declare a
validator
-Object - Implement
INotifyDataErrorInfo.GetErrors()
, and redirect it to the validator
- Implement
INotifyDataErrorInfo.GetAllErrors()
, and redirect it to the validator
- Override
ValidatableBindableBase.ValidateAllProperties()
, and redirect it to the validator
- Inject the
validator
-Object via Constructor (and store it) - Subscribe the
IValidator.ErrorsChanged
-Event, to call the ValidatableBindableBase.OnErrorsChanged()
-Method
Now my simplification:
public class Problem5 : FluentNotifyDataErrorInfo {
private readonly AbstractValidator<Problem5> validator;
protected override ValidationResult Validate() { return this.validator.Validate(this); }
public Problem5(AbstractValidator<Problem5> validator) {
this.validator = validator;
- Inherit from
FluentNotifyDataErrorInfo
- Declare a
validator
-Object - Override
FluentNotifyDataErrorInfo.Validate()
, and redirect it to the validator
- Inject the
validator
-Object via Constructor (and store it)
Memory-Footprint
Usually Validation concerns many Objects, or even large amounts of Data. So it's a good habit, to keep the validations memory-footprint small.
In the original-concrete Implementation of IValidator<Problem5>
tetsushmz allocates for each Data-Item its own Dictionary
, and it's own Problem5Validator
, an object, which holds all the Validation-Rules.
This is not necessarily necessary ;-) .
My Implementation broadcasts the same Problem5Validator
-Instance to all Data-Items, and my FluentNotifyDataErrorInfo
(Data-Items Base-Class) only contains an Error-Dictionary, if Errors are present - otherwise it is set to a static empty Dictionary
.
Drawbacks of my Alternative
- My Data-Item-Assembly needs to reference the FluentValidation-Library. I decided to dispense the discoupling
IValidator<T>
-Interface of the original, to get things simpler.
Since that Library is needed anyway in a fluent-validated Application, no matter, whether the Data-Item-Assembly refers it or not. - Some scenarious may require, that each Data-Item holds its own Set of Validation-Rules. I cannot imagine such szenariouses, but I must accept the possibility of that.
Sidenote: Difference-Analyse-Algorithm
One (small) Challenge to master is, to compare the previous error-list with a newer version, to detect, which errors are vanished, which remained the same, which were changed and which are new.
Difference-Analyse is a general pattern, and here is a reciepe, to solve such performantly:
- convert list1 to a dictionary or a hashset - name it
dic1
. - then loop list2, and try remove each item2 from the
dic1
. Hashset/Dictionaries Remove()
-Function returns False
, if the item isn't present - If
True
: both Items are same - means: That Item is unchanged - If
False
: That item2 is new - After having looped all item2s, the remaining items (if present) in
dic1
are the deleted ones - since in list2 they were not present.
This reciepe is meant to be adapted to the current scenario - if you like, look at my FluentNotifyDataErrorInfo.ProcessChangedErrors()
- Method:
1 private void ProcessChangedErrors(Dictionary<string, string> updatedErrors) {
2 var oldErrs = this._Errors;
3 _Errors = updatedErrors;
4 foreach (var kvp in updatedErrors) {
5 string oldErr;
6 if (oldErrs.TryGetValue(kvp.Key, out oldErr)) {
7 oldErrs.Remove(kvp.Key);
8 if (oldErr == kvp.Value) continue;
9 }
10
11 ErrorsChanged.Raise(this, new DataErrorsChangedEventArgs(kvp.Key));
12 }
13
14 foreach (var oldErrKey in oldErrs.Keys)
15 ErrorsChanged.Raise(this, new DataErrorsChangedEventArgs(oldErrKey));
16 }
Its Job is to Raise the INotifyDataErrorInfo.ErrorsChanged
-Event for each Property, on which the presence (or only the error-Message) of an error did change.
I had to alter the reciepe above in the detail, that I used Dictionary.TryGetValue()
instead of Dictionary.Remove()
, because an additional Check is required to detect, whethr it is changed or not (line #8).
Conclusion
Some people may see me as an unpleasant smart-ass, and I'm usure, whether I could reject that convincingly ;-) . But does that really matter? If my unpleasant property helps you to work better with validations, then it's not completely unworthy, is it?
And I really think, examining different approaches, which achieve the same result, is very helpful, to figure out useful patterns, capabilities, flexibilites, pros and cons and stuff.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.