Windows 8 アプリを作成していますが、非同期呼び出しで問題が発生しています。これには2つの結果があると思うので、できるだけ多くの詳細を提供しようとします:
- 非同期呼び出しに関しては、私は完全に間違ったことをしています
- または、私はそれを間違っていますが、そもそもそこにあるべきではないこの問題を私に突きつける間違ったアーキテクチャかもしれません。
私は Windows Azure と MVVM の初心者ですが、状況は次のとおりです...</p>
アプリは現在 Windows 8 用に構築されていますが、他のプラットフォームも使用できるようにしたいので、最初に行ったことは、Windows Azure Web サイトに発行される WebAPI プロジェクトを作成することです。そうすれば、JSON を使用してデータを転送でき、WebAPI コントローラーは、Window Azure Table Storage との間のデータ要求を処理するリポジトリに接続します。2 番目の部分は、Azure Web サイトからデータを要求する MVVM Light Windows 8 アプリです。
それでは、WebAPI プロジェクトを詳しく見てみましょう。ここでは、まずカテゴリ モデルを用意します。
public class Category : TableServiceEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
public string Parent { get; set; }
}
カテゴリ モデルには名前と説明が含まれているだけです (id は TableServiceEntity の RowKey です)。また、カテゴリがネストされている場合は、文字列参照が親カテゴリに追加されます。最初の疑問が生じます: 親は文字列ではなくカテゴリの型である必要があり、バックエンド側のカテゴリ モデルには子カテゴリのコレクションが必要ですか??
次に、リポジトリを定義する 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();
}
リポジトリが明確になったので、コントローラーを見てみましょう。IRepository リポジトリを含む ApiController である CategoriesController があります。(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";
}
}
これは、Category Observable コレクションを含むページのロジックを共有するために作成した ViewModel から拡張されています。この ViewModel の重要事項:
- 高レベルのデータ サービスである ViewModel に挿入される IBudgetTrackerDataService
- カテゴリ ViewModel でラップされたカテゴリのコレクションを含む ObservableCollection
- バインディング用のいくつかのプロパティ (fe: ビューで ProgressRing を処理するための IsLoadingCategories)
- 非同期呼び出しが完了した後に 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);
}
簡単に言えば:
- 呼び出しをネストすると、これらの呼び出しが失敗するのはなぜですか?
- 第二に、私は愚かで、檻の親子関係を別の方法で処理する必要がありますか?