Reputation: 35781
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
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