Search code examples
c#wpfreactiveuireactivex

Command throws exception when duplicate values are received by Interaction.Handle


I have the following method implemented in my view-model

private async Task OpenAudioFileImpl ()
{
    await OpenFileInteraction.Handle(FileTypes.Audio)
        .Where(fp => !string.IsNullOrWhiteSpace(fp))
        .Where(fp => fp != AudioFilePath)
        .Where(fp => File.Exists(fp))
        .Log(this, $"New audio file path selected")
        .Select(x => AudioFilePath = x);
}

It is called via this command: OpenAudioFile = ReactiveCommand.CreateFromTask(async _ => await OpenAudioFileImpl());

The registered handler in my view opens a Microsoft.Win32.OpenFileDialog so I can select the file I want.

My goal is to only have the value of AudioFilePath update when a new file is selected and that file actually exists. When I click the button, the dialog opens properly. As long as I select a different file every time, I get no errors and AudioFilePath updates properly. However, if I select the same file again or cancel the dialog, the command throws an exception. I did some console outputs and am pretty sure that the issue is in this method, I just can't figure out what it is.

Being that its the ReactiveCommand that's throwing the error, the only way that I know to deal with it is:

OpenAudioFile.ThrownExceptions.Subscribe(ex =>
{
    this.Log().Error("OpenAudioFile failed to execute.", ex);
});

and the output I get is

Exception thrown: 'System.InvalidOperationException' in System.Reactive.dll
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
MainWindowViewModel: OpenAudioFile failed to execute.

which doesn't give me much information.


Solution

  • The reason you're getting an exception in those cases is because you're using async await. The behavior of awaiting an observable is to return the last element (before the OnCompleted), or throw the OnError that was observed. However, if the sequence contains no elements, you'll get an InvalidOperationException. So those Where statements are filtering out the only emitted element.

    I prefer to avoid asnyc/await as much as possible when working with Rx. But if you want to stick with async/await, you'll just have to modify your logic a bit. For example, maybe removing the Where statements and doing a conditional assignment in the Select and validating elsewhere. Not sure of your overall setup, so maybe you can think of something better.