Search code examples
c#oauthasync-awaitmyobtaskcompletionsource

MYOB oauthService.GetTokensAsync not showing dialog


As a result of my question about TaskCompletionSource

I tried a similar technique for getting the tokens

private t.Task<OAuthTokens> GetOAuthTokens()
    {
        var tcs = new t.TaskCompletionSource<OAuthTokens>();
        t.Task.Run(
            async () =>
            {
                var oauthService = new OAuthService(_configurationCloud);
                var code = OAuthLogin.GetAuthorizationCode(_configurationCloud);
                var response = await oauthService.GetTokensAsync(code);

                tcs.SetResult(response);
            });
        return tcs.Task;
    }

calling this using

var task1 = GetOAuthTokens();
_oAuthKeyService.OAuthResponse = task1.Result;

However the program locks up when I run it.

The following works OK

        var oauthService = new OAuthService(_configurationCloud);
        var code = OAuthLogin.GetAuthorizationCode(_configurationCloud);  // causes a login dialog
        var tokens = oauthService.GetTokens(code);
        _oAuthKeyService.OAuthResponse = tokens;

and brings up the authorisation dialog box.


Solution

  • When I answered your previous question I assumed you had a requirement to use a TaskCompletionSource object so I am sorry if this has sent you in the wrong direction. As Paulo has said you don't normally need to use TaskCompletionSource with async/await code but you do need to understand a bit more how to use it.

    Calling Result on a Task will cause that thread to block, now in a non-UI thread this isn't such an issue (just not ideal) but in a UI thread this will effectively stop your UI from responding until the task has completed, assuming it just doesn't stop completely due to a deadlock.

    The thing is to learn how to use async/await in a UI environment because to get it to work you have too use async/await everywhere otherwise you will end up trying to use Task.Result to access your data and get a blocked UI thread for your troubles.

    This is a good starter guide - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

    Now I assume you are pulling the code from the page like this (cobbled from code samples on GitHub) and then fetching the tokens.

    private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        var content = webBrowser1.DocumentText;
        var regex = new Regex(@"\<title\>(.+?)=(.+?)\</title\>");
        var match = regex.Match(content);
        if (!match.Success || match.Groups.Count != 3)
            return;
    
        switch (match.Groups[1].Value.ToLowerInvariant())
        {
            case "code": // we have a code
                var code = match.Groups[2].Value;
                var config = new ApiConfiguration(Configuration.ClientId, Configuration.ClientSecret, Configuration.RedirectUrl);
                var service = new OAuthService(config, new WebRequestFactory(config));
                var tokens = service.GetTokensAsync(code).Result; // <= blocking
                _keyService.OAuthResponse = tokens;
                break;
    
            case "error": // user probably said "no thanks"
                webBrowser1.Navigate(_logoffUri);
                break;
        }
    } 
    

    however your code is blocking on the .Result

    What you need to do is use async/await all the way up however when you use await it complains about the lack of async on the method, so, just add it; yes this is allowed and there are many articles and blog posts about this that catch people new to async/await in winforms/wpf UI.

    e.g.

    // we add async to the callback - yup it's allowed 
    private async void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        var content = webBrowser1.DocumentText;
        var regex = new Regex(@"\<title\>(.+?)=(.+?)\</title\>");
        var match = regex.Match(content);
        if (!match.Success || match.Groups.Count != 3)
            return;
    
        switch (match.Groups[1].Value.ToLowerInvariant())
        {
            case "code": // we have a code
                var code = match.Groups[2].Value;
                var config = new ApiConfiguration(Configuration.ClientId, Configuration.ClientSecret, Configuration.RedirectUrl);
                var service = new OAuthService(config, new WebRequestFactory(config));
                var tokens = await service.GetTokensAsync(code); // <= now we can use await here => non-blocking
                _keyService.OAuthResponse = tokens;
                break;
    
            case "error": // user probably said "no thanks"
                webBrowser1.Navigate(_logoffUri);
                break;
        }
    } 
    

    I've uploaded the code as gist I hope it helps