Alex
Alex

Reputation: 35781

Calling async method from view model's constructor

I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6. The app has numerous view models that call async methods for initialization. Here's a sample view model:

public class MyViewModel : MyViewModelBase
{
    public MyViewModel()
    {
        PopulateParameters();

        // Other code...
    }

    public ObservableCollection<ParametersViewModel> 
        Parameters { get; private set; } = 
            new ObservableCollection<ParametersViewModel>();

    private async void PopulateParameters()
    {
        var service = new MyDataService();
        Parameters.Clear();
        foreach(var parameter in await service.GetParameters())
            Parameters.Add(parameter);      
    }
    // Other methods and properties
}

In MyDataService class, I have this method:

public async Task<ParametersViewModel> GetParameters()
{
    using (var context = new MyEntities())
    {
        var query = (from param in context.Parameters
            select new ParametersViewModel
            {
                // Populate ParametersViewModel properties here...
            }
            );
        return await Task.Run(() => query);
    }
}

Note that the view model's constructor is calling the async void method PopulateParameters(). This is very bad programming and I'd like to change it. However, I'm not sure how. I can't inject the necessary data into all my view models; some have to perform their own initialization, much of which involve calls to async methods.

How do I change the above code to conform to best practices when async methods are called from a view model's constructor? Thanks.

Update: Ensure you bind to the .Result of the value returned from NotifyTask.Create(). Took me a while to figure that out. More on that here: https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Upvotes: 2

Views: 5153

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456322

I have a blog post describing several approaches to "async constructors".

In this case, though, it looks like what you'd really want is async data binding. Using the NotifyTask type from my Mvvm.Async library would look like this:

public NotifyTask<ObservableCollection<ParametersViewModel>>
    Parameters { get; private set { /* with notify, such as RaisePropertyChanged() */ } }

public MyViewModel()
{
  Parameters = NotifyTask.Create(() => GetParametersAsync(),
      new ObservableCollection<ParametersViewModel>());
  // Other code...
}

private async Task<ObservableCollection<ParametersViewModel>> GetParametersAsync()
{
  var service = new MyDataService();
  var result = new ObservableCollection<ParametersViewModel>();
  foreach(var parameter in await service.GetParameters())
      result.Add(parameter);
  return result;
}

The NotifyTask<T> wrapper provides several data-bindable properties, such as Result containing the observable collection, IsNotCompleted for showing loading indicators, and IsFaulted/ErrorMessage for data-binding error conditions (if you want to).

Upvotes: 5

Related Questions