Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Suggested input validation architecture for WinUI with MVVM #5859

Open
Mohamed1984 opened this issue Sep 10, 2021 · 5 comments
Open
Labels
area-InputValidation feature proposal New feature proposal product-winui3 WinUI 3 issues team-Controls Issue for the Controls team wct

Comments

@Mohamed1984
Copy link

Proposal: Suggested input validation architecture for WinUI with MVVM

Summary

Simpler input validation architecture than that based on INotifyPropertyChanged interface

Rationale

Input validation of WinUI is not yet stabilized. Hence, there is a chance for improving the architecture. The proposed input validation based on INotifyPropertyChanged interface is lacking the (form validation) feature and puts a big burden on the programmer to unnecessarily manage and store errors. A better approach is desired that is easy to implement, configurable, and satisfy all user needs.

Before i present the details of my proposal let me abstract the input validation task into three levels of complexity:

  1. Isolated property validation
    In this simple kind of validation, we are interested in validating the input based only on the user input value. The validation is independent from other property values or any other state.
    Validation using property annotation is enough for this kind of validation. The programmer need only annotate the viewmodel properties with desired validation rules.

  2. Contextual property validation
    In this kind of validation, the validation logic of a property depends on other state than its own value. Consider for example the verification that user name written in a text field is unique. This may require sending a request to a web server to verify the uniqueness of the name.

  3. Form validation
    In some circumstances, the form state may be invalid even if all input fields are valid. This may happen if the input fields values are not consistent in some way that depends on business logic. Such kind of validation can be implemented after the user clicks the submit button, but it may be beneficial to do the form validation logic as the user input data.

Proposed approach

The programmer should be able to select one of the three validation levels he thinks suitable for him. Let's consider how each approach may be implemented.

Isolated property validation

This should simply be enabled whenever the view model (or page/window) implements some interface such as IValidatable

public interface IValidatable<T>
   {
   }

The IValidatable is a generic interface that takes the view model class type as argument. The UI part should inspect the view model object for IValidatable interface and correspondingly respond to PropertyChanged event by checking for property validation annotations and doing validation stuff. The validation errors need not be propagated to the view model.
The input control should have some Errors property that contains the validation errors.

Contextual property validation

Another option for the programmer is to implement contextual property validation approach. The view model should implement the IPropertyValidatable interface.

public interface IPropertyValidatable<T>
    {
        Task ValidateProperty(IPropertyValidationContext<T> context );
    }

The method ValidateProperty should be called by the UI whenever propertyChanged event is fired. The method is asynchronous to allow for asynchronous validations (for example making sure that user name is unique).

The validation context gives enough information about other validation state, this may be useful during the contextual property validation.

public interface IPropertyValidationContext<T>
    {
        string PropertyName { get; }
        object OldPropertyValue { get; }
        void RegisterError(IValidationError error);
        PropertyValidationState GetPropertyValidationState(string propertyName);
    }
    public enum PropertyValidationState
    {
        Undetermined,
        Valid,
        Invalid
    }
    public interface IValidationError
    {
        public string Error { set; get; }
    }

The GetPropertyValidationState method returns validation state of other properties, it is possible that some input field didn't receive any user input yet, hence the corresponding property validation state should be undetermined.

Form validation

Form validation is an extension of contextual property validation that handles these cases:

  1. The form should be validated even if the user didn't input anything. consider for example a form that asks the user to enter new item data. It is invalid to leave the form empty !
  2. Form validation should be performed after all properties are checked to be valid.

Form validation can be implemented when the user implements the IFormValidatableInterface

public interface IFormValidatable<T>:IPropertyValidatable<T>
    {
        Task ValidateForm(IFormValidationContext<T> context);
    }
public interface IFormValidationContext<T>
    {
        void RegisterPropertyError(string propertyName,IValidationError error);
        void RegisterFormError(IValidationError error);
        bool IsPropertyValid(string propertyName);
        bool IsPropertyInputByUser(string propertyName);
    }

The logic for invokation of ValidateForm method should be as follows:

  1. At page load the UI validates all properties, and store the validation state (valid/invalid) of each property
  2. If all properties are valid, the ValidateForm is called
  3. Whenever the user changes any input field, the ValidateProperty is called, then if all properties are valid, ValidateForm is invoked.

The form validation state can be easily stored in the viewmodel using the validateForm method.
Unlike the ValidateProperty method, there is no undetermined input state, all properties are always checked from the beginning.
The FormValidationContext have methods to register property errors or global form errors. It can also provide validation state about any property or InputState (i.e. whether the user input any data in the field or not).

Interaction with PropertyChanged event

In validation based on INotifyPropertyChanged, the invokation of validation is the responsibility of the viewmodel designer in the property set methods. I think this is unnecessary complication. The UI should respond to PropertyChanged event and call the ValidateProperty correspondingly.

@Mohamed1984 Mohamed1984 added the feature proposal New feature proposal label Sep 10, 2021
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Sep 10, 2021
@asklar
Copy link
Member

asklar commented Sep 11, 2021

@Mohamed1984 I recommend thinking about how this would be expressed in WinRT, not C#, since these features need to work across languages.

ref: https://docs.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#parameterized-interfaces

Third-parties cannot define new parameterized interfaces. Only the parameterized interfaces defined by the system are supported.

@omikhailov
Copy link

IMO, WinUI should just provide UI for validation and do not impose restrictions on validation logic. Such logic could be provided by third-party and open-source frameworks like Windows Toolkit.

@StephenLPeters StephenLPeters added area-InputValidation team-Controls Issue for the Controls team labels Sep 13, 2021
@StephenLPeters StephenLPeters added product-winui3 WinUI 3 issues and removed needs-triage Issue needs to be triaged by the area owners labels Sep 13, 2021
@michael-hawker
Copy link
Collaborator

@Mohamed1984 there's the existing INotifyDataErrorInfo part of .NET Standard 2.0 way to do this in the BCL already, how would your proposal be different than that? (See more info about using this interface in this blog here.) It seems like we should keep evolving things in .NET across UX stacks compared to creating a new independent system.

There are other things building on top of this already like the ObservableValidator helper in the MVVM Toolkit that work for both WPF and WinUI 3 already.

This is a duplicate/related to #4671 and #4567.

@michael-hawker
Copy link
Collaborator

Also see discussions in #179.

@Mohamed1984
Copy link
Author

@Mohamed1984 I recommend thinking about how this would be expressed in WinRT, not C#, since these features need to work across languages.

ref: https://docs.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#parameterized-interfaces

Third-parties cannot define new parameterized interfaces. Only the parameterized interfaces defined by the system are supported.

I have no thorough knowledge of WinRT. But i think the solution may be based on source generation tools.
The binding that uses {x:Bind } generates code for doing one way /two way binding. Why not simply call the method "Task ValidateProperty(IPropertyValidationContext context )" from the generated code after some change event is triggered?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-InputValidation feature proposal New feature proposal product-winui3 WinUI 3 issues team-Controls Issue for the Controls team wct
Projects
None yet
Development

No branches or pull requests

6 participants