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

Remove DelegateCommand.FromAsyncHanlder #785

Closed
brianlagunas opened this Issue Oct 3, 2016 · 88 comments

Comments

Projects
None yet
@brianlagunas
Member

brianlagunas commented Oct 3, 2016

The current implementation of DelegateCommand is broken because of the newest FromAsyncHandler feature that was added by Microsoft prior to Prism being open sourced. Any exception thrown in a command is swallowed. For example, this test fails:

        [Fact]
        public void Test_should_fail_because_of_thrown_exception()
        {
            Assert.Throws<Exception>(() =>
            {
                new DelegateCommand(() => { throw new Exception(); }).Execute();
            });
        }

This is because of the way the DelegateCommand is implemented to support an async scenario. The problem is that ICommand is not called async by any of the XAML frameworks, and having an async command provides no value unless you are calling your command in code (which should be rare).

  • Step one is marking it Obsolete, and releasing an update to NuGet.
  • Step two is fixing the implementation by removing async command stuff, and to refactor DelegateCommand.
  • Step three is push new version 6.3 to nugget mid next year.

This will be a massive breaking change to anyone using FromAsyncHandler, but it is necessary.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 3, 2016

Member

Please review this branch to make sure I didn't miss anything. I tried to simplify the DelegateCommand without breaking anything (except for the async stuff)

https://github.com/PrismLibrary/Prism/tree/DelegateCommand-Improvements/Source/Prism/Commands

Member

brianlagunas commented Oct 3, 2016

Please review this branch to make sure I didn't miss anything. I tried to simplify the DelegateCommand without breaking anything (except for the async stuff)

https://github.com/PrismLibrary/Prism/tree/DelegateCommand-Improvements/Source/Prism/Commands

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

https://github.com/PrismLibrary/Prism/blob/DelegateCommand-Improvements/Source/Prism/Commands/DelegateCommandBase.cs#L27:

protected readonly Func<object, Task> _executeMethod;

Tasks are not needed anymore; _executeMethod should be Action<object>.

Contributor

dvorn commented Oct 5, 2016

https://github.com/PrismLibrary/Prism/blob/DelegateCommand-Improvements/Source/Prism/Commands/DelegateCommandBase.cs#L27:

protected readonly Func<object, Task> _executeMethod;

Tasks are not needed anymore; _executeMethod should be Action<object>.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

This implementation does not provide good experience if someone wants to override the execute method (e.g., to add logging). I would expect to have public virtual void Execute(); in DelegateCommand and public virtual void Execute(T parameter); in DelegateCommand<T>. Those method should be called when a command is invoked by XAML framework via ICommand.

Contributor

dvorn commented Oct 5, 2016

This implementation does not provide good experience if someone wants to override the execute method (e.g., to add logging). I would expect to have public virtual void Execute(); in DelegateCommand and public virtual void Execute(T parameter); in DelegateCommand<T>. Those method should be called when a command is invoked by XAML framework via ICommand.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

@dvorn good catch :)

Just so you know, Execute(T parmater) is only used when you call the command in code against the DelegateCommand type. When XAML invokes the command, it will always use ICommand.Execute(object parameter). The framework will never directly call the Execute(T parameter) method. That's the reason I removed those methods. I wanted to create consistency in the API. I could add tem back it's no big deal. If I do that, if someone declares DelegateCommand MyCommand, and called MyCommand.Execute() somewhere in code, then changes the command type to ICommand (ICommand MyCommand), it will be broken.

Member

brianlagunas commented Oct 5, 2016

@dvorn good catch :)

Just so you know, Execute(T parmater) is only used when you call the command in code against the DelegateCommand type. When XAML invokes the command, it will always use ICommand.Execute(object parameter). The framework will never directly call the Execute(T parameter) method. That's the reason I removed those methods. I wanted to create consistency in the API. I could add tem back it's no big deal. If I do that, if someone declares DelegateCommand MyCommand, and called MyCommand.Execute() somewhere in code, then changes the command type to ICommand (ICommand MyCommand), it will be broken.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

Just so you know, Execute(T parmater) is only used when you call the command in code against the DelegateCommand type.

This is only in Prism 5 incorrect implementation.

One of possible implementations:

void ICommand.Execute(object parameter)
{
    this.Execute((T)parameter);
}

public virtual void Execute(T parameter)
{
    base.Execute(parameter);
}
Contributor

dvorn commented Oct 5, 2016

Just so you know, Execute(T parmater) is only used when you call the command in code against the DelegateCommand type.

This is only in Prism 5 incorrect implementation.

One of possible implementations:

void ICommand.Execute(object parameter)
{
    this.Execute((T)parameter);
}

public virtual void Execute(T parameter)
{
    base.Execute(parameter);
}
@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

What would be the purpose of that? Regardless, the XAML framework is still only calling Execute(object param), while the developer would be calling DelegateCommand.Execute(T parameter). Then, base.Execute is still another ICommand implementation. I guess I'm not seeing the benefit.

Member

brianlagunas commented Oct 5, 2016

What would be the purpose of that? Regardless, the XAML framework is still only calling Execute(object param), while the developer would be calling DelegateCommand.Execute(T parameter). Then, base.Execute is still another ICommand implementation. I guess I'm not seeing the benefit.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

The benefit? It solves the problem. You override Execute(T) in the derived class, and both XAML and developer are executing the same method.

Contributor

dvorn commented Oct 5, 2016

The benefit? It solves the problem. You override Execute(T) in the derived class, and both XAML and developer are executing the same method.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

Suppose you have

class MyCommand<T> : DelegateCommand<T>
{
    public override Execute(T parameter)
    {
        base.Execute(parameter);
        // additional code
    }
}

XAML framework will call DelegateCommand.ICommand.Execute and then:
MyCommand.Execute
DelegateCommand.Execute(T)
DelegateCommandBase.Execute(object)
_executeMethod
// additional code

Contributor

dvorn commented Oct 5, 2016

Suppose you have

class MyCommand<T> : DelegateCommand<T>
{
    public override Execute(T parameter)
    {
        base.Execute(parameter);
        // additional code
    }
}

XAML framework will call DelegateCommand.ICommand.Execute and then:
MyCommand.Execute
DelegateCommand.Execute(T)
DelegateCommandBase.Execute(object)
_executeMethod
// additional code

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

Based on what is there now, there is only a single Execute method that is called by both XAML and code which is the same.

public virtual void Execute(object parameter = null)
{
    _executeMethod(parameter);
}

If you override this method in your custom command, it gives you the exact same behavior, minus the strongly type parameter. That is what I was meaning about the benefit of doing what you have vs. what's there. It is the strongly typed parameter that you want?

Member

brianlagunas commented Oct 5, 2016

Based on what is there now, there is only a single Execute method that is called by both XAML and code which is the same.

public virtual void Execute(object parameter = null)
{
    _executeMethod(parameter);
}

If you override this method in your custom command, it gives you the exact same behavior, minus the strongly type parameter. That is what I was meaning about the benefit of doing what you have vs. what's there. It is the strongly typed parameter that you want?

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

Exactly. Or the lack of parameter. In the present form it is doable but not obvious. This is what I called "not a good experience".

Contributor

dvorn commented Oct 5, 2016

Exactly. Or the lack of parameter. In the present form it is doable but not obvious. This is what I called "not a good experience".

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

I don't see a problem with adding the strongly typed parameter as you have it. As long as it behaves the exact same when XAML or code calls it.

Member

brianlagunas commented Oct 5, 2016

I don't see a problem with adding the strongly typed parameter as you have it. As long as it behaves the exact same when XAML or code calls it.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

By doing this, we now have a slightly confusing API. We have two overloads of Execute and CanExecute.

In the case of DelegateCommand<string>

Execute(object parameter)
Execute(string parameter)

In the case of DelegateCommand

Execute()
Execute(object parameter)

Not really sure this helps a whole lot. Maybe I am missing something.

Member

brianlagunas commented Oct 5, 2016

By doing this, we now have a slightly confusing API. We have two overloads of Execute and CanExecute.

In the case of DelegateCommand<string>

Execute(object parameter)
Execute(string parameter)

In the case of DelegateCommand

Execute()
Execute(object parameter)

Not really sure this helps a whole lot. Maybe I am missing something.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

Execute(T parameter) is just a helper method for strongly typed parameters when calling in code so you don't pass an invalid type. XAML doesn't care about this and only uses Execute(object parameter). If you override Execute(object parameter) you are not blocked from adding any custom logic in your derived command. I pushed an update to the branch. Check it out. I cleaned the API up and not show the duplicate Execute/CanExecute methods (made them protected).

Member

brianlagunas commented Oct 5, 2016

Execute(T parameter) is just a helper method for strongly typed parameters when calling in code so you don't pass an invalid type. XAML doesn't care about this and only uses Execute(object parameter). If you override Execute(object parameter) you are not blocked from adding any custom logic in your derived command. I pushed an update to the branch. Check it out. I cleaned the API up and not show the duplicate Execute/CanExecute methods (made them protected).

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

You are right. Moreover, there is a corner case of DelegateCommand<object>. Also, my sample assumes that DelegateCommand and DelegateCommand<T> both explicitly implement ICommand.Execute which I do not like.

Actually, this was "one of possible implementation" which was shorter to type. Real implementation could be

public class DelegateCommandBase : ICommand
{
    ICommand.Execute(object parameter)
    {
        OnExecute(parameter);
    }
    protected virtual void OnExecute(object parameter)
    {
        Doexecute(parameter);
    }
    protected void DoExecute(object parameter)
    {
        _executeMethod(parameter);
    }
}
public class DelegateCommand<T> : DelegateCommandBase
{
    protected override OnExecute(object parameter)
    {
        Execute((T) parameter);
    }
    public virtual Execute(T parameter)
    {
        DoExecute(parameter);
    }
}
public class MyCommand<T> : DelegateCommand<T>
{
    public override Execute(T parameter)
    {
        base.Execute(parameter);
        // extra stuff
    }
}
Contributor

dvorn commented Oct 5, 2016

You are right. Moreover, there is a corner case of DelegateCommand<object>. Also, my sample assumes that DelegateCommand and DelegateCommand<T> both explicitly implement ICommand.Execute which I do not like.

Actually, this was "one of possible implementation" which was shorter to type. Real implementation could be

public class DelegateCommandBase : ICommand
{
    ICommand.Execute(object parameter)
    {
        OnExecute(parameter);
    }
    protected virtual void OnExecute(object parameter)
    {
        Doexecute(parameter);
    }
    protected void DoExecute(object parameter)
    {
        _executeMethod(parameter);
    }
}
public class DelegateCommand<T> : DelegateCommandBase
{
    protected override OnExecute(object parameter)
    {
        Execute((T) parameter);
    }
    public virtual Execute(T parameter)
    {
        DoExecute(parameter);
    }
}
public class MyCommand<T> : DelegateCommand<T>
{
    public override Execute(T parameter)
    {
        base.Execute(parameter);
        // extra stuff
    }
}
@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

I pushed an update to the branch. Check it out.

You ended up in a variant they had in Prism 4. These commands are not overridable in a sense that you cannot override Execute(T parameter), and overriding Execute(object parameter) (especially for parameter-less command) in not obvious (poor experience).

Contributor

dvorn commented Oct 5, 2016

I pushed an update to the branch. Check it out.

You ended up in a variant they had in Prism 4. These commands are not overridable in a sense that you cannot override Execute(T parameter), and overriding Execute(object parameter) (especially for parameter-less command) in not obvious (poor experience).

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 5, 2016

Contributor

And I doubt that it will ever work: when you call base.Execute(parameter) from the DelegateCommand, will it execute the override of the Execute method in the derived class?

Contributor

dvorn commented Oct 5, 2016

And I doubt that it will ever work: when you call base.Execute(parameter) from the DelegateCommand, will it execute the override of the Execute method in the derived class?

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

Crap, you're right! I didn't think about that :)

Member

brianlagunas commented Oct 5, 2016

Crap, you're right! I didn't think about that :)

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

I didn't realize how ugly this DelegateCommand was :)

Member

brianlagunas commented Oct 5, 2016

I didn't realize how ugly this DelegateCommand was :)

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

You know... if we didn't care about that silly strongly typed parameter this would all be a non-issue. Is it really that important? I don't see any other command that implements it like that (MVVMLight, etc).

Member

brianlagunas commented Oct 5, 2016

You know... if we didn't care about that silly strongly typed parameter this would all be a non-issue. Is it really that important? I don't see any other command that implements it like that (MVVMLight, etc).

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

Also, as a side effect, when trying to override the Execute(T parameter) method using intellisense, a Visual Studio exception is raised complaining of duplicate methods; because of the Execute(object parameter) in the base class. I would argue this is a worse experience.

Member

brianlagunas commented Oct 5, 2016

Also, as a side effect, when trying to override the Execute(T parameter) method using intellisense, a Visual Studio exception is raised complaining of duplicate methods; because of the Execute(object parameter) in the base class. I would argue this is a worse experience.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 5, 2016

Member

And, if a developer overrides OnExecute, it will never fire when calling DelegateCommand.Execute in code. That will only invoke with a XAML invocation, leading to another poor/confusing experience.

Member

brianlagunas commented Oct 5, 2016

And, if a developer overrides OnExecute, it will never fire when calling DelegateCommand.Execute in code. That will only invoke with a XAML invocation, leading to another poor/confusing experience.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 6, 2016

Contributor

After some more thinking...

We already decided that async was a bad idea.

Now I think that the entire concept of overridable Execute method is conceptually wrong.

When you create a DelegateCommand you provide a delegate which contains the code that needs to be executed. Why didn't you put all you need there? The delegate command isn't something (a framework) that is executing its own code and providing you a hook for modifications. DelegateCommand does just that - executes your delegate.

In a class derived from the DelegateCommand you do not need two extension points - one in the base constructor parameters, and the other being overridable Execute method. If you have the two, a question arises how they should relate to each other.

Everything you would write in the overridden Execute method you could as well write in the constructor parameter delegate.

If you need some extra functionality common to all your commands, you can go something like this:

public class MyCommand<T> : DelegateCommand<T>
{
    private bool isRunning;
    public MyCommand(Action<T> action)
        : base((o) =>
            {
                 isRunning = true;
                 action(o);
                 isRunning = false;
            });
}

Thus, I suggest to remove "virtual" from the Execute method in DelegateCommandBase, essentially returning to Prism 4 variant.

Contributor

dvorn commented Oct 6, 2016

After some more thinking...

We already decided that async was a bad idea.

Now I think that the entire concept of overridable Execute method is conceptually wrong.

When you create a DelegateCommand you provide a delegate which contains the code that needs to be executed. Why didn't you put all you need there? The delegate command isn't something (a framework) that is executing its own code and providing you a hook for modifications. DelegateCommand does just that - executes your delegate.

In a class derived from the DelegateCommand you do not need two extension points - one in the base constructor parameters, and the other being overridable Execute method. If you have the two, a question arises how they should relate to each other.

Everything you would write in the overridden Execute method you could as well write in the constructor parameter delegate.

If you need some extra functionality common to all your commands, you can go something like this:

public class MyCommand<T> : DelegateCommand<T>
{
    private bool isRunning;
    public MyCommand(Action<T> action)
        : base((o) =>
            {
                 isRunning = true;
                 action(o);
                 isRunning = false;
            });
}

Thus, I suggest to remove "virtual" from the Execute method in DelegateCommandBase, essentially returning to Prism 4 variant.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 6, 2016

Contributor

Note also that developers (@nickkazepis) ended up in exactly this approach: #746 (comment)

Contributor

dvorn commented Oct 6, 2016

Note also that developers (@nickkazepis) ended up in exactly this approach: #746 (comment)

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 6, 2016

Contributor

Complete sample:

public class MyCommand<T> : DelegateCommand<T>
{
    private bool isRunning;
    public MyCommand(Func<T, Task> executeMethod, Func<T, bool> canExecuteMethod)
        : base(async (o) =>
            {
                 isRunning = true;
                 await executeMethod(o);
                 isRunning = false;
            },
            (o) => !isRunning && canExecuteMethod(o));
}
Contributor

dvorn commented Oct 6, 2016

Complete sample:

public class MyCommand<T> : DelegateCommand<T>
{
    private bool isRunning;
    public MyCommand(Func<T, Task> executeMethod, Func<T, bool> canExecuteMethod)
        : base(async (o) =>
            {
                 isRunning = true;
                 await executeMethod(o);
                 isRunning = false;
            },
            (o) => !isRunning && canExecuteMethod(o));
}
@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 18, 2016

Member

I am actually leaning towards sealing the DelegateCommand and DelegateCommand<T> classes. Thus, any custom command should derive from DelegateCommandBase. See the latest update.

Member

brianlagunas commented Oct 18, 2016

I am actually leaning towards sealing the DelegateCommand and DelegateCommand<T> classes. Thus, any custom command should derive from DelegateCommandBase. See the latest update.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 19, 2016

Contributor

@brianlagunas But DelegateCommandBase is not overridable now. Having DelegateCommand sealed will make creating custom commands more difficult than it could be.

Contributor

dvorn commented Oct 19, 2016

@brianlagunas But DelegateCommandBase is not overridable now. Having DelegateCommand sealed will make creating custom commands more difficult than it could be.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 19, 2016

Member

All other commands are sealed, and probably for the same reason. Creating a custom command that derives from DelegateCommandBase is not difficult, and it would prevent all the issues we are trying to avoid. You get your strongly typed Execute/CanExecute :)

Member

brianlagunas commented Oct 19, 2016

All other commands are sealed, and probably for the same reason. Creating a custom command that derives from DelegateCommandBase is not difficult, and it would prevent all the issues we are trying to avoid. You get your strongly typed Execute/CanExecute :)

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Oct 20, 2016

Contributor

But then if you want to create a custom command you'll need to repeat all the stuff that already is in DelegateCommand: casting of object parameters to T in constructors and, in particular, dealing with T which is a value type. It's doable but not a great experience.

Contributor

dvorn commented Oct 20, 2016

But then if you want to create a custom command you'll need to repeat all the stuff that already is in DelegateCommand: casting of object parameters to T in constructors and, in particular, dealing with T which is a value type. It's doable but not a great experience.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Oct 20, 2016

Member

We can't make everyone happy. You can choose between loosing your Execute<T> which is a bad experience (and hasn't been fully tested yet), or losing the ability to derive from DelegateCommand<T> (that every other command does) which is a bad experience. Either way, someone is getting a bad experience.

Member

brianlagunas commented Oct 20, 2016

We can't make everyone happy. You can choose between loosing your Execute<T> which is a bad experience (and hasn't been fully tested yet), or losing the ability to derive from DelegateCommand<T> (that every other command does) which is a bad experience. Either way, someone is getting a bad experience.

@feinstein

This comment has been minimized.

Show comment
Hide comment
@feinstein

feinstein Nov 13, 2016

Contributor

@dvorn all the issues you raised (multiple invocations, cancellation and etc), Stephen addressees them in his MVVM Commands blog post.

In this image you can see the AsyncCommand can bind Error Messages, Exceptions, Results, Loading Status, simplifying, encapsulating and reusing (all the magical words we devs love to hear and repeat, don't we? haha) a lot the work we should be doing in case we need async behavior in our commands:

ic716159

What's not on that image is the testability that wasn't comprimised at all since the AsyncCommand is just an Execute calling ExecuteAsync that returns a Taks and this Task can be used for testing, making your test cases target only ExecuteAsync and forget Execute.

I think many people in this thread have read all this already but I didn't see anyone pointing this out.

@brianlagunas My current project deals mainly with async services. AFAIK the Prism library is knows as the "Proven Patterns and Practices for predictable results" which means, as I understand it, proven patterns for software development in general and not a pure MVVM library....am I wrong?

So as I see it, if there are patterns that make our job dealing with async code more simple and secure, why should we not use it or share it?

I understand your points on DelegateCommand not being relevant as async...but should we just forget async entirely? Most new architectures, specially the ones dealing with HTTP services will be based on async, it provides the most responsive user experience and leverages the programmers ability to deal with concurrency.

Last time I checked async is being pushed by Microsoft mobile as the new way to do things to provide a better UX, and MVVM is the architecture of choice of all future Microsoft related applications, so having an async command is nothing but natural to me.

Yes there are several different use case scenarios, but I think we can customize most things to work nicely as the developer wants them, a lot of them can be just a change in behavior with a simple enum flag, and most use case scenarios are pretty well known. Even Stephen Cleary said his solution isn't going to be the best thing for everyone, bust still not even Prism can achieve this.

This is just my 2 cents on this matter. I intent to use Stephen's AsyncCommand pattern in my project because it will simplify it a lot, specially with all the bindings I need for showing loading, errors and results in the XAML UI, but I will be glad to find something similar on Prism on the coming future.

Contributor

feinstein commented Nov 13, 2016

@dvorn all the issues you raised (multiple invocations, cancellation and etc), Stephen addressees them in his MVVM Commands blog post.

In this image you can see the AsyncCommand can bind Error Messages, Exceptions, Results, Loading Status, simplifying, encapsulating and reusing (all the magical words we devs love to hear and repeat, don't we? haha) a lot the work we should be doing in case we need async behavior in our commands:

ic716159

What's not on that image is the testability that wasn't comprimised at all since the AsyncCommand is just an Execute calling ExecuteAsync that returns a Taks and this Task can be used for testing, making your test cases target only ExecuteAsync and forget Execute.

I think many people in this thread have read all this already but I didn't see anyone pointing this out.

@brianlagunas My current project deals mainly with async services. AFAIK the Prism library is knows as the "Proven Patterns and Practices for predictable results" which means, as I understand it, proven patterns for software development in general and not a pure MVVM library....am I wrong?

So as I see it, if there are patterns that make our job dealing with async code more simple and secure, why should we not use it or share it?

I understand your points on DelegateCommand not being relevant as async...but should we just forget async entirely? Most new architectures, specially the ones dealing with HTTP services will be based on async, it provides the most responsive user experience and leverages the programmers ability to deal with concurrency.

Last time I checked async is being pushed by Microsoft mobile as the new way to do things to provide a better UX, and MVVM is the architecture of choice of all future Microsoft related applications, so having an async command is nothing but natural to me.

Yes there are several different use case scenarios, but I think we can customize most things to work nicely as the developer wants them, a lot of them can be just a change in behavior with a simple enum flag, and most use case scenarios are pretty well known. Even Stephen Cleary said his solution isn't going to be the best thing for everyone, bust still not even Prism can achieve this.

This is just my 2 cents on this matter. I intent to use Stephen's AsyncCommand pattern in my project because it will simplify it a lot, specially with all the bindings I need for showing loading, errors and results in the XAML UI, but I will be glad to find something similar on Prism on the coming future.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 14, 2016

Contributor

@feinstein It is very characteristic what Mr. Cleary ended up with in that blog post:

The new UI will change the Go button to mean “start a new asynchronous operation and add it to the list of operations.” What this means is that the Go button is now actually synchronous.

Actually the positive value of Mr. Cleary's approach is observable (bindable) Task properties, and this is not the same as async command per se.

Contributor

dvorn commented Nov 14, 2016

@feinstein It is very characteristic what Mr. Cleary ended up with in that blog post:

The new UI will change the Go button to mean “start a new asynchronous operation and add it to the list of operations.” What this means is that the Go button is now actually synchronous.

Actually the positive value of Mr. Cleary's approach is observable (bindable) Task properties, and this is not the same as async command per se.

@feinstein

This comment has been minimized.

Show comment
Hide comment
@feinstein

feinstein Nov 14, 2016

Contributor

@dvorn yes, precisely, and observable (bindable) Task properties is the main goal I think we could achieve here.

I think AsyncDelegateCommand would be a fitting name for it since it is a regular synchronous command that receives a delegate to an async operation and starts it. Hence Async-Delegate Command.

Contributor

feinstein commented Nov 14, 2016

@dvorn yes, precisely, and observable (bindable) Task properties is the main goal I think we could achieve here.

I think AsyncDelegateCommand would be a fitting name for it since it is a regular synchronous command that receives a delegate to an async operation and starts it. Hence Async-Delegate Command.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 14, 2016

Member

Proven Patterns and Practices for predictable results

@feinstein this has yet to be proven. When the XAML platforms add native support for async commands, I will add it. Until then, you can simply add Cleary's approach to your application.

Member

brianlagunas commented Nov 14, 2016

Proven Patterns and Practices for predictable results

@feinstein this has yet to be proven. When the XAML platforms add native support for async commands, I will add it. Until then, you can simply add Cleary's approach to your application.

@feinstein

This comment has been minimized.

Show comment
Hide comment
@feinstein

feinstein Nov 14, 2016

Contributor

@brianlagunas OK, I understand your reasons, thanks for listening though.

Contributor

feinstein commented Nov 14, 2016

@brianlagunas OK, I understand your reasons, thanks for listening though.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 23, 2016

Member

I just checked in another approach. @dvorn check it out and let me know your thoughts.

Member

brianlagunas commented Nov 23, 2016

I just checked in another approach. @dvorn check it out and let me know your thoughts.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

@brianlagunas You removed a check for value types. This is going against the original design:

/// The constructor deliberately prevents the use of value types.
/// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
/// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.

My understanding is that will not work good in the following case: suppose you use something like ListBox.SelectedIndex as a parameter to the command. During initialization of XAML, CanExecute(null) will be issued and the view model will interpret that as CanExecute(0). Thus, the command will be enabled although no item was selected in the listbox. With Nullable the user would be able to distinguish between the absent value and the valid value of 0.

However, I may be wrong. I never used DelegateCommands with value types. ListBox.SelectedItem may be a bad example; it has a value of -1 to indicate absent selection. Maybe XAML calls CanExecute(null) anyway. Just do not know.

Contributor

dvorn commented Nov 23, 2016

@brianlagunas You removed a check for value types. This is going against the original design:

/// The constructor deliberately prevents the use of value types.
/// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
/// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.

My understanding is that will not work good in the following case: suppose you use something like ListBox.SelectedIndex as a parameter to the command. During initialization of XAML, CanExecute(null) will be issued and the view model will interpret that as CanExecute(0). Thus, the command will be enabled although no item was selected in the listbox. With Nullable the user would be able to distinguish between the absent value and the valid value of 0.

However, I may be wrong. I never used DelegateCommands with value types. ListBox.SelectedItem may be a bad example; it has a value of -1 to indicate absent selection. Maybe XAML calls CanExecute(null) anyway. Just do not know.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

@brianlagunas Oh, you removed the check for value type completely. This will crash

 : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))

when the parameter is null and T is e.g. int.

Contributor

dvorn commented Nov 23, 2016

@brianlagunas Oh, you removed the check for value type completely. This will crash

 : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))

when the parameter is null and T is e.g. int.

@pdinnissen

This comment has been minimized.

Show comment
Hide comment
@pdinnissen

pdinnissen Nov 23, 2016

Contributor

@dvorn if T is int then the parameter should never be null and if it is then wouldn't a crash be an expected behaviour as opposed to "hiding" it behind a default value type?

Also, should DelegateCommandBase still have the following code?:

        void ICommand.Execute(object parameter)
        {
            Execute(parameter);
        }

        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute(parameter);
        }

Since @brianlagunas and @dvorn mentioned that WPF will "ignore" any additional overrides function that add additional code (ie: logging like @dvor said).

Contributor

pdinnissen commented Nov 23, 2016

@dvorn if T is int then the parameter should never be null and if it is then wouldn't a crash be an expected behaviour as opposed to "hiding" it behind a default value type?

Also, should DelegateCommandBase still have the following code?:

        void ICommand.Execute(object parameter)
        {
            Execute(parameter);
        }

        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute(parameter);
        }

Since @brianlagunas and @dvorn mentioned that WPF will "ignore" any additional overrides function that add additional code (ie: logging like @dvor said).

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

@pdinnissen XAML knows nothing about CanExecute(T parameter) and works with ICommand.CanExecute(object parameter). Unfortunately, it calls CanExecute(null).

WPF does not "ignore" anything. All it does is calling ICommand.Execute. Everything else is our code which may be different and work differently.

Contributor

dvorn commented Nov 23, 2016

@pdinnissen XAML knows nothing about CanExecute(T parameter) and works with ICommand.CanExecute(object parameter). Unfortunately, it calls CanExecute(null).

WPF does not "ignore" anything. All it does is calling ICommand.Execute. Everything else is our code which may be different and work differently.

@pdinnissen

This comment has been minimized.

Show comment
Hide comment
@pdinnissen

pdinnissen Nov 23, 2016

Contributor

Oh right. Too early in the morning to keep all this straight. Thanks for clarifying.

I have a small recommendation that can be ignored for the parameterless DelegateCommand's constructor:
: base((o) => executeMethod(), (o) => canExecuteMethod())
be changed to:
: base((_) => executeMethod(), (_) => canExecuteMethod())
It makes it more explicit that the lambda parameter is being ignored.

Contributor

pdinnissen commented Nov 23, 2016

Oh right. Too early in the morning to keep all this straight. Thanks for clarifying.

I have a small recommendation that can be ignored for the parameterless DelegateCommand's constructor:
: base((o) => executeMethod(), (o) => canExecuteMethod())
be changed to:
: base((_) => executeMethod(), (_) => canExecuteMethod())
It makes it more explicit that the lambda parameter is being ignored.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 23, 2016

Member

@dvorn yeah, I am still playing around with the value type thing. I think I am going to remove the Action/Func's from the DelegateCommanBase, and have each command responsible for their own delegates instead of passing them around.

Member

brianlagunas commented Nov 23, 2016

@dvorn yeah, I am still playing around with the value type thing. I think I am going to remove the Action/Func's from the DelegateCommanBase, and have each command responsible for their own delegates instead of passing them around.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

@pdinnissen I am not opposed to (_)=> and used it myself but I noticed that this notation is less readable under harsh viewing conditions.

Contributor

dvorn commented Nov 23, 2016

@pdinnissen I am not opposed to (_)=> and used it myself but I noticed that this notation is less readable under harsh viewing conditions.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 23, 2016

Member

@dvorn check it out now. What do you think of this direction? I will worry about the value type thing later.

Member

brianlagunas commented Nov 23, 2016

@dvorn check it out now. What do you think of this direction? I will worry about the value type thing later.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

@brianlagunas I thought about keeping delegates separately, too. Also, you could make implement ICommand in derived classes instead of base. A couple comments:

I did not tried to run this code. Could there be a problem with overload resolution and/or Intellisense for DelegateCommand<object> ?

Now DelegateCommandBase has nothing to do with delegates. Its responsibility is observing "can execute changed" event. Maybe a different name?

Contributor

dvorn commented Nov 23, 2016

@brianlagunas I thought about keeping delegates separately, too. Also, you could make implement ICommand in derived classes instead of base. A couple comments:

I did not tried to run this code. Could there be a problem with overload resolution and/or Intellisense for DelegateCommand<object> ?

Now DelegateCommandBase has nothing to do with delegates. Its responsibility is observing "can execute changed" event. Maybe a different name?

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 23, 2016

Member

To handle the null Value Type issue, I think I am going to just throw a Debug.Assert instead or preventing the use of Value Types. No other command restricts the use of value types, and they don't seem to have any issues. I just need to figured out if the WPF framework passes null at anytime during it's initialization that is not controlled by the developer. I personal have never run into this.

        protected override void Execute(object parameter)
        {
            Debug.Assert(parameter == null && typeof(T).GetTypeInfo().IsValueType, "You have defined a ValueType for your paramater, but are tyring to pass null.  Fix your code!");

            Execute((T)parameter);
        }
Member

brianlagunas commented Nov 23, 2016

To handle the null Value Type issue, I think I am going to just throw a Debug.Assert instead or preventing the use of Value Types. No other command restricts the use of value types, and they don't seem to have any issues. I just need to figured out if the WPF framework passes null at anytime during it's initialization that is not controlled by the developer. I personal have never run into this.

        protected override void Execute(object parameter)
        {
            Debug.Assert(parameter == null && typeof(T).GetTypeInfo().IsValueType, "You have defined a ValueType for your paramater, but are tyring to pass null.  Fix your code!");

            Execute((T)parameter);
        }
@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

Google: "command parameter is null first time canexecute is called"

Contributor

dvorn commented Nov 23, 2016

Google: "command parameter is null first time canexecute is called"

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 23, 2016

Member

WOW! Okay, we'll just have to leave the restriction of Value Types in there then. Other than that, does everything else look goo to you?

Member

brianlagunas commented Nov 23, 2016

WOW! Okay, we'll just have to leave the restriction of Value Types in there then. Other than that, does everything else look goo to you?

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Nov 23, 2016

Contributor

Overall, I'm fine with it.

Yet another solution for value types might be to just return false from CanExecute(null) and do nothing in Execute(null).

Contributor

dvorn commented Nov 23, 2016

Overall, I'm fine with it.

Yet another solution for value types might be to just return false from CanExecute(null) and do nothing in Execute(null).

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Nov 24, 2016

Member

Alright, this has been merged into the master and will be in the next major release. Since Prism has never been on semantic versioning, and since this is such as massive break for devs, I will move Prism to semantic versioning on the next release.... Prism 7.0.

Member

brianlagunas commented Nov 24, 2016

Alright, this has been merged into the master and will be in the next major release. Since Prism has never been on semantic versioning, and since this is such as massive break for devs, I will move Prism to semantic versioning on the next release.... Prism 7.0.

@StanleyBroo

This comment has been minimized.

Show comment
Hide comment
@StanleyBroo

StanleyBroo Nov 25, 2016

StanleyBroo commented Nov 25, 2016

@weitzhandler

This comment has been minimized.

Show comment
Hide comment
@weitzhandler

weitzhandler Feb 20, 2017

I'd love to see AsyncCommand incorporated in Prism.
I use XAML & Prism mostly for LoB apps that relies mostly on client-server data transfer and I'd love to have the commands asynchronous.

weitzhandler commented Feb 20, 2017

I'd love to see AsyncCommand incorporated in Prism.
I use XAML & Prism mostly for LoB apps that relies mostly on client-server data transfer and I'd love to have the commands asynchronous.

@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn

dvorn Feb 20, 2017

Contributor

@weitzhandler Just use "async void" delegate to initialize your commands. If you assume more functionality, what do you want exactly from the following list: #785 (comment) ?

Contributor

dvorn commented Feb 20, 2017

@weitzhandler Just use "async void" delegate to initialize your commands. If you assume more functionality, what do you want exactly from the following list: #785 (comment) ?

@Robzilla

This comment has been minimized.

Show comment
Hide comment
@Robzilla

Robzilla Mar 27, 2017

Rather than having to sift through all 85 posts can we have a simple example of how to refactor a DelegateCommand.FromAsyncHandler call that uses the Execute and CanExecute as parameters in the constructor.

This is a huge breaking change and will come as quite a surprise to a lot of devs. The release notes are not very helpful regarding how to refactor existing code so if anyone does post an example it might be worth adding a link to the release notes.

Robzilla commented Mar 27, 2017

Rather than having to sift through all 85 posts can we have a simple example of how to refactor a DelegateCommand.FromAsyncHandler call that uses the Execute and CanExecute as parameters in the constructor.

This is a huge breaking change and will come as quite a surprise to a lot of devs. The release notes are not very helpful regarding how to refactor existing code so if anyone does post an example it might be worth adding a link to the release notes.

@brianlagunas

This comment has been minimized.

Show comment
Hide comment
@brianlagunas

brianlagunas Mar 27, 2017

Member

It shouldn't come as a surprise as the preview has been out since Nov of 2016, and was blogged about and talked about in this issue as well as other forums. I did the best I could to notify everyone. But, of course only those paying attention would have seen the notice.

None the less, it no different than any other ICommand.

var command = DelegateCommand(Execute);
async void Execute()
{
    await your code
}
Member

brianlagunas commented Mar 27, 2017

It shouldn't come as a surprise as the preview has been out since Nov of 2016, and was blogged about and talked about in this issue as well as other forums. I did the best I could to notify everyone. But, of course only those paying attention would have seen the notice.

None the less, it no different than any other ICommand.

var command = DelegateCommand(Execute);
async void Execute()
{
    await your code
}
@dvorn

This comment has been minimized.

Show comment
Hide comment
@dvorn
Contributor

dvorn commented Mar 27, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment