type SearchCardCode from ui and the observable not work after VerifyCommand throw error, why??
public class ViewModel : ReactiveObject
{
private string _SearchCardCode;
public string SearchCardCode
{
get { return _SearchCardCode; }
set
{
this.RaiseAndSetIfChanged(ref _SearchCardCode, value);
}
}
private bool _IsValid;
public bool IsValid
{
get { return _IsValid; }
set { this.RaiseAndSetIfChanged(ref _IsValid, value); }
}
private string _ErrorMessage;
public string ErrorMessage
{
get { return _ErrorMessage; }
set { this.RaiseAndSetIfChanged(ref _ErrorMessage, value); }
}
public ReactiveCommand<Unit, bool> VerifyCommand { get; set; }
ObservableAsPropertyHelper<bool> _IsLoading;
public bool IsLoading
{
get { return _IsLoading.Value; }
}
public ViewModel()
{
this.VerifyCommand = ReactiveCommand.CreateFromTask(async _ => await VerifyCommandHandler(this.SearchCardCode));
this.VerifyCommand.Subscribe(x =>
{
this.IsValid = x;
});
this.VerifyCommand.ThrownExceptions.Subscribe(x =>
{
this.ErrorMessage = x.Message;
});
this.VerifyCommand.IsExecuting.ToProperty(this, x => x.IsLoading, out this._IsLoading, false);
//type SearchCardCode from ui and the observable not work after VerifyCommand throw error, why??
this.WhenAnyValue(x => x.SearchCardCode)
.DistinctUntilChanged()
.Do(x => Reset())
.Where(x => !string.IsNullOrWhiteSpace(x) && x.Length >= 16)
.Select(_ => Unit.Default)
.InvokeCommand(this.VerifyCommand);
}
private void Reset()
{
this.IsValid = false;
this.ErrorMessage = string.Empty;
}
private async Task<bool> VerifyCommandHandler(string searchCardCode)
{
if (searchCardCode.EndsWith("0"))
{
return false;
}
else if (searchCardCode.EndsWith("1"))
{
return true;
}
else
{
throw new Exception("Error");
}
}
}
So it looks like Exceptions from ReactiveCommand.Execute are propagated to the observable sequence the execute is subscribed on
For purpose of explanation let's just assume your code looks like this
this.WhenAnyValue(x => x.SearchCardCode)
.DistinctUntilChanged()
.Do(x => Reset())
.Where(x => !string.IsNullOrWhiteSpace(x) && x.Length >= 16)
.Select(_ => Unit.Default)
SelectMany(data => VerifyCommand.Execute(Unit.Default))
.Subscribe();
InvokeCommand just inserts some fancy stuff before that to ensure "CanExecute" is true
https://github.com/reactiveui/ReactiveUI/blob/master/src/ReactiveUI/ReactiveCommand.cs#L1064
If "Execute" causes an exception then it propagates that exception to the Observable Sequence and once an Observable Sequence has gone into the "error" state it is considered done and will no longer produce values.
There's lots of different ways to handle this with Reactive
http://www.introtorx.com/Content/v1.0.10621.0/11_AdvancedErrorHandling.html
If you were to take what I have above and rewrite it like this
this.WhenAnyValue(x => x.SearchCardCode)
.DistinctUntilChanged()
.Do(x => Reset())
.Where(x => !string.IsNullOrWhiteSpace(x) && x.Length >= 16)
.Select(_ => Unit.Default)
SelectMany(data => VerifyCommand.Execute(Unit.Default).Catch(Observable.Return(false)))
.Subscribe();
Then your code would keep executing
Though in your case I would just try catch the VerifyHandler method and handle the exception there so they don't propagate up to the Observable
Also RxUI 7 has some super cool parameter propagating so you can simplify your code a little bit by doing this
this.VerifyCommand = ReactiveCommand.CreateFromTask<string, bool>(async data => await VerifyCommandHandler(data));
and then your invoke just looks like this
this.WhenAnyValue(x => x.SearchCardCode)
.DistinctUntilChanged()
.Do(x => Reset())
.Where(x => !string.IsNullOrWhiteSpace(x) && x.Length >= 16)
.InvokeCommand(this.VerifyCommand);
Love your work @PureWeen - thankyou.
@PureWeen you said is very good, let me to deepen the understanding
----Aware of before I see the wrong source code version after i see the "https://github.com/reactiveui/ReactiveUI/blob/master/src/ReactiveUI/ReactiveCommand.cs#L1064" source code
I see the wrong source code version following :
public static IDisposable InvokeCommand<T, TResult>(this IObservable<T> This, ReactiveCommand<T, TResult> command)
{
return This.Throttle(x => command.CanExecute.Where(b => b))
.Select(x => command.Execute(x).Catch(Observable.Empty<TResult>()))
.Switch()
.Subscribe();
}
The chunk of code I was referring to is this from the master branch
public static IDisposable InvokeCommand<T, TResult>(this IObservable<T> This, ReactiveCommand<T, TResult> command)
{
return This
.WithLatestFrom(command.CanExecute, (value, canExecute) => InvokeCommandInfo.From(command, canExecute, value))
.Where(ii => ii.CanExecute)
.SelectMany(ii => command.Execute(ii.Value))
.Subscribe();
}
That is really strange behavior of ReactiveCommand. In case of creating ReactiveCommand from observable we cannot rely on .ThrownExceptions channel.
So we have to options:
var cmd = ReactiveCommand.CreateFromTask(async param => await _someService.SomeDataChannel(param))
public static IDisposable InvokeCommandSafe<T, TResult>(this IObservable<T> source, ReactiveCommand<T, TResult> command)
{
return source
.SelectMany(data => command.Execute(data).Catch(Observable.Return(default(TResult))))
.Subscribe();
}
Are there any hidden problems with this approach? If no why ReactiveUI does not use it by default?
@Allesad you've commented on an old issue, but that's fine. This was a regression recently introduced with RxUI 7. It has already been fixed (see #1248), and will be included in the next release. If you like, you can try out the pre-release using the feeds mentioned here.
@kentcb Oh, I didn't know about the fix - Google just lead me to this discussion :) But this information may be helpful for other wanderers from Google search.
For now I think I'll stick with my temp extension and will replace it after next release.
Thank you!