我正在构建一个 Windows 8 应用程序,但我遇到了异步调用问题。我将尝试提供尽可能多的细节,因为我认为我有两个结果:
- 当涉及到异步调用时,我做错了
- 或者我做错了,但也可能是错误的架构让我遇到了这个问题,而这个问题本来就不应该存在。
我是 Windows Azure 和 MVVM 的新手,但情况就是这样……</p>
该应用程序现在是为 Windows 8 构建的,但我也希望能够使用其他平台,所以我首先要做的是创建一个发布到 Windows Azure 网站的 WebAPI 项目。这样,我可以使用 JSON 传输数据,并且 WebAPI 控制器连接到一个存储库,该存储库正在处理与 Window Azure 表存储之间的数据请求。第二部分是一个 MVVM Light Windows 8 应用程序,它从 Azure 网站请求数据。
因此,让我们更详细地看一下 WebAPI 项目。在这里,我有一个类别模型开始。
public class Category : TableServiceEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
public string Parent { get; set; }
}
类别模型只包含一个名称和描述(id 是 TableServiceEntity 的 RowKey)。如果类别是嵌套的,还会将字符串引用添加到父类别。出现第一个问题:Parent 是否应该是 Category 类型而不是 string,后端的 Category 模型是否应该有一组子类别?
然后我有我的 IRepository 接口来定义存储库。(正在进行中;-))它还使用规范模式来传递查询范围。这一切正常,您可以使用浏览器进行测试并浏览到: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();
}
现在存储库已经清楚了,让我们看一下控制器。我有一个 CategoriesController,它只是一个包含 IRepository 存储库的 ApiController。(用 Ninject 注入,但在这里无关)
public class CategoriesController : ApiController
{
static IRepository<Category> _repository;
public CategoriesController(IRepository<Category> repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
_repository = repository;
}
控制器包含一些方法,例如:
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>();
}
到目前为止,我们已经看到了后端,让我们继续讨论这里的实际问题:MvvmLight 客户端和对 WebAPI 控制器的异步 http 请求。
在客户端项目中,我也有一个类别模型。
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; }
}
不要介意 PartitionKey 和 RowKey 属性,分区键应该被忽略,因为它不关心应用程序存在什么 Azure 表服务实体属性。RowKey 实际上可以重命名为 Id。但实际上并不相关。
主视图的 ViewModel 如下所示:
public class MainViewModel : CategoryBasedViewModel
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IBudgetTrackerDataService budgetTrackerDataService)
: base(budgetTrackerDataService)
{
PageTitle = "Home budget tracker";
}
}
它从我创建的 ViewModel 扩展而来,用于共享包含 Category Observable 集合的页面的逻辑。此 ViewModel 中的重要内容:
- 一个 IbudgetTrackerDataService 注入到 ViewModel 中,这是一个高级数据服务
- 一个 ObservableCollection 包含 category-ViewModel-wrapped 类别的集合
- 一些用于绑定的属性(fe: IsLoadingCategories 来处理视图上的 ProgressRing)
- 异步调用完成后IBudgetTrackerDataService调用的getCategoriesCompleted回调方法
所以代码如下:
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
}
}
}
这一切都有效,但现在我希望父/子关系在类别上工作。为此,我在 CategoryViewModel 中添加了逻辑,以便它在构造时获取子类别……</p>
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
因此 CategoryBasedViewModel 的构造是获取类别并调用回调方法 GetCategoriesCompleted:
_dataService.GetCategoriesAsync(GetCategoriesCompleted);
该回调方法也在调用 CategoryViewModel 的构造函数。在那里,另一个异步方法用于获取类别的子项。
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
还有我的问题!GetCategoriesByParentAsync 是在另一个异步调用内部发生的异步调用,代码只是中断了调用并且什么都不做。
数据服务实现接口:
public interface IBudgetTrackerDataService
{
void GetCategoriesAsync(Action<IList<Category>, Exception> callback);
void GetCategoriesByParentAsync(string parent, Action<IList<Category>,
Exception> callback);
}
异步方法包含以下代码:
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);
}
长话短说:
- 为什么嵌套调用时这些调用会失败?
- 其次,我是不是很愚蠢,我应该以不同的方式处理笼子的父/子关系吗?