CommunityToolkit / CommunityToolkit/dotnet

Add properties for validation state and error

Aperta
#488 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub
feature request :mailbox_with_mail: mvvm-toolkit :toolbox: needs author feedback :memo:
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

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.