Forum Discussion

Steve_PP's avatar
Steve_PP
Experienced Partner
25 days 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.

  • Raff's avatar
    Raff
    Experienced Partner

    Hi Steve

     

    Many thanks for providing the code.  My app is still working but if I ever need to get new tokens or install the app on another machine I'll run into issues and hence very interested in your solution.

     

    I have replaced the old GetAuthorizationCode code with yours, however on my machine it hangs on the  "await webView2.EnsureCoreWebView2Async(null);".  

     

    I'm guessing it is an environmental issue but if you or anyone else has any suggestion sit would be greatly appreciated.

     

    Thanks

     

  • Here's my VB.Net version of Steve's module for accessing Auth Code using WebView2
    -----------------------------------------------------------------------------------------------------

    Module OAuthLogin

     

    Private Const csOAuthServer As String = "https://secure.myob.com/oauth2/account/authorize/"
    Private Const csOAuthLogoff As String = "https://secure.myob.com/oauth2/account/LogOff/"
    Private Const csOAuthScope As String = "CompanyFile"

       

    Private MyCurrentURI As String = ""
    Private MyUrl As String = ""
    Private MyAuthCode As String = ""

    Private WithEvents webVW2 As New Microsoft.Web.WebView2.WinForms.WebView2

       

    Public Function GetAuthorizationCode_WEB2(config As IApiConfiguration) As String
            Try
                MyUrl = String.Format("{0}?client_id={1}&redirect_uri={2}&scope={3}&response_type=code", csOAuthServer, config.ClientId, HttpUtility.UrlEncode(config.RedirectUrl), csOAuthScope)
                MyCurrentURI = ""

                Dim frm As Form = New Form()
                frm.ShowIcon = False

                webVW2 = New Microsoft.Web.WebView2.WinForms.WebView2
                webVW2.Dock = DockStyle.Fill
                frm.Controls.Add(webVW2)

                webVW2.Source = New Uri(MyUrl)

                frm.Size = New Size(800, 600)
                frm.ShowDialog()

                Return MyAuthCode

            Catch ex As Exception
                Return MyAuthCode
            End Try
        End Function


        Private Function ExtractAuthorizationCode(ByVal uri As String) As String
            Try
                Dim uriObj As New Uri(uri)
                Dim queryParams = HttpUtility.ParseQueryString(uriObj.Query)
                Return queryParams("code")
            Catch ex As Exception
                Return ""
            End Try
        End Function

     

     

      Private Sub webVW2_CoreWebView2InitializationCompleted(sender As Object, e As CoreWebView2InitializationCompletedEventArgs) Handles webVW2.CoreWebView2InitializationCompleted
            Try
                If e.IsSuccess Then
                    webVW2.CoreWebView2.Navigate(MyUrl)
                End If
            Catch ex As Exception
            End Try
        End Sub

     

    Private Sub webVW2_NavigationStarting(sender As Object, e As CoreWebView2NavigationStartingEventArgs) Handles webVW2.NavigationStarting
            Try
                MyCurrentURI = e.Uri
            Catch ex As Exception
            End Try
        End Sub

     

    Private Sub webVW2_NavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs) Handles webVW2.NavigationCompleted
            Try
                If MyCurrentURI.Contains("code=") Then
                    MyAuthCode = ExtractAuthorizationCode(MyCurrentURI)
                End If
            Catch ex As Exception
            End Try
        End Sub

     

    End Module

  • eJulia's avatar
    eJulia
    Trusted Partner

    I am still mystified as to why people keep on about IE versions and Edge. I am trying to make a WinForms app work and the relevant thing is what dlls are needed and how they are used, not what browser I, or my users, use. I suspect the issue is that the perpetrators did not understand that fact.

     

    I suspect that is an issue about where that //desktop redirect goes but the real meat is not that but the data that is returned. I have two apps that use the same code. One now works having disabled the error that arises from the redirect, but on my test PC as opposed to my development PC it launches Edge, quite unnecessarily as far as I can see. 

     

    Can anyone explain what that redirect is meant to do? I have never understood that. There is no reason I can see why an actual external browser should come into it. 

     

    The other app fails, despite using the exact same code (same source files!) The only difference, apart from addressing a wider set of end points which should not be relevant, is a different code and secret. 

     

    Off to examine possible differences in their csproj files.

    • The_Doc's avatar
      The_Doc
      Ultimate Partner

      Hi eJulia 

       

      I think this talk about IE versions and Edge browsers may just be a red herring - see my earlier post. I am awaiting until MYOB actually roll out the changes where ( IE is no longer supported). 

       

      However, to explain some of your questions - and I am no expert in OAuth - I have just picked it up as required - the 1st call you need to make to the MYOB secure token server is an http GET call - and ( though I have no app in VB.NET or VC#.NET - I am proficient in both these languages though prefer VB.NET because it is easier)  - but to make this initial call to actually get your Access_code you need to say "Hey MYOB I am a registered developer here is my ident" - and MYOB will say - yep know who you are you logged onto my.myob.com.au a few minutes ago so here is your access_code ( or please authenticate who you are - here is a logon to myob - and you get a logon popup) 

       

      You can't do all this in a VB.NET/VC#.NET WinForm directly as it is a GET HTTP call.

       

      You can either embed into your Form a webview browser - which then does this for you or you can actually use HttpWebRequest class to perform a request and retrieve a response from a given URL ( however, this likely wouldn't handle the MYOB logon if required).

       

      Hence the talk about web browsers and the default in VB.NET, VC#.NET and MSAccess are IE based ones. and hence the talk about webbrowser2, webviewer2, Antview2 which are Edge browser based.

       

      Or you can just take the initial http GET call and manually put the url into a browser - IE, Opera, Edge etc - and in the past the return was a blank page - and you could capture the page source code using a method Object.Document.....   and inbedded in that returned payload was the access_code.

       

      That stopped working - seems MYOB weren't sending this json payload anymore - hence the error when your redirect url was http://desktop - but interestingly there wa a return url from the server - but to capture it you needed to know a different method - it wasn't in the document returned (json formatted page) - it was in a url.

       

      It can be complicated to understand - now the redirect url has to match what you put on the developers tab in your developer licencing - there is a field called redirect_url - and this typically is used to send the http GET call back to where it came from ( hence preventing a malicious intercept)  - but this can be be a formatted webcart page or similar - but if you don't have such a page then the default is http://desktop - which as mentioned had a formatted payload and didn't cause an error.

       

      Probably confused you by now.

       

      Why I couldn't understand why this didn't seem logical about the Edge browser - and Tana in api tech kept saying you need a Webviewer2 browser - for 2 weeks - was that you actually didn't.

       

      Any browser worked and still does - what MYOB had done is stuffed up the payload so now you got an error - which we all thought was "an error" - and the codes were wrong  - until I manually took the returned access_code and pasted it to my refresh code and it worked.

       

      Hope all this explanation helps - it really is a mess up by MYOB and I still advocate at the present - keep your code as is - any browser will work - do 2 things

       

      1. Alter the method used to capture the returned string which contains a legit access_code
      2. Alter the extraction of your access_code, access_token and refresh_token ( as these have all changed in size, look and position in the returned payload

       

      And you will find you will be back online.

       

      The Doc

       

       

      https://stackoverflow.com/questions/92522/http-get-in-vb-net?rq=4

      • The_Doc's avatar
        The_Doc
        Ultimate Partner

        Hi eJulia 

         

        Forgot to mention the link at the bottom talks about using the HTTPWeb class in VB.NET.

         

        The Doc

  • eJulia's avatar
    eJulia
    Trusted Partner

    Interesting stackoverflow post. It might have been about using VB.NET but the code example was C# so I tried it out and got back a page of junk from MYOB which was a dreadful ad for the whole API concept, raving on and listing all the end points as if the recipient was a newby user who might be about to start developing an add-on. Certainly not stuff to throw at add-on users as if they were beginner developers.

     

    It possibly sheds light on the thinking behind all this. Zero has been rated as having better add-ons so someone in marketing, who has no idea what change management is, and thinks everything that comes in via the internet has a browser at the other end, has provoked this awful process round behind the back of those in MYOB who know anything about software and change management etc. It is the kindest theory I have been able to formulate so far!

     

    • The_Doc's avatar
      The_Doc
      Ultimate Partner

      Hi eJulia 

       

      The article jumps between Vb & C# - but there is a bit in there about 'Use the WebRequest class"  which is what the MYOB SDK uses to make their call - and where it scrambles in their code.

       

      I am now just coming up for air after getting all my clients back online - ALL are MS Access databases - the 1st one in which I panicked I converted to AntView2 - then quietly analysed things and realised the problem WASN'T  the browser - the rest I have left on the std MS Access WebBrowser and altered my code ( the fixes were minor in essence) - .. .all are back online and running perfectly.

       

      I found a very old version of the old VB.NET utility MYOB rolled out in about 2013 ( they also had a VC#.Net version) - that emulates the process of getting data from a clients database.

       

      It was built initially for local based datafiles which was very common 2013 and few were online.

       

      I remember getting the VB version going but not the VC#  -but being neither VB or VC# proficient then shelved both and went MS Access from the ground.

       

      Having now proven the browser is a red herring ( which I actually told Tana ( in MYOB API)  in the middle of this mess - and MYOB had rolled out the Edge browser fix - no matter which browser I used the returned payload was the same.

       

      Sorry - long story - anyways I have found an old version of the VB.NET utility (2013) Visual Studio 2013) version and converted and updated it to VS 2022 and it is working. The SDK updated fine.

       

      And it hangs as soon as it goes to get the Access Code - web browser pops up exactly the same error as I was getting manually - and it uses the WebRequest Class in VB.NET - and I think this is the problem.

       

      When I re-run - the browser just gives an error and fails to run.

       

      eJulia- the reason you got a jumble back is not a failure - if you read my posts you will gleam why - you are looking at the returned document/Source and yes it is rubbish!!!!! The code you need is no longer embedded in the page returned and this is where I think things have gone wrong - it use to be embedded in a json formatted payload that loaded as a web page = BLANK but by examining the "Document" component returned you could search for "code=" but this is no longer happening - the  "Document" method no longer is useful - it is the returned URL title you need and that is where the code is. 

       

      POST this in any browser - any EDGE/CHROME/OPERA/FOX  - 

      https://secure.myob.com/oauth2/account/authorize?client_id=[<< developer Id>>]&redirect_uri=http://desktop&response_type=code&scope=CompanyFile

       

      and you will get an access code returned. That is what you need to capture - it gives an error page - and if you right click and examine the source it is gobblety gooch - but before this mess contained the embedded code=....

       

      Your VB, VC# calls now have to examine NOT the returned webbrowser.Document method but the Webbrowser.URL ( or similar) I haven't quite found the method to get your code.

      I repeat THE BROWSER TYPE IS IRRELVANT AND I BELIEVE THIS IS A RED HERRING.

       

      In MS Access it use to be 

       

      Me.WebView.Object.Document.documentElement.outerHTML

       

      and now is 

      Me.WebView.Object.Document.URL

       

      Simple - found by trial and error and I believe this will be the anser in VB & VC# - I will let you know my outcome.

       

      The Doc

       

       

       

      • Steve_PP's avatar
        Steve_PP
        Experienced Partner

        Hi The_Doc eJulia 

         

        I already posted the solution for .NET based on The_Doc findings about 9 days ago:

         

        1. Change webB.DocumentText to webB.Url.AbsoluteUri
        2. Replace ExtractSubstring with:

                private static string ExtractAuthorizationCode(string uri)
                {
                    var uriObj = new Uri(uri);
                    var queryParams = HttpUtility.ParseQueryString(uriObj.Query);
                    return queryParams["code"];
                }

         

        I understand finding solutions in these threads is tricky 🙂