microsoft-ui-xaml
microsoft-ui-xaml copied to clipboard
Add Input Validation Support to WinUI (UWP and Win32 Desktop) with INotifyDataErrorInfo
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
@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.
This is the kind of validation mentioned during Build 2018
` <TextBox x:Name="UserName" Header="User Name:" Text="{x:Bind ViewModel.Person.UserName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<PasswordBox 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:
-
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.
-
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).
-
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.