Forum Discussion

Steve_PP's avatar
Steve_PP
Experienced User
6 months 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.

  • Steve_PP's avatar
    Steve_PP
    Experienced User

    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.

    • Steve_PP's avatar
      Steve_PP
      Experienced User

      Just to complete this thread, the issue with new app keys is that you can only create 2 without getting MYOB involved to manually activate them.

    • Trish_Lee's avatar
      Trish_Lee
      Experienced User

      Hi Steve_PP 

       

      It is all working ok as we don't have a new app and are using the SDK.

      We are just investigating what other WebView files we need to include for the client installation and then we should be good to go with updating our clients which will be an enormous relief.

      You have been extremely generous, and I cannot thank you enough.

      Trish

       

  • Trish_Lee's avatar
    Trish_Lee
    Experienced User

    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

  • Trish_Lee's avatar
    Trish_Lee
    Experienced User

    Hi Steve_PP ,

    Can I bother you for the 

    ExtractAuthorizationCode proceedure, it would help enormously.

     

    Thanks

    Trish

    • Steve_PP's avatar
      Steve_PP
      Experienced User

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

          }

      • Digiwise's avatar
        Digiwise
        Trusted User

        Hi Steve and other developers.

         

        This thread was very helpful in getting my solution running again.  Thank you for sharing the helpful information!

         

        Just a note that I ran into a problem when my app was installed in the Program Files (x86) folder.  WebView2 needs to create a user data folder and it can't create that folder under Program Files.  The version I am using doesn't give an error message when it can't create the folder - it just didn't display the browser.

         

        I found the fix at https://github.com/MicrosoftEdge/WebView2Feedback/issues/297 - there is code there to set the data folder:

         

        public BrowserForm(string url = null)
                {
                    InitializeComponent();
                    InitializeBrowser(url);
        
                }
        private async void InitializeBrowser(string url = null)
                {
                    var userDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\SoftwareName";
                    var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
                    await webView21.EnsureCoreWebView2Async(env);
                    webView21.Source = new UriBuilder(url ?? Settings.Default.HomePage).Uri;
                }

         

        Hope this helps!

         

        Clancy