I have a simple DelegateCommand class that looks like this:
public class DelegateCommand<T> : System.Windows.Input.ICommand where T : class
{
public event EventHandler CanExecuteChanged;
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public DelegateCommand(Action<T> execute) : this(execute, null)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
this._execute = execute;
this._canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (this._canExecute == null)
return true;
return this._canExecute((T)parameter);
}
public void Execute(object parameter)
{
this._execute((T)parameter);
}
public void RaiseCanExecuteChanged()
{
this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
I am using GalaSoft.MvvmLight for validation and normally I would just do something like this in the View constructor:
this.MyCommand = new DelegateCommand<object>(o => {
//Do execute stuff
}, o =>
{
//Do CanExecute stuff
var validateResult = this.Validator.ValidateAll();
return validateResult.IsValid;
});
public DelegateCommand<object> MyCommand { get; }
This all works great when I have a simple validation check like:
this.Validator.AddRequiredRule(() => this.SomeProperty, "You must select.......");
but now I need a validation method that executes a long running task (in my case a WebService call) so when I want to do somthing like this:
this.Validator.AddAsyncRule(async () =>
{
//Long running webservice call....
return RuleResult.Assert(true, "Some message");
});
and therefore declare the command like this:
this.MyCommand = new DelegateCommand<object>(o => {
//Do execute stuff
}, async o =>
{
//Do CanExecute ASYNC stuff
var validateResult = await this.Validator.ValidateAllAsync();
return validateResult.IsValid;
});
I'm in a bit of a pickle because the standard ICommand implementation doesn't appear to deal with async scenarios.
Without too much thought it seems that you could potentially re-write the DelegateCommand class to support such functionality but I have looked at the way that Prism deals with this https://prismlibrary.github.io/docs/commanding.html, However it seems that they also DO NOT support async CanExecute methods.
So, is there a way around this problem? Or is there something fundamentally broken in trying to run an Async method from CanExecute using ICommand?
Delegatecommand is satisfying the ICommand interface. You can't just change the signature of stuff and it'll still work. It also needs to do it's thing on the UI thread so you can't thread it.
Bear in mind that all commands in a view will have canexecute checked whenever there is a user interaction. Even if you could make the predicate async then it could well be hit numerous times with performance implications.
The thing to do in this case is to make CanExecute quickly return the value of a bool.
Encapsulate your code elsewhere, say in a Task. Call that code asynchronously and return the result to the bool. Then raise canexecutechanged on your delegatecommand so the value of that bool is read.
You probably also want to set that bool false initially and check it's value inside your Action. That way the user can't click a button bound to it repeatedly.
Depending on the amount of input going on and how many of these things you might have in a view you might want to consider taking steps so the expensive process is only run whilst the data has changed since last run.
Since you already have a canexecute predicate you could alter your delegatecommand implementation to add this guard bool and make it public.
Note
When complicated expensive validation is necessary there are two other approaches often used.
1) Validate properties as they're entered. This spreads out the validation so it happens as the user fills fields and is probably done before he's ready to click that submit button.
2) Let the user hit submit but do any (further expensive) checks at that time and report validation failures then. This guarantees there's only the one check when they hit submit.