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 ErrorsChanged.Raise(this, new DataErrorsChangedEventArgs(kvp.Key));
12 }
13 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.