0

want to update a C# Winforms application to use await. The application calls the MYOB Accountright API via an SDK. I am using Dot Net Framework 4.5.1

The old code is like this

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);
    }

I want code something like this

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?
        }
}

How do I do that?

[Update5]

I tried

    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;
        }
    }

However I get a 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}

I have cross posted this problem to MYOB Support

Kirsten
  • 15,730
  • 41
  • 179
  • 318

2 Answers2

3

The latest version of the MYOB.AccountRight.API.SDK you are referencing already has overloads for supporting async/await on .NET4, .NET45 and PCL.

The Sample code was created as an example for someone using .NET 3.5 (hence no async/await). Another sample (windows phone) shows async/await in action using the SDK

[Update]

You are probably getting an OData related exception as the Item entity does not have a Date field for which you can filter by (see docs).

When you catch an ApiCommunicationException (of which ApiValidationException is a subclass) there is an Errors property that provides more detail.

There is also a RequestId (and some other properties) which are very useful should you need to talk to the support guys if you have issues talking to the cloud hosted API.

Shaun Wilde
  • 8,228
  • 4
  • 36
  • 56
  • 1
    FYI: We are currently prepping a new release (candidate) with updated nuget references which should be available in the next few days. – Shaun Wilde Jul 22 '15 at 08:58
  • Do you have an example on how to call ItemService.GetAsync ? – Kirsten Jul 22 '15 at 09:49
  • 1
    not with ItemService but here is one with [ContactService](https://github.com/MYOB-Technology/AccountRight_WindowsMobile_sampleApp/search?utf8=%E2%9C%93&q=GetAsync) - the syntax is the same. With hindsight I'd probably write it slightly differently nowadays but if you know async/await that should be a breeze. – Shaun Wilde Jul 22 '15 at 10:50
  • actually I realised that GetRangeAsync is what I need... but I can't seem to get it working. I replaced the update section of my question to show my latest attempt. – Kirsten Jul 22 '15 at 11:21
  • Edited question accordingly – Kirsten Jul 22 '15 at 22:02
  • 1
    The issue is that you are sending "$orderby=Date desc". Since Item does not have a Date field then you can't order by it. Try ordering by a field the entity does have. – Shaun Wilde Jul 23 '15 at 00:24
1

You can use a TaskCompletionSource object that you can resolve with the result or error callback. I'm not sure what the signature of the error callback is so that part probably wont work.

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;
}

You can then return the Task object that it creates which you can use for async things. I'm not really sure about what logic you are trying to implement because your question is not very detailed but you can use the method with the await command because it returns a Task object like the following.

private async void FetchAllItemsAsync() 
{
    int totalPages;
    do
    {
        items = await FetchItemsAsync()
        totalPages = (int)Math.Ceiling((double)(items.Count / PageSize));
        _currentPage++;
    } while (_currentPage < totalPages)

}
Sean Dawson
  • 5,587
  • 2
  • 27
  • 34
  • I updated my question with my implementation of your idea. However I get a bad request error – Kirsten Jul 22 '15 at 06:52
  • That exception is returned by the server. Can you have a look at the exception information and see what the server says is wrong with your request? – Sean Dawson Jul 22 '15 at 06:56
  • How do I do that? I did paste the exception into my question. – Kirsten Jul 22 '15 at 07:00
  • I think we are on the right track http://stackoverflow.com/questions/15316613/real-life-scenarios-for-using-taskcompletionsourcet – Kirsten Jul 22 '15 at 07:26