CommunityToolkit / CommunityToolkit/dotnet
Add properties for validation state and error
- Dominant language
- C#
- Stars
- 3.8k
- Forks
- 400
- PR merge metrics
- No merged PRs in 30d
Description
### Overview
I tried to add validations for my sign in and sign forms based on ObservableValidator.
I found that there are many lines of code can be generated by a generator.
Here is [a snapshot of my code](https://github.com/tuyen-vuduc/chick-and-paddy-dotnet-maui/blob/main/src/ChickAndPaddy/Features/Auth/Models/ForgotPasswordFormModel.cs) to have validation in my form
```csharp
namespace ChickAndPaddy;
public abstract class BaseFormModel : ObservableValidator
{
protected virtual string[] ValidatableAndSupportPropertyNames => new string[0];
public virtual bool IsValid()
{
ValidateAllProperties();
foreach (var propertyName in ValidatableAndSupportPropertyNames)
{
OnPropertyChanged(propertyName);
}
return !HasErrors;
}
}
public partial class ForgotPasswordFormModel : BaseFormModel
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PhoneNumberValid), nameof(PhoneNumberInvalidMessage))]
[Required(ErrorMessage = "Your phone number is required to recover your password.")]
[Phone(ErrorMessage = "You have enter an invalid phone number.")]
[NotifyDataErrorInfo]
string phoneNumber;
public bool PhoneNumberValid => GetErrors(nameof(PhoneNumber)).Any() == false;
public string PhoneNumberInvalidMessage => GetErrors(nameof(PhoneNumber)).FirstOrDefault()?.ErrorMessage;
protected override string[] ValidatableAndSupportPropertyNames => new[]
{
nameof(PhoneNumber),
nameof(PhoneNumberValid),
nameof(PhoneNumberInvalidMessage),
};
}
````
**Another version**
```csharp
public partial class ForgotPasswordFormModel : BaseFormModel
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PhoneNumberErrors))]
[Required(ErrorMessage = "Your phone number is required to recover your password.")]
[Phone(ErrorMessage = "You have enter an invalid phone number.")]
[NotifyDataErrorInfo]
string phoneNumber;
public string PhoneNumberErrors => GetErrors(nameof(PhoneNumber));
protected override string[] ValidatableAndSupportPropertyNames => new[]
{
nameof(PhoneNumber),
nameof(PhoneNumberValid),
nameof(PhoneNumberInvalidMessage),
};
}
```
### API breakdown
- Add `IsValid` to `ObservableValidator` class
- To validate all properties and
- To notify validatable and support properties (so is the UI reflected)
- Changes to the generated observable property
- We should make a call to `SetProperty` instead of assigning backing field directly if the base class is `ObservableObject`
- We should call to `SetProperty` version of `ObservableValidator` if the owning class inherits from that operation with param `validate` assigned to true if there is any validation attributes
**Current generated code**
```csharp
///
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.ComponentModel.DataAnnotations.RequiredAttribute(ErrorMessage = "Please enter your phone number")]
[global::System.ComponentModel.DataAnnotations.PhoneAttribute(ErrorMessage = "Please enter a valid phone number")]
public string UserName
{
get => userName;
set
{
if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(userName, value))
{
OnUserNameChanging(value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.UserName);
userName = value;
OnUserNameChanged(value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserName);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameValid);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameInvalidMessage);
}
}
}
```
**Base class is ObjectValidator**
```csharp
///
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.ComponentModel.DataAnnotations.RequiredAttribute(ErrorMessage = "Please enter your phone number")]
[global::System.ComponentModel.DataAnnotations.PhoneAttribute(ErrorMessage = "Please enter a valid phone number")]
public string UserName
{
get => userName;
set
{
if (SetProperty(ref userName, value, global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.UserName))
{ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameValid);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameInvalidMessage);
}
}
}
```
**Base class is ObservableObject**
```csharp
///
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.ComponentModel.DataAnnotations.RequiredAttribute(ErrorMessage = "Please enter your phone number")]
[global::System.ComponentModel.DataAnnotations.PhoneAttribute(ErrorMessage = "Please enter a valid phone number")]
public string UserName
{
get => userName;
set
{
var validate = true; // check by generator to know if there are any validation attributes attached to the field
if (SetProperty(ref userName, value, validate, global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.UserName))
{ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameValid);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.UserNameInvalidMessage);
}
}
}
```
### Usage example
```
// Call this method to validate and notify validatable and support properties
validator.IsValid();
```
### Breaking change?
No
### Alternatives
Define properties manually
```csharp
namespace ChickAndPaddy;
public abstract class BaseFormModel : ObservableValidator
{
protected virtual string[] ValidatableAndSupportPropertyNames => new string[0];
public virtual bool IsValid()
{
ValidateAllProperties();
foreach (var propertyName in ValidatableAndSupportPropertyNames)
{
OnPropertyChanged(propertyName);
}
return !HasErrors;
}
}
public partial class ForgotPasswordFormModel : BaseFormModel
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PhoneNumberValid), nameof(PhoneNumberInvalidMessage))]
[Required(ErrorMessage = "Your phone number is required to recover your password.")]
[Phone(ErrorMessage = "You have enter an invalid phone number.")]
[NotifyDataErrorInfo]
string phoneNumber;
public bool PhoneNumberValid => GetErrors(nameof(PhoneNumber)).Any() == false;
public string PhoneNumberInvalidMessage => GetErrors(nameof(PhoneNumber)).FirstOrDefault()?.ErrorMessage;
protected override string[] ValidatableAndSupportPropertyNames => new[]
{
nameof(PhoneNumber),
nameof(PhoneNumberValid),
nameof(PhoneNumberInvalidMessage),
};
}
````
**Another version**
```csharp
public partial class ForgotPasswordFormModel : BaseFormModel
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PhoneNumberErrors))]
[Required(ErrorMessage = "Your phone number is required to recover your password.")]
[Phone(ErrorMessage = "You have enter an invalid phone number.")]
[NotifyDataErrorInfo]
string phoneNumber;
public string PhoneNumberErrors => GetErrors(nameof(PhoneNumber));
protected override string[] ValidatableAndSupportPropertyNames => new[]
{
nameof(PhoneNumber),
nameof(PhoneNumberValid),
nameof(PhoneNumberInvalidMessage),
};
}
```
### Additional context
_No response_
### Help us help you
Yes, I'd like to be assigned to work on this item
Contributor guide
Assessment
This issue has not been assessed yet.