Mike
Mike

Reputation: 418

Binding data from WCF into a MVVM client always empty

I'm making my first Windows 8 client. This is also the first time I've used async methods to load a property so please forgive me if this is a noobie question.

I have a WCF service and started from the Split Page template in visual studio for the client (but I'm going to pretty much replace everything).

When I get the data into my Views directly inside of the "SplitPage" code behind everything is loaded properly and shows up. However, when I try to use MVVM the property doesn't have any items in it when it gets bound. Because of WCF it is an async call to get the data is that the cause of the problem? Is there away to make sure that they data is back before returning the property (properties themselves don't seem to be able to be labelled async)?

Here's What I did:

For testing purposes I put a ListView (code says listbox but I went with listview in the XAML) and added the following code as an async method called in the OnNavigatedTo handler:

private async void GetUsersList()
{
   ServiceClient client = new ServiceClient();
   List<UserDTO> _users = (await client.GetUsersAsync()).ToList();
   foreach(UserDTO user in _users)
   {
      UserListBox.Items.Add(new UserView(user));
   }
   TestStack.Children.Add(new UsersView());
}

This works fine and when the page loads the UserListBox contains the UserViews.

I then tried to go into a full MVVM pattern and made a UsersViewModel and UsersView (plural) along with a repository that in its constructor initializes a property Users with an ObservableCollection of UserDTOs it has pulled from my WCF service. That is what the last line of the above message is doing is setting the view into a stackpanel on the page.

The view and the viewmodel are glued together in a resources file:

<DataTemplate x:Key="vm:UsersViewModel">
    <vw:UsersView />
</DataTemplate>

The binding is a little different than I'm used to since apparently .Net 4.5 doesn't have the x:Type property on DataTemplates anymore.

The part of the repository where the data is loaded looks as follows:

    private ObservableCollection<UserDTO> _users = new ObservableCollection<UserDTO>();
    private ServiceClient _client = new ServiceClient();

    public UserRepository()
    {
        GetUsers();
    }

    public async void GetUsers()
    {
        var tempList = await _client.GetUsersAsync();
        foreach(UserDTO item in tempList)
        {
            _users.Add(item);
        }
    }

The only thing that the constructor for the UsersViewModel does is create an instance of the repository and load UserViewModel items into its observablecollection of UserViewModel:

public UsersViewModel()
{
   _repo = new UserRepository();
   foreach (UserDTO item in _repo.Users)
   {
      _users.Add(new UserViewModel(item.Id));
   }
}

I've tried putting output statements everywhere and sure enough the properties "getter" is returning an empty list even though the same code directly in the SplitPage code returns my testing items that are already in the database WCF is feeding from. Could it be something as simple as the thread that the code runs on? Perhaps SplitPage is running the call to WCF on the UI thread so the binding doesn't happen until the data async call has returned but with MVVM for some reason the databinding happens right away while the data is loaded on a background thread? If so shouldn't the fact that it is an ObservableCollection get the UI notified when the data finally does appear in the property?

Upvotes: 1

Views: 717

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457217

async methods return before they have finished executing. You're not seeing any users because GetUsers returned to the UserRepository constructor which returned to the UsersViewModel constructor before the users were loaded.

The solution I most prefer is asynchronous factory methods, e.g., for UserRepository:

private UserRepository()
{
}

private async Task InitializeAsync()
{
    var tempList = await _client.GetUsersAsync();
    foreach(UserDTO item in tempList)
    {
        _users.Add(item);
    }
}

public static async Task<UserRepository> Create()
{
    var ret = new UserRepository();
    await ret.InitializeAsync();
    return ret;
}

The biggest benefit of the async factory method approach is that you never get an instance that hasn't been initialized.

However, there are certain situations where you must have a public constructor rather than an async factory method, e.g., IoC/DI or data binding.

In cases like these, I find the following pattern helpful:

public MyConstructor()
{
  Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
  // asynchronous initialization here
}

You could apply it to your repository like this:

private ObservableCollection<UserDTO> _users = new ObservableCollection<UserDTO>();
private ServiceClient _client = new ServiceClient();

public UserRepository()
{
    Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
    var tempList = await _client.GetUsersAsync();
    foreach(UserDTO item in tempList)
    {
        _users.Add(item);
    }
}

You can then use the same pattern in your dependent class:

public UsersViewModel()
{
    _repo = new UserRepository();
    Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
    // Wait for the repository to initialize
    await _repo.Initialized;

    foreach (UserDTO item in _repo.Users)
    {
        _users.Add(new UserViewModel(item.Id));
    }
}

As a side note: as a general rule you should avoid async void. You may find my async/await intro helpful.

Upvotes: 2

Related Questions