CommunityToolkit / CommunityToolkit/dotnet

Implements NotifyPropertyValidationForAttribute

Open
#718 0 comments 2 reactions 0 assignees View on GitHub
feature request :mailbox_with_mail:
Dominant language
C#
Stars
3.8k
Forks
400
PR merge metrics
No merged PRs in 30d

Description

### Overview

Somes read-only properties may dependes on other to be validated, when the source property change, we shall be able to trigger validation in to a target property that dependes on it.

So a `[NotifyPropertyValidationFor(nameof(TargetProperty))] ` can be used to annotate the source property and then generate the code to call `ValidateProperty(TargetProperty, nameof(TargetProperty))`;

### API breakdown

Notify property change attribute used to decorate validation dependencies.

```csharp

//
/// An attribute that can be used to validate the target property when te annotated property changes. When this attribute is
/// used, the generated property setter will also call for the properties specified in the attribute data. This can be useful to keep the code compact when
/// there are one or more dependent properties that should be validated when the value of the annotated observable
/// property is changed. If this attribute is used in a field without , it is ignored.
///
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public class NotifyPropertyValidationForAttribute : Attribute
{
///
/// Initializes a new instance of the class.
///
/// The name of the property to validate when the annotated property changes.
public NotifyPropertyValidationForAttribute(string propertyName);

///
/// Initializes a new instance of the class.
///
/// The name of the property validate when the annotated property changes.
///
/// The other property names to validate when the annotated property changes. This parameter can optionally
/// be used to indicate a series of dependent properties from the same attribute, to keep the code more compact.
///
public NotifyPropertyValidationForAttribute(string propertyName, params string[] otherPropertyNames);

///
/// Gets the property names to validate when the annotated property changes.
///
public string[] PropertyNames { get; }
}
```

The Source Generator for properties shall include the call to ValidateProperty after notify the change and only when the property has changed like this.

```csharp

///
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Name
{
get => _name;
set
{
if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(_name, value))
{
OnNameChanging(value);
OnNameChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
_name = value;
ValidateProperty(value, "SelectedBirthMonthIndex");
OnNameChanged(value);
OnNameChanged(default, value);
//Notify validation dependency property here
ValidateProperty(FullName, "FullName")

OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
}
}
}

```

### Usage example

In the next sample the FullName property depends on _name and _lastName and as a computed property validation needs to be fired externally, currently we can do this using the partials method on each source property but annotate the property with `NotifyPropertyValidationFor` will be easier.
```csharp
public class MyClass : ObservableValidator
{
[Required]
[ObservableProperty]
[NotifyPropertyValidationFor(nameof(FullName))]
private string _name;

[Required[
[ObservableProperty]
[NotifyPropertyValidationFor(nameof(FullName))]
private string _lastName;

[MinLength(5)]
[MaxLength(100)]
[CustomValidation(typeof(MyClass), nameof(ValidateFullName))]
public string FullName => $"{Name} {LastName}";

public static ValidationResult ValidateFullName(string name, ValidationContext context)
{
MyClass instance = (MyClass)context.ObjectInstance;
bool isValid = CustonValidationHere(FullName);

if (isValid)
{
return ValidationResult.Success;
}

return new("The name was not validated by the fancy custom validator");
}
}
```

### Breaking change?

I'm not sure

### Alternatives

As mention before the current alternative to implement the `OnPropertyChanged` partial method for each source property and call to `ValidateProperty(TargetProperty, nameof(TargetProperty));`

Like this:

```csharp
public class MyClass : ObservableValidator
{
[Required]
[ObservableProperty]
private string _name;

[Required[
[ObservableProperty]
private string _lastName;

[MinLength(5)]
[MaxLength(100)]
[CustomValidation(typeof(MyClass), nameof(ValidateFullName))]
public string FullName => $"{Name} {LastName}";

public static ValidationResult ValidateFullName(string name, ValidationContext context)
{
MyClass instance = (MyClass)context.ObjectInstance;
bool isValid = CustonValidationHere(FullName);

if (isValid)
{
return ValidationResult.Success;
}

return new("The name was not validated by the fancy custom validator");
}

partial void OnNameChanged(string value)
{
ValidateProperty(FullName, nameof(FullName));
}

partial void OnLastNameChanged(string value)
{
ValidateProperty(FullName, nameof(FullName));
}
}
```

### Additional context

_No response_

### Help us help you

Yes, I'd like to be assigned to work on this item

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.