Microsoft-ui-xaml: Add Input Validation Support to UWP with INotifyDataErrorInfo

Created on 14 Jan 2019  ·  50Comments  ·  Source: microsoft/microsoft-ui-xaml

Proposal: Add Input Validation Support with INotifyDataErrorInfo

Summary

Add Input Validation Support with INotifyDataErrorInfo that is supported by x:Bind and Binding. Both Markup Extensions should get a new ValidatesOnNotifyDataErrors property that is - like in WPF's Binding - true by default.

Rationale

Currently UWP does not have any Input Validation built into the platform. But every line of business app requires input validation. It's one of the most important features for a proper enterprise app. There's an entry on uservoice that says this feature will come with WinUI, but I haven't seen anything yet: https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/13052589-uwp-input-validation

feature proposal needs-winui-3 team-Markup

Most helpful comment

INDEI requires you to implement all your entities and sub entities.

Can you elaborate more on this?

Using rules, no changes are required, it all works automatically using validation attributes only.

For the most part, it all works automatically when using ValidationAttributes and INotifyDataErrorInfo as well. I'll show some code snippets here that are in the spec, just so we're (hopefully) on the same page.

FWIW, we're planning on having this ValidationBase class live in the Toolkit, so you don't have to write this boilerplate code yourself.

public class ValidationBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
   public event PropertyChangedEventHandler PropertyChanged;
   public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

   protected void SetValue<T>(ref T currentValue, T newValue, [CallerMemberName] string propertyName = "")
   {
       if (!EqualityComparer<T>.Default.Equals(currentValue, newValue))
       {
           currentValue = newValue;
           OnPropertyChanged(propertyName, newValue);
       }
   }

   readonly Dictionary<string, List<ValidationResult>> _errors = new Dictionary<string, List<ValidationResult>>();
   public bool HasErrors
   {
       get
       {
           return _errors.Any();
       }
   }
   public IEnumerable GetErrors(string propertyName)
   {
       return _errors[propertyName];
   }

   private void OnPropertyChanged(string propertyName, object value)
   {
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       Validate(propertyName, value);
   }

   private void AddErrors(string propertyName, IEnumerable<ValidationResult> results)
   {
       if (!_errors.TryGetValue(propertyName, out List<ValidationResult> errors))
       {
           errors = new List<ValidationResult>();
           _errors.Add(propertyName, errors);
       }

       errors.AddRange(results);
       ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
   }

   private void ClearErrors(string propertyName)
   {
       if (_errors.TryGetValue(propertyName, out List<ValidationResult> errors))
       {
           errors.Clear();
           ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
       }
   }

   public void Validate(string memberName, object value)
   {
       ClearErrors(memberName);
       List<ValidationResult> results = new List<ValidationResult>();
       bool result = Validator.TryValidateProperty(
            value,
            new ValidationContext(this, null, null)
            {
                MemberName = memberName
            },
            results
            );

        if (!result)
        {
            AddErrors(memberName, results);
        }
    }
}

Your model would then derive from that class and look like this:

public class Person : ValidationBase
{
    private string name;
    [MinLength(4)]
    [MaxLength(6)]
    public string Name
    {   
        get { return name; }
        set { SetValue(ref name, value); }
    }
}

Assuming this Person class is set to a ViewModel property on your Page, you then bind to that, and validation happens automatically:

<TextBox Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />

I understand this might be a little different than what you are doing today, but we're trying to not support two different ways of doing the same thing if we don't need to. Let me know if this makes sense, or if you think there is still something we're missing!

All 50 comments

@LucasHaines how does this relate to the input validation features you've been looking into?

@jevansaks This is directly related to the Input Validation work i'm outlining.

Great. If you've any preview stuff to share, I'm happy to try it and provide feedback and thoughts.

image

This is the kind of validation mentioned during Build 2018

`
x:Name="UserName" Header="User Name:"
Text="{x:Bind ViewModel.Person.UserName,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}" />

x:Name="Password" Header="Password:"
Password="{x:Bind ViewModel.Person.Password,
UpdateSourceTrigger=LostFocus,
Mode=TwoWay}"/>
`

@thomasclaudiushuber how important do you think making this work as part of {Binding} is? I only ask because the technical challenges there are non-trivial, as well as it not being able to work downlevel. Our original idea was to just support x:Bind, which will only require markup compiler changes, and is capable of working downlevel.

Also, we were only planning on supporting the INotifyDataErrorInfo and DataAnnotations paradigm. We weren't intending on adding something analogous to Binding.ValidationRules, and thus didn't feel there was sufficent need for a ValidatesOnNotifyDataErrors.

We'd love to get your feedback as we continue to work on this feature so that it can be done right!

@stevenbrix

Short answer: Yes, what you think and what you plan sounds great. Just x:Bind is fine, and just INotifyDataErrorInfo is fine too.

Long:

Just x:Bind? 👍

In all WPF-LOB cases I can think of there was always a kind of ViewModel that was known at compile-time, no duck typing, so x:Bind is fine. I wrote {Binding} in this issue too as I thought you can support it to get the same syntax as in WPF. But this is more "nice to have" than super important. And as {Binding} and x:Bind are two completely different things behind the scenes and the runtime stuff with {Binding} is non-trivial like you mentioned, forget about {Binding}. I think having it just for x:Bind is totally fine, it will work for the use cases I can think of. And from all the conference talks I did on UWP and x:Bind, I can tell you that x:Bind is one of the most favorite UWP features of all XAML developers and I told all of them "Prefer x:Bind over Binding wherever you can". :) Getting intellisense, perf, step-into-code, and compile-time errors makes x:Bind the new default on UWP, so having the validation support just there is fine and a good decision imo.

Just INotifyDataErrorInfo and DataAnnotations paradigm? 👍

Just supporting INotifyDataErrorInfo and DataAnnotations paradigm sounds good to me. WPF doesn't pick up Data Annotations automatically, you need to hook them up manually in your INotifyDataErrorInfo implementation by using the Validator class. You mean this when you say "DataAnnotations paradigm", right? Or do you plan built-in DataAnnotation support that allows a dev to just put a Data Annotation on a property and this just works? Like in ASP.NET Core MVC? While that would be great, I think it's not necessary. Having the classes ValidationContext, Validator and ValidationResult and the other classes from System.ComponentModel.DataAnnotations is sufficient imo.

Add Binding.ValidationRules? Noooooo ;-)

No, definitely don't add that. INotifyDataErrorInfo is enough and it's the best. I think I never used ValidationRules anymore after IDataErrorInfo support was added to WPF (If I remember correctly this was in .NET Framework 3.5). And when INotifyDataErrorInfo was added in .NET Framework 4.5, my customers and I have used that interface in all projects for input validation. Supporting just this interface is fine.

Add ValidatesOnNotifyDataErrors? No, but...

Yes, you don't have to add this one. But it has nothing to do with the Binding.ValidationRules. This property is directly related to the INotifyDataErrorInfo interface. On WPF's Binding Markup Extension, ValidatesOnNotifyDataErrors is by default true, which means the {Binding} picks up the implemented INotifyDataErrorInfo validation if the bound object implements that interface. If you set the ValidatesOnNotifyDataErrors property on the Binding markup extension to false, then the Binding Markup Extension won't pick up the INotifyDataErrorInfo implementation for the validation. So, maybe this isn't too hard to implement with x:Bind, but maybe we don't need it. So, no, don't do it. That's ok to leave this out. But let me describe the scenario where I needed this in the past:

WPF controls show by default a red border that is defined via the Validation.ErrorTemplate. Now let's assume you have an INotifyDataErrorInfo implementation with a class that has FirstName property. It's required, so you return an error if the FirstName property is null or empty. Now you bound a TextBox to that FirstName property. When a user removes that firstname, a red border appears. Great, exactly what you want. But maybe you have at another place in the UI another control that is bound to the FirstName property too. For example a TextBlock in a Tab-Header. WPF will display on that TextBlock a red border too when there's a validation error. But this might not be what you want. You want the error just on the TextBox where the user can change the firstname, but not in the TextBlock in the Tab header. To get rid of the red border on the TextBlock, you don't have to edit the TextBlock's ErrorTemplate, instead you just turn of the INotifyDataErrorInfo validation on the TextBlock-FirstName-Binding by setting the ValidatesOnNotifyDataErrors property to false. That's the one and only use case I ever had for this property. :)

I hope this helps.

Summary

Yes, I totally agree, having input validation just on x:Bind with support for INotifyDataErrorInfo is a good plan.

No, it's a fantastic plan, I'm already super excited!

@thomasclaudiushuber thanks for the detailed follow up! The core scenarios you describe lineup with what we believed to be the real value driver, and so that's good to hear. The overall design of this feature and API has been in flux, mostly due to the previous work that WPF had. There were a few major things to call out here:

  1. As discussed above, there were certain aspects of WPF's validation system that we didn't feel added any value, or were superseded by better patterns, and so we weren't planning on bringing them to UWP.

  2. Certain features/functionality of WPF's validation system take advantages of core WPF features that don't exist in UWP. Most notably here is the adorner layer, which allows any UIElement in WPF to display validation visuals, not just Controls (via adding some template part).

  3. UWP Xaml does not currently have the concept of attached events, and mirroring the Validation class would add the Validation.Error event. Not that is a deal breaker, just something to call out as we're generally wary of adding new concepts, as it only adds complexity. On top of that, since the work requires changes to the Control's Template, only the set of controls we provide the specific template changes for would work out of the box. Generally speaking, having an API that appears to work, but doesn't in some scenarios is not good practice, and so we were thinking it would be better to divorce ourselves from that model. This would mean having something else like a common interface and/or attributes that controls implement.

We're still in the process of understanding how to do API and spec reviews in the new and exciting open source world. We'd love to get the community's feedback, so hopefully we can get the API proposals out soon so the community can take a look and help us land something everyone is excited about!

@stevenbrix When you do begin the open source spec for the validation features, will you provide the illustrations of how the validation will look, as well as the sample scenarios so the conversation can be a little broader for those of us more into the UI side, than the coding :)

@mdtauk Absolutely. The current UI we shared is still accurate. If you have feedback, feel free to provide on this thread.

@LucasHaines The UI looks good to me. But I wonder how you can adjust how the errors are displayed. I haven't found anything regarding this and I don't know if you have already something to share in that area. Will there be something similar like the attached Validation.ErrorTemplate property from WPF?

Is there a timeline for when a formal spec/proposal will be released for this?
What can I do to help this get into the product sooner?

I agree with @mrlacey above... I could really use this now, so I'm happy to help out where I can.

We are working on an open spec and I will let everyone know it's available in the spec repo.

Question: why not support IDataErrorInfo as well? It's supported in .net standard, and it would be weird not to support it? This should not affect performance for the people not using it since it could even be checked at compile time which interface to use for the binding?

We started open spec review for Input Validation in the open spec repo. https://github.com/Microsoft/microsoft-ui-xaml-specs/pull/26

Hi guys, how are we tracking on this - do we have an ETA?

@knightmeister We have an open spec created here. Please feel free to participate in the review and help shape the feature. We are planning to ship this as part of WinUI 3.0. If you want more information on the WinUI 3.0 roadmap check out issue #717.

Hey Everyone - please take a look at the latest samples in the spec. @stevenbrix made some great updates to the samples and address other feedback.

One question: will the validation information flow into the control hierarchy ? For example will it be possible for a container control (eg TabView or Pivot) to know that there is a child control in an invalid state ?

Think of a complex view with multiple containers controls, where you want to highlight which parts of the view requires attention, for example by changing the style of a Pivot header when any child control is in an invalid state.

This is a critical issue to me.
My entire XAML validation system is based on WPF validation rules, it reads the field and entity model from the BindingExpression and creates validation rules according to the DA validation attributes (see this).

For example will it be possible for a container control (eg TabView or Pivot) to know that there is a child control in an invalid state ?

@tbolon yes, this is something that can be done, although it's unlikely there will be built in support in those containers for it. We've thought about creating a Form control that would have this functionality built-in, but it's probably on the backlog at the moment. Like in WPF, there is a ValidationError (that name could be wrong) event that each control fires when it becomes invalid/valid. This event isn't a routed event though, so you have to subscribe on each element that you are interested in listening to.

My entire XAML validation system is based on WPF validation rules,

@weitzhandler we currently aren't planning on adding something like the ValidationRule to WinUI. Is there something that using this gives you over using ValidationAttributes and having your model implement INotifyDataErrorInfo? I'd love to see what your Model and XAML look like to get a better understanding of your usage scenario.

INDEI requires you to implement all your entities and sub entities.
Using rules, no changes are required, it all works automatically using validation attributes only.

INDEI requires you to implement all your entities and sub entities.

Can you elaborate more on this?

Using rules, no changes are required, it all works automatically using validation attributes only.

For the most part, it all works automatically when using ValidationAttributes and INotifyDataErrorInfo as well. I'll show some code snippets here that are in the spec, just so we're (hopefully) on the same page.

FWIW, we're planning on having this ValidationBase class live in the Toolkit, so you don't have to write this boilerplate code yourself.

public class ValidationBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
   public event PropertyChangedEventHandler PropertyChanged;
   public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

   protected void SetValue<T>(ref T currentValue, T newValue, [CallerMemberName] string propertyName = "")
   {
       if (!EqualityComparer<T>.Default.Equals(currentValue, newValue))
       {
           currentValue = newValue;
           OnPropertyChanged(propertyName, newValue);
       }
   }

   readonly Dictionary<string, List<ValidationResult>> _errors = new Dictionary<string, List<ValidationResult>>();
   public bool HasErrors
   {
       get
       {
           return _errors.Any();
       }
   }
   public IEnumerable GetErrors(string propertyName)
   {
       return _errors[propertyName];
   }

   private void OnPropertyChanged(string propertyName, object value)
   {
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       Validate(propertyName, value);
   }

   private void AddErrors(string propertyName, IEnumerable<ValidationResult> results)
   {
       if (!_errors.TryGetValue(propertyName, out List<ValidationResult> errors))
       {
           errors = new List<ValidationResult>();
           _errors.Add(propertyName, errors);
       }

       errors.AddRange(results);
       ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
   }

   private void ClearErrors(string propertyName)
   {
       if (_errors.TryGetValue(propertyName, out List<ValidationResult> errors))
       {
           errors.Clear();
           ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
       }
   }

   public void Validate(string memberName, object value)
   {
       ClearErrors(memberName);
       List<ValidationResult> results = new List<ValidationResult>();
       bool result = Validator.TryValidateProperty(
            value,
            new ValidationContext(this, null, null)
            {
                MemberName = memberName
            },
            results
            );

        if (!result)
        {
            AddErrors(memberName, results);
        }
    }
}

Your model would then derive from that class and look like this:

public class Person : ValidationBase
{
    private string name;
    [MinLength(4)]
    [MaxLength(6)]
    public string Name
    {   
        get { return name; }
        set { SetValue(ref name, value); }
    }
}

Assuming this Person class is set to a ViewModel property on your Page, you then bind to that, and validation happens automatically:

<TextBox Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />

I understand this might be a little different than what you are doing today, but we're trying to not support two different ways of doing the same thing if we don't need to. Let me know if this makes sense, or if you think there is still something we're missing!

You're right, but that requires your entities to be non POCOs.
I should give it a try tho. Might not be so bad using an EntityBase class in a LoB app. I'm just used to use POCOs.

UWP doesn't have *HAVE Validation*!!!!!!! I just started a project. Back to WPF till a few years time.

@rufw91 I know, right?!

What are your time constraints? The first implementation of this is in the WinUI3 alpha, and we should have a preview for WinUI desktop coming soon in the //Build timeframe. We plan on RTM'ing by the end of the year

UWP doesn't have *HAVE Validation*!!!!!!! I just started a project. Back to WPF till a few years time.

I've decided to stick with WPF for the upcoming years as well. I've done it all (WPF, SL, WP, UWP, etc), in the end only 1 tech seems solid, which is WPF. Maybe in a few years it could be interesting to check out where WinUI is, but I am tired of switching to new tech and being left alone in the dark. WPF is mature and works great. Maybe in a few years Windows as an OS isn't relevant at all, so let's await that before making any more investments in the platform.

Maybe in a few years Windows as an OS isn't relevant at all, so let's await that before making any more investments in the platform.

Considering that we just surpassed 1 billion Windows 10 installs, I have a hard time believing that will ever be true. But I definitely feel your frustration and don't blame you for staying on WPF. FWIW, our team is taking ownership of WPF now, so let me know if there is anything you want to see us do there.

INDEI requires you to implement all your entities and sub entities.

Can you elaborate more on this?

With my code, all POCO entities can remain free of implementing those interfaces, and remain without a base class. INPC is auto-implemented using Fody. Most I'd do is implement IValidatableObject when more refined validation is required. And it works on sub-types as well.

FWIW, we're planning on having this ValidationBase class live in the Toolkit, so you don't have to write this boilerplate code yourself.

That's no good. What if my entities already have a base class? That's also why I'm not such a big fan of INotifyDataErrorInfo. INPC properties are already a headache for themselves, especially when dealing with massive data apps with tons of entities.

It's quite shocking that UWP doesn't have proper validation yet. That's a crucial basic feature in almost any app and should be given first priority.

It's quite shocking that UWP doesn't have proper validation yet. That's a crucial basic feature in almost any app and should be given first priority.

Yup, that's why we're working on it :)

That's no good. What if my entities already have a base class?

I'm just trying to understand your usage here. What is the base class, and could it derive from this class?

Yup, that's why we're working on it :)

Thank you, much appreciated. This, and the rest of your work.
What does the label needs-winui-3 imply?

I'm just trying to understand your usage here. What is the base class, and could it derive from this class?

I'm using OData Connected Service to get entities generated on my client.
The generated objects are of this pattern:

c# public abstract partial class ContactBase : Microsoft.OData.Client.BaseEntityType, INotifyPropertyChanged

What does the label needs-winui-3 imply?

@weitzhandler
It implies Input Validation will come with WinUI 3. (3.0 as currently planned). It probably won't come to WinUI 2 as that would require changes to the OS XAML code which is in maintenance mode by now.

I'm using UWP in an Uno Platform app. Hopefully WinUI will be covered upon its release.
Thanks for the update tho!

I'm just trying to understand your usage here. What is the base class, and could it derive from this class?

Having a base class is often mandated by 3rd party, e.g. the persistence framework you use. UWP/WinUI also demanding a base class just for validation is not an option. It needs to be an interface to be useful, none of our applications could make use of a base class. (That doesn't mean you can't have a base class for people who want to use it, it just needs to be optional and not the only way to do validation.)

Having a base class is often mandated by 3rd party, e.g. the persistence framework you use. UWP/WinUI also demanding a base class just for validation is not an option. It needs to be an interface to be useful, none of our applications could make use of a base class.

Let's back up a bit. I think the last few comments I've made have caused a bit of confusion, which is my bad.

All that will be needed by the model is to implement System.ComponentModel.INotifyDataErrorInfo (or Windows.UI.Xaml.Data.INotifyDataErrorInfo for C++). The compiler will properly generate the code to hook that up with your view when using {x:Bind}.

The ValidationBase class I referred to above is a helper class that we were planning on shipping in the community toolkit that implements this, along with INotifyPropertyChanged. There's no need to derive from it if your use case doesn't allow it.

Thanks for your clarification @stevenbrix.

What about validation attributes and IValidatableObject, are they gonna be respected by the UWP runtime?

How about other attributes, like the MaxLength attribute you mentioned earlier, will a TextBox max length be set automatically like in ASP.NET?
Same with DataTypeAttribute, EditableAttribute.
Also DisplayAttribute for generating field headers.
As far as I remember they were all supported in Silverlight.

(see this).

What about validation attributes and IValidatableObject, are they gonna be respected by the UWP runtime?

So we currently are going to have support for the Required attribute. Some controls will automatically put a little asterisk next to the header text when this is used. We are open to supporting more in the future if you want. Please open a separate issue if you'd like to see more, and just to set expectations, any extra attribute support is highly unlikely to make the initial WinUI3 release.

As for IValidatableObject, I'm not sure how that would work. As a generalization, our engine works simply by listening to the INotifyDataErrorInfo.ErrorsChanged event. The actual validation of the model is up to the app developer, and generally done when transferring the value from the target to the source.

Maybe as an alternative method to the ValidationBase you proposed, there should be a set of extension methods, that run a validation process using Validator.TryValidateObject, and inject the validation results into the entity, notifying the changes?
This way, it can be achieved easily by just implementing INotifyDataErrorInfo. We could also have an interface that inherits from INotifyDataErrorInfo that adds a property that holds the error collection, so that the UWP runtime or the toolkit can recognize as an automated validatable entity instead, and set the property automatically if implemented.

Maybe as an alternative method to the ValidationBase you proposed, there should be a set of extension methods, that run a validation process using Validator.TryValidateObject, and inject the validation results into the entity, notifying the changes?

This is a great idea :)

🦙 Wondering if the new source generator paradigms could help out here as well?

Yes @michael-hawker, I like your thoughts!

I had the same thoughts a while ago when I first read about the new source generator possibilities.

I think with the new source generator paradigms we can build / generate a lot of stuff behind the scenes for INotifyDataErrorInfo and INotifyPropertyChanged.

I have a WPF course on Pluralsight that does exactly this with T4, INotifyPropertyChanged and INotifyDataErrorInfo, including Data Annotations for validation: https://www.pluralsight.com/courses/wpf-mvvm-advanced-model-treatment
The course also uses custom Attached Properties to hook up things.
The goal of this course is to show how you can build a MVVM WPF app that shows on every input field if it was changed and what was the original value.

So, definitely, generating things and maybe ending up with a library (maybe as part of WCT) would be awesome. And I'd love to redo that course for WinUI. :)

@rufw91 I know, right?!

What are your time constraints? The first implementation of this is in the WinUI3 alpha, and we should have a preview for WinUI desktop coming soon in the //Build timeframe. We plan on RTM'ing by the end of the year

@stevenbrix I hope so, i had to switch back to WPF, im actually just about done now.

I opened this one.

To follow up on this, after a conversation with @stevenbrix on Twitter, I've integrated the base INotifyDataErrorInfo implementation he provided here into a new ObservableValidator class being included in the new MVVM Toolkit (the Microsoft.Toolkit.Mvvm library), which also implements INotifyPropertyChanged and INotifyPropertyChanging 😄

You can see the related code and a recap of everything related to that library in this issue.
I've also included links there to the original issue for the MVVM Toolkit, as well as more docs on it for new devs.

Could you please give us an update on this? Curious if UWP 10.0 18362 supports INotifyDataErrorInfo. According to the following documentation page, https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo?view=netcore-3.1 INotifyDataErrorInfo "applies" to UWP 10.0. However, with UWP 10.0 18362 installed and targeted, the text boxes aren't displaying INotifyDataErrorInfo validations. The text boxes are described in XAML as follows:

<TextBox Text="{x:Bind ViewModel.Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

And we use ReactiveValidationObject as INotifyDataErrorInfo implementation here.

@worldbeater this is only in available in the latest previews of WinUI3, are you using that?

Thank you @stevenbrix! Going to try out WinUI3. Glad to hear INotifyDataErrorInfo and UWP are befriending, finally ✨

@worldbeater, it's still in preview, so we would love to get your feedback on it! 💪

@worldbeater by the way, docs says it's available on UWP 10.0, but it doesn't mean it's supported by it. Documentation isn't clear enough.

Was this page helpful?
0 / 5 - 0 ratings