{"action":"created","issue":{"url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/5","labels_url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/5/labels{/name}","comments_url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/5/comments","events_url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/5/events","html_url":"https://github.com/runceel/ReactiveProperty/issues/5","id":98360366,"number":5,"title":"Support for more complex async ReactiveCommands","user":{"login":"BADF00D","id":2852160,"avatar_url":"https://avatars.githubusercontent.com/u/2852160?v=3","gravatar_id":"","url":"https://api.github.com/users/BADF00D","html_url":"https://github.com/BADF00D","followers_url":"https://api.github.com/users/BADF00D/followers","following_url":"https://api.github.com/users/BADF00D/following{/other_user}","gists_url":"https://api.github.com/users/BADF00D/gists{/gist_id}","starred_url":"https://api.github.com/users/BADF00D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BADF00D/subscriptions","organizations_url":"https://api.github.com/users/BADF00D/orgs","repos_url":"https://api.github.com/users/BADF00D/repos","events_url":"https://api.github.com/users/BADF00D/events{/privacy}","received_events_url":"https://api.github.com/users/BADF00D/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":9,"created_at":"2015-07-31T10:17:59Z","updated_at":"2015-08-06T13:38:48Z","closed_at":null,"body":"Hello,\r\nI had a problem for which I couldn't find a solution in your library. I want to perform an async task in my demo app.\r\nThe view-model: \r\n```csharp\r\npublic class ViewModel {\r\n\tpublic ReactiveProperty<string> FamilyName { get; private set; }\r\n\tpublic ReactiveCommand StoreUserCommand { get; private set; }\r\n\tpublic ReactiveProperty<string> Status { get; private;}\r\n}\r\n```\r\nStoring a user takes a lot of time, so I want to perform this command asynchronous. So my ctor looks like this:\r\n\r\n```csharp\r\npublic ViewModel() {\r\n\tFamilyName = new ReactiveProperty<string>();\r\n\tStatus = new ReactiveProperty<string>();\r\n\t\r\n\tStoreUserCommand = FamilyName\r\n\t\t\t.Select(s => !string.IsNullOrWhiteSpace(s) && s.Length > 5)\r\n\t\t\t.ToReactiveCommand();\r\n\tStoreUserCommand.Subscribe(async _=> {\r\n\t\tawait Task.Delay(1000);//storing user take some time\r\n\t\tMessageBox.Show(\"Done\");\r\n\t});\r\n}\r\n```\r\nThis solution makes the StoreUserCommand perform async, but it does not disable the button I bound it to. So I came up with this solution to initialize the command:\r\n```csharp\r\nvar isExceuting = new ReplaySubject<bool>(1);\r\nisExceuting.OnNext(false);\r\nStoreUserCommand = FamilyName\r\n\t.Select(s => !string.IsNullOrWhiteSpace(s) && s.Length > 5)\r\n\t.CombineLatest(isExceuting, (ce, ie) => ce && !ie)\r\n\t.ToReactiveCommand();\r\n\r\nStoreUserCommand.Subscribe(async _ => {\r\n\tisExceuting.OnNext(true);\r\n\tawait Task.Delay(1000);//storing user take some time\r\n\tisExceuting.OnNext(false);\r\n\tMessageBox.Show(\"Done\");\r\n});\r\n```\r\nAs you can see, I have to to a lot of work, to simply do something async and prevent the user from doing this in parallel.\r\nNow I have another problem. Some of the commands I use, have some preparation and wrap-up task that have to be done in the GUIs thread. An example: The command writes log messages that are visible in the GUI. These have to be cleared, before the new run. \r\nI don't wanted to expand the last result above. On the one hand, there is lots of code that can be reused in similar situation, on the other hand there is already to much fluff code. It is hard to read. I simply wanted something like this:\r\n```csharp\r\nStoreUserCommand = FamilyName\r\n\t.Select(s => !string.IsNullOrWhiteSpace(s) && s.Length > 5)\r\n\t.DoingAsync(() => {\r\n\t\tThread.Sleep(1000);//storing a used takes a long time\r\n\t\tMessageBox.Show(\"Done\");\r\n\t})\r\n\t.Prepare(() => Status.Value = \"Started\")//done in GUI thread\r\n\t.WrapUp(_ => Status.Value = \"End\") //done in GUI thread\r\n\t.ToAsyncReactiveCommand();\r\n```\r\n\r\nSo I implemented something that used the builder pattern and some extension methods:\r\n```csharp\r\npublic class ReactiveCommandProperties<T> {\r\n\tprivate readonly IObservable<bool> _can_excecute;\r\n\tprivate readonly Func<T> _action_to_perform_async;\r\n\tprivate readonly IList<Action> _preparation_actions = new List<Action>();\r\n\tprivate readonly IList<Action<T>> _wrapup_actions = new List<Action<T>>();\r\n\tprivate bool _allow_concurent_runs;\r\n\r\n\tinternal ReactiveCommandProperties(IObservable<bool> can_excecute, Func<T> action_to_perform_async) {\r\n\t\t_can_excecute = can_excecute;\r\n\t\t_action_to_perform_async = action_to_perform_async;\r\n\t}\r\n\r\n\tinternal ReactiveCommandProperties<T> AddPreparation(Action preparation) {\r\n\t\t_preparation_actions.Add(preparation);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tinternal ReactiveCommandProperties<T> AddWrapUp(Action<T> wrapup) {\r\n\t\t_wrapup_actions.Add(wrapup);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tinternal ReactiveCommand Build(out IDisposable subscription) {\r\n\t\tvar is_executing = new ReplaySubject<bool>(1);\r\n\t\tis_executing.OnNext(false);\r\n\t\tvar command = _can_excecute.CombineLatest(is_executing, (ce, ie) => ce && !ie)\r\n\t\t\t.ToReactiveCommand();\r\n\r\n\t\tvar preparations = _preparation_actions.ToArray();\r\n\t\tvar wrapups = _wrapup_actions.ToArray();\r\n\t\tvar block_concurent_calls = !_allow_concurent_runs;\r\n\r\n\t\tsubscription = command.Subscribe(async _ => {\r\n\t\t\tif (block_concurent_calls)\r\n\t\t\t\tis_executing.OnNext(true);\r\n\t\t\t\t\r\n\t\t\tforeach (var action in preparations) {\r\n\t\t\t\taction();\r\n\t\t\t}\r\n\t\t\t\t\r\n\t\t\tvar task = Task.Factory.StartNew(_action_to_perform_async);\r\n\t\t\tawait task;\r\n\r\n\t\t\tforeach (var wrapup in wrapups) {\r\n\t\t\t\twrapup(task.Result);\r\n\t\t\t}\r\n\r\n\t\t\tif(block_concurent_calls)\r\n\t\t\t\tis_executing.OnNext(false);\r\n\t\t});\r\n\r\n\t\treturn command;\r\n\t}\r\n\r\n\tinternal ReactiveCommandProperties<T> AllowConcurentRuns() {\r\n\t\t_allow_concurent_runs = true;\r\n\r\n\t\treturn this;\r\n\t}  \r\n}\r\n\r\npublic static class ReactiveCommandExtension {\r\n\tpublic static ReactiveCommandProperties<T> DoingAsync<T>(this IObservable<bool> canExecute, Func<T> action_to_perform_async) {\r\n\t\treturn new ReactiveCommandProperties<T>(canExecute, action_to_perform_async);\r\n\t}\r\n\r\n\tpublic static ReactiveCommandProperties<Unit> DoingAsync(this IObservable<bool> canExecute, Action action_to_perform_async) {\r\n\t\treturn new ReactiveCommandProperties<Unit>(canExecute, ()=>\r\n\t\t{\r\n\t\t\taction_to_perform_async();\r\n\t\t\treturn Unit.Default;\r\n\t\t});\r\n\t}\r\n\r\n\tpublic static ReactiveCommandProperties<T> AllowConcurentCalls<T>(this ReactiveCommandProperties<T> properties) {\r\n\t\treturn properties.AllowConcurentRuns();\r\n\t} \r\n\r\n\tpublic static ReactiveCommandProperties<T> Prepare<T>(this ReactiveCommandProperties<T> properties, Action prepare) {\r\n\t\treturn properties.AddPreparation(prepare);\r\n\t}\r\n\r\n\tpublic static ReactiveCommandProperties<T> WrapUp<T>(this ReactiveCommandProperties<T> properties, Action<T> wrapup) {\r\n\t\treturn properties.AddWrapUp(wrapup);\r\n\t}\r\n\r\n\tpublic static ReactiveCommand ToAsyncReactiveCommand<T>(this ReactiveCommandProperties<T> properties) {\r\n\t\tIDisposable disposable;\r\n\t\treturn properties.Build(out disposable);\r\n\t}\r\n\tpublic static ReactiveCommand ToAsyncReactiveCommand<T>(this ReactiveCommandProperties<T> properties, out IDisposable subscription) {\r\n\t\treturn properties.Build(out subscription);\r\n\t}\r\n}\r\n```\r\n\r\nThis solution support:\r\n* Preventing the user from calling command multiple times by changing CanExcecute to false.\r\n* But optional allow used to call command multiple times concurrently.\r\n* Optionally adding Preparation tasks in GUI-thread.\r\n* Optionally adding Wrap-up tasks in GUI-thread.\r\n* An optional result from background task.\r\n* Separates WHAT is done from HOW its done.\r\n* Exposing internal created subscription to let consumer handle disposing, if he/she wish to.\r\n\r\nThe only thing that is not supported, is a custom scheduler."},"comment":{"url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/comments/128368198","html_url":"https://github.com/runceel/ReactiveProperty/issues/5#issuecomment-128368198","issue_url":"https://api.github.com/repos/runceel/ReactiveProperty/issues/5","id":128368198,"user":{"login":"xin9le","id":4776688,"avatar_url":"https://avatars.githubusercontent.com/u/4776688?v=3","gravatar_id":"","url":"https://api.github.com/users/xin9le","html_url":"https://github.com/xin9le","followers_url":"https://api.github.com/users/xin9le/followers","following_url":"https://api.github.com/users/xin9le/following{/other_user}","gists_url":"https://api.github.com/users/xin9le/gists{/gist_id}","starred_url":"https://api.github.com/users/xin9le/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xin9le/subscriptions","organizations_url":"https://api.github.com/users/xin9le/orgs","repos_url":"https://api.github.com/users/xin9le/repos","events_url":"https://api.github.com/users/xin9le/events{/privacy}","received_events_url":"https://api.github.com/users/xin9le/received_events","type":"User","site_admin":false},"created_at":"2015-08-06T13:38:48Z","updated_at":"2015-08-06T13:38:48Z","body":"Thank you for using in/on your product :+1:\r\n\r\nIts issue is by design, because we built some validation functions on `INotifyDataErrorInfo` interface. Therefore `ObserveErrorChanged` method returns `IObservable<IEnumerable>`.\r\n\r\nWe considered string is used as error content frequently, so `SetValidateNotifyError` method provides string overload."}}