I am building a Windows 8 app and I’m running into trouble with async calls. I am going to try to provide as many details as possible because I think there I 2 outcomes to this :
- I am doing stuff completely wrong when it comes to the async calls
- Or I am doing it wrong but also, it might me the wrong architecture that puts me up with this problem which shouldn’t be there in the first place.
I am a newby to Windows Azure and to MVVM but here’s the situation…
The app is now built for Windows 8 but I also want to be able to use other platforms, so what I have done first is creating a WebAPI project which is published to a Windows Azure Web Site. That way, I can use JSON to transfer data and the WebAPI controllers connect to a Repository that is handling data requests to and from Window Azure Table Storage. The second part is a MVVM Light Windows 8 app that requests data from the Azure Web Site.
So let’s take a more detailed look at the WebAPI project. Here I have a category model to start with.
public class Category : TableServiceEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
public string Parent { get; set; }
}
The category model simply contains a Name and Description (The id is the RowKey of the
TableServiceEntity). Also a string reference is added to the parent category in case the categories are nested. First question arises : Should the Parent be of the type Category instead of string and should the Category model on the backend side have a collection of child categories??
Then I have my IRepository interface to define repositories. (Work in progress ;-)) It is also using the Specification pattern to pass query ranges. This is all working as you can test using your browser and browse to : http://homebudgettracker.azurewebsites.net/api/categories
public interface IRepository<T> where T : TableServiceEntity
{
void Add(T item);
void Delete(T item);
void Update(T item);
IEnumerable<T> Find(params Specification<T>[] specifications);
IEnumerable<T> RetrieveAll();
void SaveChanges();
}
Now that the repositories are clear, let’s take a look at the controller. I have a CategoriesController which is just an ApiController containing an IRepository repository. (Injected with Ninject but irrelevant here)
public class CategoriesController : ApiController
{
static IRepository<Category> _repository;
public CategoriesController(IRepository<Category> repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
_repository = repository;
}
The controller contains a few methods like for example:
public Category GetCategoryById(string id)
{
IEnumerable<Category> categoryResults =_repository.Find(new ByRowKeySpecification(id));
if(categoryResults == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
if (categoryResults.First<Category>() == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return categoryResults.First<Category>();
}
By now, we have seen the backend and let’s move on to the actual problem here: The MvvmLight client and the async http request to the WebAPI controller.
On the client side project, I also have a category model.
public class Category
{
[JsonProperty("PartitionKey")]
public string PartitionKey { get; set; }
[JsonProperty("RowKey")]
public string RowKey { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
[JsonProperty("Timestamp")]
public string Timestamp { get; set; }
[JsonProperty("Parent")]
public string ParentRowKey { get; set; }
public ObservableCollection<Category> Children { get; set; }
}
Don’t mind the PartitionKey and RowKey properties, the partition key should be left out because it does not concern the application what Azure table service entitiy properties exist. The RowKey could actually be renamed to Id. But not actually relevant here.
The main view’s ViewModel looks like this:
public class MainViewModel : CategoryBasedViewModel
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IBudgetTrackerDataService budgetTrackerDataService)
: base(budgetTrackerDataService)
{
PageTitle = "Home budget tracker";
}
}
It extends from a ViewModel that I created to share the logic for pages that contain a Category Observable collection. The important stuff in this ViewModel:
- A IBudgetTrackerDataService that is injected into the ViewModel which is a high level data service
- An ObservableCollection containing a collection of category-
ViewModel-wrapped categories - Some properties for binding (fe: IsLoadingCategories to handle ProgressRing on the view)
- A getCategoriesCompleted callback method called by the IBudgetTrackerDataService after
the async call will be completed
So the code is as follows:
public abstract class CategoryBasedViewModel : TitledPageViewModel
{
private IBudgetTrackerDataService _dataService;
private ObservableCollection<CategoryViewModel> _categoryCollection;
private Boolean isLoadingCategories;
public const string CategoryCollectionPropertyName = "CategoryCollection";
public const string IsLoadingCategoriesPropertyName = "IsLoadingCategories";
public Boolean IsLoadingCategories
{
get
{
return isLoadingCategories;
}
set
{
if (isLoadingCategories != value)
{
isLoadingCategories = value;
RaisePropertyChanged(IsLoadingCategoriesPropertyName);
}
}
}
public ObservableCollection<CategoryViewModel> CategoryCollection
{
get
{
return _categoryCollection;
}
set
{
_categoryCollection = value;
RaisePropertyChanged(CategoryCollectionPropertyName);
}
}
public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService)
{
wireDataService(budgetTrackerDataService);
}
public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService, string pageTitle)
{
PageTitle = pageTitle;
wireDataService(budgetTrackerDataService);
}
private void wireDataService(IBudgetTrackerDataService budgetTrackerDataService)
{
_dataService = budgetTrackerDataService;
CategoryCollection = new ObservableCollection<CategoryViewModel>();
IsLoadingCategories = true;
_dataService.GetCategoriesAsync(GetCategoriesCompleted);
}
private void GetCategoriesCompleted(IList<Category> result, Exception error)
{
if (error != null)
{
throw new Exception(error.Message, error);
}
if (result == null)
{
throw new Exception("No categories found");
}
IsLoadingCategories = false;
CategoryCollection.Clear();
foreach (Category category in result)
{
CategoryCollection.Add(new CategoryViewModel(category, _dataService));
// Added the dataService as a parameter because the CategoryViewModel will handle the search for Parent Category and Children catagories
}
}
}
This is all working but now I want the Parent/Children relation to work on categories. For this, I have
added logic to the CategoryViewModel so that it is fetching child categories when constructed…
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
So the construction of a CategoryBasedViewModel is fetching categories and invokes the callback method GetCategoriesCompleted:
_dataService.GetCategoriesAsync(GetCategoriesCompleted);
That callback method is also calling the constructor of the CategoryViewModel. There, another async method is used to fetch the children of a category.
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
And there is my problem! The GetCategoriesByParentAsync is an async call happening inside the other async call and the code just breaks out of the call and does nothing.
The data service implements the interface :
public interface IBudgetTrackerDataService
{
void GetCategoriesAsync(Action<IList<Category>, Exception> callback);
void GetCategoriesByParentAsync(string parent, Action<IList<Category>,
Exception> callback);
}
The async methods contain the following code:
public async void GetCategoriesAsync(Action<IList<Category>, Exception> callback)
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await _client.GetAllCategories();
// Invoke the callback function passed to this operation
callback(categoryEnumerable.ToList<Category>(), null);
}
public async void GetCategoriesByParentAsync(string parent, Action<IList<Category>,
Exception> callback)
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await
_client.GetCategoriesWithParent(parent);
// Invoke the callback function passed to this operation
callback(categoryEnumerable.ToList<Category>(), null);
}
Long story short:
- Why do these calls fail when nesting the calls?
- Secondly, Am I being stupid and should I handle the parent / children
relation of the cagegories differently?
I’m going to sidestep the parent/child relation question for now and just address the
asyncissues.First, there are a couple general guidelines for
asynccode that I explain in detail in myasync/awaitintro blog post:async void(returnTaskorTask<T>instead).ConfigureAwait(false)when applicable.I’ve seen the
callbackdelegate approach taken by others but I’m not sure where it’s coming from. It doesn’t work well withasyncand only serves to complicate the code, IMO. TheTask<T>type was designed to represent a result value coupled with anExceptionand it works seamlessly withawait.So first, your data service:
or better yet, if you don’t actually need
IList<T>:(and at that point, you may question what purpose your data service is serving).
Moving on to the MVVM
asyncissues:asyncdoesn’t play especially well with constructors. I’ve got a blog post coming out in a few weeks that will address this in detail, but here’s the gist:My personal preference is to use asynchronous factory methods (e.g.,
public static async Task<MyType> CreateAsync()) but this isn’t always possible especially if you’re using DI/IoC for your VMs.In this case, I like to expose a property on my types that require asynchronous initialization (actually, I use an
IAsyncInitializationinterface, but for your code a convention will work just as well):public Task Initialized { get; }.This property is set only once in the constructor, like this:
You then have the option of having your “parent” VM wait for its “child” VMs to initialize. It’s not clear that this is what you want, but I’ll assume that you want
IsLoadingCategoriesto betrueuntil all the child VMs have loaded:I added the
InitializationErrorandNotifyOnInitializationErrorAsyncto demonstrate one way to surface any errors that may happen during initialization. SinceTaskdoesn’t implementINotifyPropertyChanged, there’s no automatic notification if/when the initialization fails, so you have to surface it explicitly.