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

[Feature] Microsoft.Toolkit.Mvvm package (Preview 2) #3429

Merged
merged 38 commits into from
Sep 24, 2020

Conversation

Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Aug 13, 2020

Closes #3428

PR Type

What kind of change does this PR introduce?

  • Feature

Overview

This PR is used to track and implement new features and tweaks for the Microsoft.Toolkit.Mvvm package.
See the linked issue for more info, and for a full list of changes included in this PR.

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link:
  • Sample in sample app has been added / updated (for bug fixes / features)
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

@Sergio0694 Sergio0694 added feature 💡 improvements ✨ DO NOT MERGE ⚠️ .NET Components which are .NET based (non UWP specific) labels Aug 13, 2020
@Sergio0694 Sergio0694 added this to the 7.0 milestone Aug 13, 2020
@ghost
Copy link

ghost commented Aug 13, 2020

Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌

@ghost ghost requested review from michael-hawker, azchohfi and Kyaa-dost August 13, 2020 16:36
@ghost ghost added the feature request 📬 A request for new changes to improve functionality label Aug 13, 2020
@Sergio0694
Copy link
Member Author

Looks like the changes in 1cb4df2 uncovered a bug in the .NET Native compiler, so the build is now failing in Release UWP 🤦‍♂️

ILT0005: 'C:\Program Files (x86)\Microsoft SDKs\UWPNuGetPackages\runtime.win10-x64.microsoft.net.native.compiler\2.2.8-rel-28605-00\tools\x64\ilc\Tools\nutc_driver.exe @"C:\Users\Sergio\source\repos\FieldAccessorRepro\FieldAccessorRepro\obj\x64\Release\ilc\intermediate\MDIL\FieldAccessorRepro.rsp"' returned exit code 2

I managed to isolate the issue in the custom FieldAccessor<T> type that was introduced. Specifically, this code is enough to cause the build error even in a blank UWP app (just place this right in MainPage.xaml.cs or anywhere else:

public delegate ref T FieldAccessor<T>();

private int n;

private void Button_OnClick(object sender, RoutedEventArgs e)
{
    FieldAccessor<int> accessor = () => ref n;

    accessor() = default;
}

I've also prepared a repro project which you can find here: FieldAccessorRepro.zip.

Will create a full .NET Native repro to send to the team, but in the meantime I was wondering whether @MichalStrehovsky or @tommcdon might have some ideas of how to work around the issue on the UWP end? Maybe some .rd.xml magic? 😄

@michael-hawker
Copy link
Member

@msftbot make sure @stevenbrix reviews before merging

@ghost
Copy link

ghost commented Sep 10, 2020

Hello @michael-hawker!

Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:

  • I'll only merge this pull request if it's approved by @stevenbrix

If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you".

Copy link

@stevenbrix stevenbrix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fairly good. Most comments are around validation of unset required properties. I feel like those should be considered invalid. There is a Validator.TryValidateObject method you can use to validate the whole model.

[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we do for properties marked Required?

If those fields are null, should we consider them errors?

Copy link
Member Author

@Sergio0694 Sergio0694 Sep 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I have to say I'm not 100% I understand the official documentation here.
From the MS docs:

GetErrors: Gets the validation errors for a specified property or for the entire entity.

I read that as "get/retrieve the currently stored errors for either a specific property or for the entity (so for all properties)", as in, return the errors that have previously been computed, without actually doing any validation now. By that logic, the code right now is just returning the existing errors corresponding to the input query 🤔

I mean, that makes sense to me since we expose a separate method to actually validate properties and update errors, which is ValidateProperty, but I have to say with how the docs are currently phrased that's not entirely clear.

I guess the question is - should GetErrors just return existing errors for a given query, or actually perform a new validation for a given property (or all properties, if the whole entity is targeted)? Thoughts?

Copy link

@stevenbrix stevenbrix Sep 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, the docs aren't clear. I imagine, like many things, it's up to us to define the behavior.

Edit: I saw your samples below, and realized this behavior is correct :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some bug in GitHub is causing me to not be able to comment on this further down. But basically, I don't feel like a ValidateProperty method should be required. Can ObservableValidator override the SetProperty method and call ValidateProperty itself?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, at the end of the day - I think this ok. When a user opens up a form, we don't want a bunch of errors for fields they haven't entered anything into yet

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fold ValidateProperty into the SetProperty method? Those should always go hand-in-hand.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 28b1977 as discussed on Discord 👍

{
if (errorsChanged)
{
this.totalErrors--;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure what this count value is for. Can we not use the errors dictionary as the source of truth?

Copy link
Member Author

@Sergio0694 Sergio0694 Sep 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an XML comment for that here:

https:/windows-toolkit/WindowsCommunityToolkit/blob/be109be7a7c1b03c9fac2fa51e6b1f4c398f5a48/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs#L27-L32

My idea here was - with how the code is currently setup, the dictionary can contain entries for valid properties, just with the list of errors being empty (so that we can reuse the same List<ValidationResult> instances when possible. So in order to check whether we have errors, we'd have to traverse the entire dictionary and check whether all values are empty lists (or if no entries are there at all). This would have a cast of O(N), as you'd have to traverse the entire dictionary once to check all properties, and the dictionary could potentially contain all the existing properties for a given object. So the point of this additional field, which is kept in sync with the dictionary, is to provide a fast O(1) accessor to quickly check whether or not there are any errors in the current instance.

Let me know what you think! 😊

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the attention to perf, but how often do we expect errors to transition between valid/invalid?

My gut is that we won't really be allocating that often to warrant this extra bit of complexity and size trade off. Computers are much faster at allocating/deallocating memory than humans are at typing :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point, I've removed the field in e7a4824 😊

@ghost
Copy link

ghost commented Sep 11, 2020

Hello @msftbot[bot]!

Because this pull request has the auto merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost
Copy link

ghost commented Sep 11, 2020

This PR has been marked as "needs attention 👋" and awaiting a response from the team.

Copy link

@stevenbrix stevenbrix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good!

The only thing I would suggest is to remove the ValidateProperty method. There is already the Validator class.

[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some bug in GitHub is causing me to not be able to comment on this further down. But basically, I don't feel like a ValidateProperty method should be required. Can ObservableValidator override the SetProperty method and call ValidateProperty itself?

[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, at the end of the day - I think this ok. When a user opens up a form, we don't want a bunch of errors for fields they haven't entered anything into yet

[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fold ValidateProperty into the SetProperty method? Those should always go hand-in-hand.

@ghost
Copy link

ghost commented Sep 24, 2020

This PR has been marked as "needs attention 👋" and awaiting a response from the team.

@Sergio0694 Sergio0694 merged commit ce296f9 into CommunityToolkit:master Sep 24, 2020
@Sergio0694
Copy link
Member Author

And part 1 is done! Thanks @stevenbrix for the help! 🎉🎉🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto merge ⚡ feature 💡 feature request 📬 A request for new changes to improve functionality improvements ✨ needs attention 👋 .NET Components which are .NET based (non UWP specific)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature] Microsoft.Toolkit.Mvvm package (Preview 5)
4 participants