Today, I just released an update to the Prism.Core NuGet package.  Is this 6.2.1 update, the DelegateCommand.FromAsyncHandler feature has been marked as Obsolete and will be removed from Prism in the next 6.3 update scheduled for early next year.

But why Brian?  Why are you removing DelegateCommand.FromAsyncHandler?  Well, the answer is simple.  It doesn’t work, and it actually breaks any non-async code that is used.  This can be demonstrated in a very simple test.

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

As you can see, this test FAILS!  Any exception that is thrown from a non-async method is swallowed.  Eaten right up.  This is not good!  Not good at all!  Not to mention the fact that the ICommand is not executed by any XAML framework in an async manner.  So it really provides absolutely no benefit to you.  The only argument that can really be made is that when you manually call the DelegateCommand.Execute in code you can await it, but I would argue that you should very rarely be calling commands in code.  Even if you did, you should not be trying to make ICommand act as async. 

That’s Not All

So you are now fully aware that if your apps use DelegateCommand.FromAsyncHandler, they will be broken when Prism 6.3 is released.  Now, we have another major issue we need to fix.  When Microsoft owned Prism, they made some changes to DelegateCommand that, looking back, were not the best.  Currently, if you have a custom command that derives from DelegateCommand and overrides the Execute or CanExecute methods, it’s broken.  Yup, it doesn’t work like you would probably expect.  The problem is that the code you added to your custom overridden implementation will NOT be called when invoked from XAML.  That’s right!  Your custom code only gets called when you manually call DelegateCommand.Execute in code.  Some of you may have already noticed this.

So we need to fix this too.  The only problem is, I’m not 100% confident in the approach that we should take.

My gut is telling me to drop the Execute(T parameter) signature from DelegateCommand, and just standardize on the ICommand.Execute(object parameter) signature.  This is what every other Command in every other MVVM framework does.  It would eliminate every issue that the DelegateCommand has, and it would work exactly how you would expect.  The only downside to this is that you would no longer have a strongly type parameter type in the Execute method.  Everything would still work, and your code will not be broken, but you would not have that nice reassurance of having a strongly typed parameter in the DelegateCommand.Execute method call.  Once again, this only impacts you if you are calling Execute manually in code.  That is the only time you would ever see this.  I don’t think this is a big deal, but I would love to know what you think.

This change impacts every single Prism application out there, so it important that you let your voice be heard.  I want to know what you think about these changes.  I don’t like to make breaking changes, but sometime in order to move a framework forward, to fix an major issue, or to improve its value, it is necessary.

Please join the conversation on how we should fix the DelegateCommand by commenting below or preferably in this thread: https://github.com/PrismLibrary/Prism/issues/785

Brian Lagunas

View all posts

4 comments

  • – Platform: WPF
    – Prism version: 6.2.1 (Core) / 6.2.0 (Others)

    Updating Prism.Core to 6.2.1 causes a System.IO.FileLoadException in Prism.Wpf.dll.
    The Exception is thrown at the bootstrapper.Run() method in the App.xaml.cs OnStartup().

    Stacktrace:

    at Prism.Unity.UnityBootstrapper.Run(Boolean runWithDefaultConfiguration)
    at Prism.Bootstrapper.Run()
    at MyApp.App.OnStartup(StartupEventArgs e) in *PATH*\MyApp\App.xaml.cs:Row 16.
    at System.Windows.Application.b__1_0(Object unused)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.DispatcherOperation.InvokeImpl()
    at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Windows.Threading.DispatcherOperation.Invoke()
    at System.Windows.Threading.Dispatcher.ProcessQueue()
    at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
    at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Application.RunDispatcher(Object ignore)
    at System.Windows.Application.RunInternal(Window window)
    at System.Windows.Application.Run(Window window)
    at System.Windows.Application.Run()
    at MyApp.App.Main()
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
    at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()

Follow Me

Follow me on Twitter, subscribe to my YouTube channel, and watch me stream live on Twitch.