CommunityToolkit / CommunityToolkit/dotnet
Add properties for validation state and error
- Lingua principale
- C#
- Stelle
- 3.8k
- Fork
- 400
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
### 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
Guida per i contributori
Apri la guida per i contributori
Direzione di ricerca
Inizia leggendo la classe ObservableValidator e il comportamento di ObservablePropertyGenerator descritto nell’issue. Traccia il modo in cui le proprietà generate notificano attualmente le proprietà correlate alla validazione e il modo in cui vengono rilevati gli attributi di validazione. Il lavoro è completato quando sono implementati l’API IsValid richiesto e il comportamento di validazione di SetProperty generato, senza il workaround manuale descritto.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- csharp
- Ambito
- tooling
- Tipo di issue
- Funzionalità
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Ferma
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 25/100