Page 22 - MSDN Magazine, March 2019
P. 22
Figure 3 The CheckRules Function
Figure 4 The MaxLengthRule Class
public void CheckRules(String fieldName) {
var propertyInfo = this.GetType().GetProperty(fieldName); var attrInfos = propertyInfo.GetCustomAttributes(true); foreach (var attrInfo in attrInfos)
{
if (attrInfo is IModelRule modelrule) {
var value = propertyInfo.GetValue(this);
var result = modelrule.Validate(fieldName, value); if (result.IsValid)
{
RemoveError(fieldName, attrInfo.GetType().Name); }
else {
AddError(fieldName, attrInfo.GetType().Name, result.Message); }
} }
}
public bool CheckRules() {
foreach (var propInfo in this.GetType().GetProperties( System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance)) CheckRules(propInfo.Name);
return HasErrors(); }
public class MaxLengthRule : Attribute, IModelRule {
private int _maxLength = 0;
public MaxLengthRule(int maxLength) { _maxLength = maxLength; }
public ValidationResult Validate(string fieldName, object fieldValue) {
var message = $"Cannot be longer than {_maxLength} characters";
if (fieldValue == null) { return new ValidationResult() { IsValid = true }; }
var stringvalue = fieldValue.ToString(); if (stringvalue.Length > _maxLength )
{
return new ValidationResult() { IsValid = false, Message = message }; }
else {
return new ValidationResult() { IsValid = true }; }
} }
for that field. It uses the internal _errors dictionary to determine if there are any errors for that field, as shown here:
public String Errors(String fieldName) {
if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName, new Dictionary<string, string>()); }
System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach (var value in _errors[fieldName].Values)
sb.AppendLine(value);
return sb.ToString(); }
Now, I need to add the HasErrors function, which returns true if there are any errors on any field of the model. This method is used by the client to determine if the Register button should be enabled. It’s also used by the WebAPI server to determine if the incoming model data has errors. Here’s the function code:
public bool HasErrors() {
foreach (var key in _errors.Keys)
if (_errors[key].Keys.Count > 0) { return true; }
return false; }
Values and Events
It’s time to add the GetValue method, which takes a fieldname parameter and uses reflection to find the field in the model and return its value. This is used by the Blazor client to retrieve the current value and display it in the input box, as shown right here:
public String GetValue(String fieldName) {
var propertyInfo = this.GetType().GetProperty(fieldName); var value = propertyInfo.GetValue(this);
if (value != null) { return value.ToString(); } return String.Empty;
Now add the SetValue method. It uses reflection to find the field in the model and update its value. It then fires off the CheckRules
method that validates all the rules on the field. It’s used in the Blazor client to update the value as the user types in the input textbox. Here’s the code:
public void SetValue(String fieldName, object value) {
var propertyInfo = this.GetType().GetProperty(fieldName); propertyInfo.SetValue(this, value); CheckRules(fieldName);
}
Finally, I add the event for ModelChanged, which is raised when a value on the model has been changed or a validation rule has been added or removed from the internal dictionary of errors. The Blazor client listens for this event and updates the UI when it fires. This is what causes the errors displayed to update, as shown in this code:
public event EventHandler<EventArgs> ModelChanged;
protected void OnModelChanged() {
ModelChanged?.Invoke(this, new EventArgs()); }
This validation engine is admittedly a very simple design with lots of opportunities for improvement. In a production- business application, it would be useful to have severity levels for the errors, such as Info, Warning and Error. In certain scenarios, it would be helpful if the rules could be loaded dynamically from a configuration file without the need to modify the code. I’m not advocating that you create your own validation engine; there are a lot of choices out there. This one is designed to be good enough to demo a real-world example, but simple enough to make it fit into this article and be easy to understand.
Making the Rules
At this point, there’s a RegistrationData class that contains the form fields. The fields in the class are decorated with attributes such as RequiredRule and EmailRule. The RegistrationData class inherits from a ModelBase class that contains all the logic to validate the rules and to notify the client of changes. The last piece of the vali- dation engine is the rule logic itself. I’ll explore that next.
I start by creating a new class in the SharedLibrary called IModel- Rule. This rule consists of a single Validate method that returns a ValidationResult. Every rule must implement the IModelRule interface, as shown here:
}
16 msdn magazine
C#