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

TaskResultConverter causes an InvalidCastException (can't cast COM object to T) #4505

Closed
1 task done
Sergio0694 opened this issue Mar 10, 2022 · 7 comments · Fixed by #4790
Closed
1 task done

TaskResultConverter causes an InvalidCastException (can't cast COM object to T) #4505

Sergio0694 opened this issue Mar 10, 2022 · 7 comments · Fixed by #4790
Assignees
Labels
bug 🐛 An unexpected issue that highlights incorrect behavior Completed 🔥 helpers ✋
Milestone

Comments

@Sergio0694
Copy link
Member

Sergio0694 commented Mar 10, 2022

Describe the bug

The TaskResultConverter returns DependencyProperty.UnsetValue in the fallback path.
This causes invalid cast exceptions downstream when trying to convert the object to some type.
The converter should just return null if no value is available.

Regression

No, always been like this.

Reproducible in sample app?

  • This bug can be reproduced in the sample app.

Steps to reproduce

This can be reproed in the MVVM Toolkit sample app.
Just open the "Async relay command" page and observe the crash.

Expected behavior

The converter should return null.

Screenshots

image

Help us help you

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

@Sergio0694 Sergio0694 added the bug 🐛 An unexpected issue that highlights incorrect behavior label Mar 10, 2022
@Sergio0694 Sergio0694 self-assigned this Mar 10, 2022
@michael-hawker michael-hawker added this to the 7.1.3 milestone Aug 9, 2022
@michael-hawker michael-hawker removed their assignment Aug 29, 2022
@michael-hawker michael-hawker added question ❔ Issues or PR require more information need more info 📌 labels Aug 29, 2022
@michael-hawker
Copy link
Member

@Sergio0694 this was done in #3960 to fix #3311 as the docs guidance is to return UnsetValue instead of null.

@Arlodotexe Arlodotexe self-assigned this Sep 8, 2022
@Arlodotexe Arlodotexe moved this to In Progress in Windows Community Toolkit Sep 8, 2022
@Arlodotexe
Copy link
Member

Arlodotexe commented Sep 9, 2022

More detailed docs on this issue: Exceptions from converters.

@Sergio0694 Do you have an updated link to a working repro? I'd like to take a look.

@Sergio0694
Copy link
Member Author

@Arlodotexe It's no longer crashing in the sample app because I used a temporary converter as a workaround, see CommunityToolkit/MVVM-Samples@0138b7a. If you remove it you can then repro the crash there as well. Imho we should just change this back to returning null. This is not the value being read from a dependency property, it's a value being converted. It doesn't make sense to me to return UnsetValue here.

@michael-hawker
Copy link
Member

@Sergio0694 I think what the docs and we're trying to say is the binding should be this:

<Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, FallbackValue='Task not set', Mode=OneWay}" />

DependencyProperty.UnsetValue is a sentinel value that has special meaning in the dependency property system, and bindings that are passed this value will use FallbackValue.

As passing UnsetValue should have the XAML engine use the FallbackValue instead. Though it's unclear if this is going to refer to old-style Binding vs. x:Bind as x:Bind only says "Specifies a value to display when the source or path cannot be resolved.".

@Sergio0694
Copy link
Member Author

Wouldn't that apply to an actual dependency property though? In this case it's just a converter. It's receiving a value that has no result, so it's giving you back null, which is also consistent with what the extension does. I'm not sure I understand why it should return DependencyProperty.UnsetValue in this context 🤔

I've always treated the fallback path to be someting to use if the actual path is not valid, eg. if you have a complex path and some item along the way is null, or if you're using reflection bindings and you pass an invalid binding path.

@michael-hawker
Copy link
Member

Hmm, was just looking at our existing tests here:

[TestCategory("Converters")]
[UITestMethod]
public void Test_TaskResultConverter_Instance_UnsetValue()
{
var converter = new TaskResultConverter();
Assert.AreEqual(DependencyProperty.UnsetValue, converter.Convert(null, null, null, null));
Assert.AreEqual(DependencyProperty.UnsetValue, converter.Convert("Hello world", null, null, null));
}
[TestCategory("Converters")]
[UITestMethod]
public void Test_TaskResultConverter_Instance_Null()
{
var converter = new TaskResultConverter();
var cts = new CancellationTokenSource();
cts.Cancel();
Assert.AreEqual(null, converter.Convert(Task.FromCanceled(cts.Token), null, null, null));
Assert.AreEqual(null, converter.Convert(Task.FromException(new Exception()), null, null, null));
Assert.AreEqual(null, converter.Convert(Task.CompletedTask, null, null, null));
TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
Assert.AreEqual(null, converter.Convert(tcs1.Task, null, null, null));
TaskCompletionSource<string> tcs2 = new TaskCompletionSource<string>();
Assert.AreEqual(null, converter.Convert(tcs2.Task, null, null, null));
}

If any task is bound, it should return null if there is no result, so I'm confused now. Let me pull down the MVVM Sample app and try and get a repro there to better understand what's happening.

@michael-hawker
Copy link
Member

I tested this out directly in the MVVM Sample App and I understand now where @Sergio0694 and I were disconnecting. The MVVM Toolkit's AsyncRelayCommand doesn't set the ExecutionTask's value until it is actually executing (i.e. the default isn't an empty Task it's null).

This means the binding in the app was pointing to a null instance and not a Task and passing this null instead of an expected Task to the converter (as it was originally written). So, on the WCT side, all of our existing tests were either already passing a Task, or not actually trying to convert the result of the converter to the expected target type (like an actual x:Bind does). When I tweaked the tests, I got the same result due to the returning of the UnsetValue. So, at least on the same page here!

Basically, regardless of if we have a Task or not the binding that the converter is attached to needs something that mimics the target type of the property being bound. I think the key missing part of our comment on the converter here:

The methods in this converter will safely return if the input task is still running, or if it has faulted or has been canceled.

This doesn't define the behavior if the input Task is missing. When we also need to return the default value, if the task isn't even set yet.

@ghost ghost added the In-PR 🚀 label Oct 20, 2022
michael-hawker added a commit to michael-hawker/UWPCommunityToolkit that referenced this issue Oct 20, 2022
…fault value in case task is not set or has not completed.

Updated description to include unset Task scenario (i.e. null being passed in)
Updated to include proper default value return for Value types
Included value passthru for non-Task values
michael-hawker added a commit to michael-hawker/UWPCommunityToolkit that referenced this issue Oct 20, 2022
…fault value in case task is not set or has not completed.

Updated description to include unset Task scenario (i.e. null being passed in)
Updated to include proper default value return for Value types
Included value passthru for non-Task values
Repository owner moved this from In Progress to Done in Windows Community Toolkit Oct 21, 2022
@ghost ghost added Completed 🔥 and removed In-PR 🚀 labels Oct 21, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 20, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug 🐛 An unexpected issue that highlights incorrect behavior Completed 🔥 helpers ✋
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants