Forum Discussion

Steve_PP's avatar
Steve_PP
Experienced User
1 year ago

C# WebBrowser -> WebView2 solution

Hi

 

Here is a solution for those with C#.NET apps that need to update the internal browser their desktop app invokes when OAuth is required due to breaking changes enforced by MYOB in Aug 2024. In summary, you need to replace WebBrowser with WebView2 (NuGet: Microsoft.Web.WebView2)

 

If you used the MYOB template of many years ago, you can replace OAuthLogin.GetAuthorizationCode with this:

 

        public static async Task<string> GetAuthorizationCode(IApiConfiguration config)
        {
            // Format the URL for the OAuth server login
            string url = string.Format("{0}?client_id={1}&redirect_uri={2}&scope={3}&response_type=code",
                                       CsOAuthServer, config.ClientId, HttpUtility.UrlEncode(config.RedirectUrl), CsOAuthScope);

            // Create a new form with WebView2
            var frm = new Form();
            var webView2 = new WebView2
            {
                Dock = DockStyle.Fill
            };
            frm.Controls.Add(webView2);

            // Set up a TaskCompletionSource to signal when the form can close
            var tcs = new TaskCompletionSource<string>();

            // Subscribe to NavigationCompleted to capture the OAuth redirect
            webView2.CoreWebView2InitializationCompleted += (sender, args) =>
            {
                if (args.IsSuccess)
                {
                    // Navigate to the OAuth login URL
                    webView2.CoreWebView2.Navigate(url);
                }
            };

            // Capture the URL in NavigationStarting
            webView2.NavigationStarting += (sender, args) =>
            {
                // Store the URI when navigation starts
                _currentUri = args.Uri;
            };

            // Use NavigationCompleted to check if the URI contains the authorization code
            webView2.NavigationCompleted += (sender, args) =>
            {
                if (_currentUri.Contains("code="))
                {
                    // Extract the authorization code from the URL
                    string code = ExtractAuthorizationCode(_currentUri);

                    // Signal the TaskCompletionSource that the form can close with the code
                    tcs.SetResult(code);

                    // Close the form
                    frm.Invoke((MethodInvoker)(() => frm.Close()));
                }
            };

            // Initialize WebView2 control asynchronously
            await webView2.EnsureCoreWebView2Async(null);

            frm.Size = new Size(800, 600);
            frm.Show();

            // Wait until the TaskCompletionSource is signaled (form is closed)
            string authCode = await tcs.Task;

            return authCode;

        }

 

Notes:

 

1. Change the call to the updated method to include an await:

_oAuthKeyService.OAuthResponse = oauthService.GetTokens(await   OAuthLogin.GetAuthorizationCode(_configurationCloud));

 

2. Make the containing method async

private async void Login()

 

3. You will probably get a brief flash of 'Failed navigation' as the http://desktop redirect page is shown before the form drops away. Maybe someone can sort that. Ironically, I don't need this code any more based on what I have been forced to learn this weekend.

38 Replies

  • Steve_PP's avatar
    Steve_PP
    Experienced User
    1 year ago

    Good to hear Trish.

     

    I think there is still a problem registering new apps and have sent a query to the the API team. In summary, the auth process works, you get a code and with that can successfully retrieve OAuth access and refresh tokens but you end up with a 'Forbidden' response when trying to use the access token enumerate companies (either manually or via the SDK.)

     

    However, if you have an older app key/secret (like from about 2 weeks ago) everything seems to work fine.

  • Trish_Lee's avatar
    Trish_Lee
    Experienced User
    1 year ago

    Hi Steve,

    You are an absolutely amazing person, we owe you an enormous amount of respect and gratitude, we now have our app working again.

    Thank you 

    Trish

  • Steve_PP's avatar
    Steve_PP
    Experienced User
    1 year ago

    Hi Trish, sure.

     

            // Helper function to extract the authorization code from the URL
            private static string ExtractAuthorizationCode(string uri)
            {
                var uriObj = new Uri(uri);
                var queryParams = HttpUtility.ParseQueryString(uriObj.Query);
                return queryParams["code"];
            }

     

    Here's the whole class:

     

        public static class OAuthLogin
        {
            private const string CsOAuthServer = "https://secure.myob.com/oauth2/account/authorize/";
            private const string CsOAuthScope = "CompanyFile";
            private static string _currentUri = string.Empty;

            public static async Task<string> GetAuthorizationCode(IApiConfiguration config)
            {
                // Format the URL for the OAuth server login
                string url = string.Format("{0}?client_id={1}&redirect_uri={2}&scope={3}&response_type=code",
                                           CsOAuthServer, config.ClientId, HttpUtility.UrlEncode(config.RedirectUrl), CsOAuthScope);

                // Create a new form with WebView2
                var frm = new Form();
                var webView2 = new WebView2
                {
                    Dock = DockStyle.Fill
                };
                frm.Controls.Add(webView2);

                // Set up a TaskCompletionSource to signal when the form can close
                var tcs = new TaskCompletionSource<string>();

                // Subscribe to NavigationCompleted to capture the OAuth redirect
                webView2.CoreWebView2InitializationCompleted += (sender, args) =>
                {
                    if (args.IsSuccess)
                    {
                        // Navigate to the OAuth login URL
                        webView2.CoreWebView2.Navigate(url);
                    }
                };

                // Capture the URL in NavigationStarting
                webView2.NavigationStarting += (sender, args) =>
                {
                    // Store the URI when navigation starts
                    _currentUri = args.Uri;
                };

                // Use NavigationCompleted to check if the URI contains the authorization code
                webView2.NavigationCompleted += (sender, args) =>
                {
                    if (_currentUri.Contains("code="))
                    {
                        // Extract the authorization code from the URL
                        string code = ExtractAuthorizationCode(_currentUri);

                        // Signal the TaskCompletionSource that the form can close with the code
                        tcs.SetResult(code);

                        // Close the form
                        frm.Invoke((MethodInvoker)(() => frm.Close()));
                    }
                };

                // Initialize WebView2 control asynchronously
                await webView2.EnsureCoreWebView2Async(null);

                frm.Size = new Size(800, 600);
                frm.Show();

                // Wait until the TaskCompletionSource is signaled (form is closed)
                string authCode = await tcs.Task;

                return authCode;

            }

            // Helper function to extract the authorization code from the URL
            private static string ExtractAuthorizationCode(string uri)
            {
                var uriObj = new Uri(uri);
                var queryParams = HttpUtility.ParseQueryString(uriObj.Query);
                return queryParams["code"];
            }

        }

  • Trish_Lee's avatar
    Trish_Lee
    Experienced User
    1 year ago

    Hi Steve_PP ,

    Can I bother you for the 

    ExtractAuthorizationCode proceedure, it would help enormously.

     

    Thanks

    Trish