How can I load my viewmodel and still get events? - c#

I'm trying to implement MVVM and in the ViewModel I'm doing some async fetching of data. For that purpose I've tried to loading data in the constructor:
MyModel Model { get; set; }
public MyViewModel()
{
Model = new MyModel();
Model.Foo = await LoadDataFromIsolatedStorage();
But this isnt valid as you cant append async to the contructor. So I tried a public static load function:
MyModel Model { get; set; }
public MyViewModel()
{
Model = new MyModel();
}
async public static void Load()
{
Model.Foo = await LoadDataFromIsolatedStorage();
But here WP8 complains that it Cannot await void. Because you would set up the ViewModel and bind it to the View in the code behind of the view. Boring.
Lastly a fix is making the Load function return a ViewModel, so that you in the code behind of the view can do something like:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MyViewModel viewModel = await MyViewModel.Load();
with the following code to load:
MyModel Model { get; set; }
public MyViewModel()
{
Model = new MyModel();
}
async public static Task<MyViewModel> Load()
{
MyViewModel viewModel = new MyViewModel();
viewModel.Model.Foo = await LoadDataFromIsolatedStorage();
return viewModel;
NOW, the problem at hand is that I have no control if the data loaded should force the application to navigate to another page. Lets say MyViewModel loads a variable from isolated storage, that should then make the app navigate to another page?
I've set up eventlistener to MyViewModel to make the app navigate, but I cant do this when I initiate it.
Does not work with events:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MyViewModel viewModel = await MyViewModel.Load();
viewModel.NavigationAction += viewmodel_NavigationAction;
}
void viewmodel_NavigationAction(sender, args)
{
NavigationService.Navigate(...)
}
Would work but I "cannot await void":
async protected override void OnNavigatedTo(NavigationEventArgs e)
{
MyViewModel viewModel = new MyViewModel();
viewModel.NavigationAction += viewmodel_NavigationAction;
await viewModel.Load(); // given the Load only is a async void and not Task<T>
}
void viewmodel_NavigationAction(sender, args)
{
NavigationService.Navigate(...)
}

I assume this code block "doesn't work" because the event is set too late, after the data is loaded:
MyViewModel viewModel = await MyViewModel.Load();
viewModel.NavigationAction += viewmodel_NavigationAction;
In that case, fixing your last code block is simple enough: have Load return Task and it will work. Task is the async equivalent of void; you should never use async void unless you're writing an event handler. See my best practices article for more information.
However, even when you get this working, you'll end up with an empty view until the VM loads; this may be a poor user experience if your load could take a long time (or errors out, say, if there's no network connectivity).
You may want to consider using NotifyTaskCompletion from my AsyncEx library, which I describe on my blog. That pattern allows you to (immediately) create a VM in a "loading" state, which will transition (via INotifyPropertyChanged) to a "loaded" or a "loading error" state. That pattern provides a better UX, IMO.

What roliu wrote makes pretty much sense since the whole concept of async/await pattern is based on Tasks. When writing an asynchronous method you ALWAYS need to return a task to make sure the following await will work. Usually the compiler will make some special replacements in the background, so you don't have to care about it.
You will never get a bool as a result of an asynchronous operation - but a generic bool typed Task! Make sure you understood the pattern like described at MSDN.
Then you can create something like this. It is not a Win8 App. For simplicity I chose it to be a console application.
class Program
{
static void Main(string[] args)
{
DoWork();
}
static async void DoWork()
{
await YourVoidMethod();
}
static Task YourVoidMethod()
{
Task task = Task.Run(() =>
{
// Your payload code
}
);
return task;
}
}
Just as a hint: When working with GUIs you also need to work with the dispatcher. When changing data of the UI thread you otherwise could generate cross thread exceptions.

Related

Load data (async/await)

I'm not sure about this state. I need to get data from database asynchrony.
I have class DB
public class Db{
public async Task<ObservableCollection<Person>> GetAllPerson()
{
using (var context = new Db())
{
// get data and return ObservableCollection<Person>
}
}
}
In the ViewModel I call LoadData function.
public class VM{
public ObservableCollection<Person> Person { get; set; }
private readonly DB sqlRepository;
public VM()
{
sqlRepository=new DB();
LoadData();
}
private async void LoadData()
{
Person= await sqlRepository.GetAllPerson();
}
}
I got warning: Warning CS1998 This async method lacks 'await' operators and will run synchronously.
How can I run my function asynchronously?
Should I use ?
Person=await Task.Run(()=>this.sqlRepository.GetAllPerson());
How can I run my function asynchronously?
You're approaching your problem from the wrong direction. You're trying to go "outside in" - your ViewModel wants to load the database data asynchronously. And that's a fine way of describing the problem, but it's the wrong way to solve it.
To solve it more easily, start at the other end. Whatever methods are actually calling into the database (e.g., Entity Framework calls) should be made asynchronous first, and then let async grow out from there. Eventually you'll end up with something like:
public async Task<ObservableCollection<Person>> GetAllPersonAsync()
{
using (var context = new Db())
{
// This code wasn't shown in the question.
// But from the compiler warning, it was probably using something like
// var people = People.ToList();
// return new ObservableCollection<Person>(people);
// And the async version should be:
var people = await People.ToListAsync();
return new ObservableCollection<Person>(people);
}
}
Which you could consume as:
private async void LoadData()
{
Person = await sqlRepository.GetAllPersonAsync();
}
But I recommend consuming it via NotifyTask as described in my MVVM async data binding article. That approach would give you the ability to data-bind busy spinners and whatnot.
Should I use [Task.Run]?
No. That's "fake asynchrony" - where your code acts like it's asynchronous but it's really just synchronously running on a background thread.

ViewModel creation blocking UI

In my XAML UI I have a listview which contains a list of complex objects. These complex objects have an async initialization method which loads in the data (downloads an image, formats the text, etc).
Here's the setup (pseudo code):
public class PageViewModel
{
public ObservableCollection<ItemViewModel> Items;
public async Task InitializeAsync()
{
var models = await GetModelsAsync();
List<Task> initTasks = new List<Task>();
foreach(var model in models)
{
var vm = new ItemViewModel(model)
initTasks.Add(vm.InitializeAsync());
Items.Add(vm);
}
await Task.WhenAll(initTasks);
}
}
The issue i'm seeing is that it seems like the UI thread is being blocked and unresponsive until all the tasks have completed which is confusing me. All my async complex logic is in an awaitable task.
This lead me to experiment with the only other logic here, the creation of the view model. The issue seems to dissapear when I wrap the following code in a Task.Run:
var vm = Task.Run(() => new ItemViewModel(item))
This surprised me because there's very little to no logic in the ViewModel constructor which is why I was fine initially putting it on the UI thread.
Does anyone have thoughts on why I would see the UI thread block here? Do you have any code suggestions?
I can't be sure where you are calling your init() but you should be (unless you use an intelligent framework that automatically calls it for you) callingit from your Page OnNavTo override, like this:
public sealed partial class MainPage : Page
{
public MainPage() { InitializeComponent(); }
MainPageViewModel ViewModel => DataContext as MainPage;
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
DataContext = new MainPageViewModel();
await DataContext.InitAsync();
}
}
public class MainPageViewModel
{
public Task InitAsync()
{
// TODO
}
}
As an aside, I don't recommend you load your models in parallel, but instead in series because of performance on low-powered devices. Make sense? I should not make a noticeable difference to your user, bur your app will not lock if the generation is costly.
Best of luck!

MVVM How to set datacontext when viewmodel uses async

After hours of searching I am still without answer to this question. I have read this nice writing about async MVVM and made my viewmodel to use factory method.
public class MainViewModel
{
// sic - public, contrary to the pattern in the article I cite
// so I can create it in the Xaml as below
public MainViewModel()
{
}
private async Task InitializeAsync()
{
await DoSomethingAsync();
}
public static async Task<MainViewModel> CreateAsync()
{
var ret = new MainViewModel();
await ret.InitializeAsync();
return ret;
}
}
This is clear for me, but I can't understand how to make instance of MainViewModel and set it to datacontext in MainPage. I can't simply write
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
because I should use MainViewModel.CreateAsync()-method. And I can't do it on code-behind, which I even want to do, because code-behind -constructor is normal method, not an async-method. So which is proper way to continue?
made my viewmodel to use factory method
I'm normally a fan of that approach - it's my favorite way to work around the "no async constructors" limitation. However, it doesn't work well in the MVVM pattern.
This is because VMs are your UI, logically speaking. And when a user navigates to a screen in an app, the app needs to respond immediately (synchronously). It doesn't necessarily have to display anything useful, but it does need to display something. For this reason, VM construction must be synchronous.
So, instead of trying to asynchronously construct your VM, first decide what you want your "loading" or "incomplete" UI to look like. Your (synchronous) VM constructor should initialize to that state, and it can kick off some asynchronous work that updates the VM when it completes.
This is not too hard to do by hand, or you can use the NotifyTaskCompletion approach that I described in an MSDN article on async MVVM data binding to drive the state transition using data bindings.
You have to initalize the viewmodel before the window is open. Go to your App.xaml file and remove the part: StartupUri="MainWindow.xaml". Then you go to the App.xaml.cs and add this:
protected async override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow { DataContext = await CreateAsync() };
mainWindow.Show();
}
I would re-factor. Make the MainViewModel construction / instantiation lightweight. Then create a Load or Initialize method on your VM. From the code-behind create an instance, set it to the DataContext, then invoke the init method and let it run.
E.g.
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var dc = new MainViewModel();
dc.Initialize("Hello", " ", "world");
this.DataContext = dc;
}
}
public class MainViewModel
{
/// <summary>Simple constructor</summary>
public MainViewModel() { }
public void Initialize(params object[] arguments)
{
//use the task to properly start a new thread as per:
//http://stackoverflow.com/a/14904107/1144090 and
//https://msdn.microsoft.com/en-us/library/hh965065.aspx
//(what would happen if we simply invoke init async here?)
this.InitializeAsync(arguments)
.ContinueWith(result =>
{
if (!result.IsFaulted)
return;
MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString());
});
}
private async Task InitializeAsync(params object[] arguments)
{
await Task.Delay(2333);
MessageBox.Show(String.Concat(arguments));
}
}
Note that this is the quick-and-dirty solution, the other two answers (paired with a dependency injection framework) will give you proper high-level structure for your solution.
Firstly, you should make default constructor as private to avoid misusing your class (the article you cite does this - the constructor is private).
The approach you are using to set DataContext is not suitable for MVVM pattern (the View shouldn't create its ViewModel itself).
You should create your View and ViewModel in the higher level layer and have that layer bind them. Says if the Page is your main View you should create them in App.xaml.cs by overriding OnStartup, something like this:
var page = new Page();
var dataService = new YourDataService(); // iff Create or the ctor require arguments
var viewModel = await MainViewModel.CreateAsync(dataService);
page.DataContext = viewModel;
page.Show();

Async and populating data for WPF application

I have a WPF application that initializes the state of the UI via methods in the constructor. However, it's never returning from the Wait(); in the constructor.
Here's what I am currently doing via a fairly-contrived sample:
public class SomeViewModel
{
public ICommand AddDataCommand { get { return RelayCommand(AddDataExecute); } }
public ObservableCollection<int> UIData { /* Property with INotifyPropertyChanged */}
public SomeViewModel()
{
//Load synch. here
LoadData().Wait();
}
public async Task LoadData()
{
UIData = await Task.Run(() => SomeService.SelectAllUIData());
}
public async void AddDataExecute()
{
//Add new data item to database on separate thread to keep UI responsive
await Task.Run(() => SomeService.AddNewData(0));
//Reload data from database and update UI, has to happen after AddNewData completes
await LoadData();
}
}
I believe it's hanging because I am never actually returning a Task. However, I don't know of a different way to assign UIData asynchronously that works both in the constructor and the Commands that call it.
You're seeing the classic deadlock situation that I describe on my blog.
To solve it, use async all the way, as I describe in my MSDN article on async best practices.
This can be difficult in some scenarios. I have a blog post describing a few approaches for async constructors. However, since you're talking about a ViewModel and data for the UI, you'll probably find my blog post on async properties more helpful - in particular, the section on data binding.
Don't construct the object through a constructor, if you require construction to be asynchronous. Use a static factory method:
public class SomeViewModel
{
private SomeViewModel()
{ }
public static async Task<SomeViewModel> Create()
{
SomeViewModel model = new SomeViewModel();
await model.LoadData();
return model;
}
public async Task LoadData()
{
UIData = await Task.Run(() => SomeService.SelectAllUIData());
}
//...
}
As for why your code isn't working, you're getting the standard await deadlock in which you're blocking on an asynchronous operation, that blocking is preventing continuations from being called on the UI thread, and with two different operations each waiting on the other to continue, you get a deadlock. This is why it's important to "async all the way up" rather than synchronously blocking on an asynchronous operation.

How to test an async void method

Please consider the code as shown below. By calling GetBrands, property Brands will be assigned with proper data.
public class BrandsViewModel : ViewModelBase
{
private IEnumerable<Brand> _brands;
public IEnumerable<Brand> Brands
{
get { return _brands; }
set { SetProperty(ref _brands, value); }
}
public async void GetBrands()
{
// ......
Brands = await _dataHelper.GetFavoriteBrands();
// ......
}
}
But if I test it as shown below, the test failed. How do I wait for the async call inside method GetBrands?
[TestMethod]
public void AllBrandsTest()
{
BrandsViewModel viewModel = new BrandsViewModel();
viewModel.GetBrands();
Assert.IsTrue(viewModel.Brands.Any());
}
The simple answer here is: don't make it an async void. In fact, don't ever make something an async void unless it absolutely has to be to work as an event-handler. The things that async void loses are precisely the things that you want here for your test (and presumably for your real code).
Make it an async Task method instead, and you now have the ability to wait for completion (with timeout) / add a continuation, and to check whether it exited with success or an exception.
This is a single word change, to:
public async Task GetBrands()
{
// ......
Brands = await _dataHelper.GetFavoriteBrands();
// ......
}
and then in the test:
[TestMethod]
public async Task AllBrandsTest()
{
BrandsViewModel viewModel = new BrandsViewModel();
var task = viewModel.GetBrands();
Assert.IsTrue(task.Wait(YOUR_TIMEOUT), "failed to load in time");
Assert.IsTrue(viewModel.Brands.Any(), "no brands");
}
Your model (a DTO) is populating itself (data access). This is too much for one class to do. Usually when you ask yourself "How on earth can I test this", it's time for refactoring. Create a separate data access class:
BrandsViewModel viewModel = new BrandsViewModel();
var brandAccess = new BrandsDataAccess();
viewModel.Brands = await brandAccess.GetAllBrands();
Assert.IsTrue(viewModel.Brands.Any());
Now you can test BrandsDataAccess.GetAllBrands().

Categories

Resources