Рефакторинг API-вызовов для использования await
Хочу обновить приложение C# Winforms для использования await. Приложение вызывает API-интерфейс MYOB Accountright через SDK. Я использую Dot Net Framework 4.5.1
Старый код такой
public void GetItems( CompanyFile companyFile )
{
var itemSvc = new ItemService(MyConfiguration, null, MyOAuthKeyService);
string pageFilter = string.Format("$top={0}&$skip={1}&$orderby=Date desc", PageSize,
PageSize * (_currentPage - 1));
itemSvc.GetRange(MyCompanyFile, pageFilter, MyCredentials, OnComplete, OnError);
}
/// <summary>
/// Method called on Async complete
/// </summary>
/// <param name="statusCode"></param>
/// <param name="items"></param>
/// <remarks></remarks>
private void OnComplete(System.Net.HttpStatusCode statusCode,
PagedCollection<Item> items)
{
myItems = items;
}
/// <summary>
/// Callback if there is an error
/// </summary>
/// <param name="uri"></param>
/// <param name="ex"></param>
/// <remarks></remarks>
private void OnError(Uri uri, Exception ex)
{
Trace.WriteLine("In OnError");
MessageBox.Show(ex.Message);
}
Я хочу код что-то вроде этого
private async Task FetchItemsAsync()
{
var itemSvc = new ItemService(MyConfiguration, null, MyOAuthKeyService);
string pageFilter = string.Format("$top={0}&$skip={1}&$orderby=Date desc", PageSize,
PageSize * (_currentPage - 1));
itemSvc.GetRange(MyCompanyFile, pageFilter, MyCredentials, OnComplete, OnError);
var totalPages = (int)Math.Ceiling((double)(myItems.Count / PageSize));
while (_currentPage < totalPages)
{
await LoadMore(); // how do I write this?
}
}
Как я могу это сделать?
[Update5]
Я старался
private const double PageSize = 400;
protected CancellationTokenSource MyCancellationTokenSource;
protected CompanyFile MyCompanyFile;
protected IApiConfiguration MyConfiguration;
protected ICompanyFileCredentials MyCredentials;
protected ItemService MyItemService;
protected IOAuthKeyService MyOAuthKeyService;
private int _currentPage = 1;
private int _totalPages;
public void FetchItems(CompanyFile companyFile, IApiConfiguration configuration, ICompanyFileCredentials credentials)
{
MyCompanyFile = companyFile;
MyConfiguration = configuration;
MyCredentials = credentials;
MyCancellationTokenSource = new CancellationTokenSource();
MyItemService = new ItemService(MyConfiguration, null, MyOAuthKeyService);
FetchAllItemsAsync();
}
private async void FetchAllItemsAsync()
{
try
{
var items = new List<Item>();
int totalPages = 0;
do
{
string pageFilter = string.Format("$top={0}&$skip={1}&$orderby=Date desc", PageSize, PageSize * (_currentPage - 1));
CancellationToken ct = MyCancellationTokenSource.Token;
Log("About to Await GetRange");
Task<PagedCollection<Item>> tpc = MyItemService.GetRangeAsync(MyCompanyFile, pageFilter, MyCredentials, ct, null);
Log("About to Await GetRange B");
PagedCollection<Item> newItems = await tpc; // fails here
Log("Page {0} retrieved {1} items", _currentPage, newItems.Count);
if (totalPages == 0)
{
totalPages = (int)Math.Ceiling((items.Count / PageSize));
}
items.AddRange(newItems.Items.ToArray());
_currentPage++;
}
while (_currentPage < totalPages);
MessageBox.Show(string.Format("Fetched {0} items", items.Count));
}
catch (ApiCommunicationException ex)
{
Log(ex.ToString());
throw;
}
catch (Exception exception)
{
Log(exception.ToString());
throw;
}
}
Однако я получаю исключение ValidationException
{"Encountered a validation error (http://localhost:8080/AccountRight/ab5c1f96-7663-4052-8360-81004cfe8598/Inventory/Item/?$top=400&$skip=0&$orderby=Date desc)"}
[MYOB.AccountRight.SDK.ApiValidationException]: {"Encountered a validation error (http://localhost:8080/AccountRight/ab5c1f96-7663-4052-8360-81004cfe8598/Inventory/Item/?$top=400&$skip=0&$orderby=Date desc)"}
base: {"Encountered a validation error (http://localhost:8080/AccountRight/ab5c1f96-7663-4052-8360-81004cfe8598/Inventory/Item/?$top=400&$skip=0&$orderby=Date desc)"}
ErrorInformation: "Warning, error messages have not been finalised in this release and may change"
Errors: Count = 1
RequestId: "e573dfed-ec68-4aff-ac5e-3ffde1c2f943"
StatusCode: BadRequest
URI: {http://localhost:8080/AccountRight/ab5c1f96-7663-4052-8360-81004cfe8598/Inventory/Item/?$top=400&$skip=0&$orderby=Date desc}
Я написал эту проблему в службу поддержки MYOB
2 ответа
В последней версии https://www.nuget.org/packages/MYOB.AccountRight.API.SDK/, на которую вы ссылаетесь, уже есть перегрузки для поддержки async/await в.NET4, .NET45 и PCL.
Пример кода был создан в качестве примера для кого-то, использующего.NET 3.5 (следовательно, нет async/await). Другой пример ( Windows Phone) показывает асинхронность / ожидание в действии с использованием SDK
[Обновить]
Вы, вероятно, получаете исключение, связанное с OData, как Item
сущность не имеет Date
поле, по которому вы можете фильтровать (см. документы).
Когда вы ловите ApiCommunicationException
(из которых ApiValidationException
является подклассом) есть свойство Errors, которое предоставляет более подробную информацию.
Также есть RequestId
(и некоторые другие свойства), которые очень полезны, если вам нужно поговорить с ребятами из службы поддержки, если у вас возникли проблемы с общедоступным облачным API.
Вы можете использовать объект TaskCompletionSource, который можно разрешить с помощью обратного вызова результата или ошибки. Я не уверен, что подпись обратного вызова ошибки так, что часть, вероятно, не будет работать.
private Task<PagedCollection<Item>> FetchItemsAsync()
{
var taskSource = new TaskCompletionSource<PagedCollection<Item>>();
var itemSvc = new ItemService(MyConfiguration, null, MyOAuthKeyService);
string pageFilter = string.Format("$top={0}&$skip={1}&$orderby=Date desc", PageSize,
PageSize * (_currentPage - 1));
itemSvc.GetRange(
MyCompanyFile,
pageFilter,
MyCredentials,
(statusCode, items) => taskSource.TrySetResult(items),
(error) => taskSource => taskSource.TrySetException(error) // Not sure if this is correct signature
);
return taskSource.Task;
}
Затем вы можете вернуть созданный им объект Task, который вы можете использовать для асинхронных операций. Я не совсем уверен, какую логику вы пытаетесь реализовать, потому что ваш вопрос не очень подробный, но вы можете использовать метод с командой await, потому что он возвращает объект Task, как показано ниже.
private async void FetchAllItemsAsync()
{
int totalPages;
do
{
items = await FetchItemsAsync()
totalPages = (int)Math.Ceiling((double)(items.Count / PageSize));
_currentPage++;
} while (_currentPage < totalPages)
}