Results 1 to 4 of 4

Thread: Visual Basic .NET - Using a WebView2 to Render UI for Desktop Applications

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,985

    Visual Basic .NET - Using a WebView2 to Render UI for Desktop Applications

    In case you missed it, Google won the browser wars although FireFox still nips at their heels. Most every browser now uses Chromium as the base web browser. Before this more universal approach to browsers, there were many shim libraries like jQuery that handled cross-browser capability, but now we really don't have that problem anymore; this makes front-end web development much easier for developers.

    So how does this relate to desktop development and VB.NET specifically? Well as a result of Internet Explorer being dropped and Edge being Microsoft's browser, they also released a new desktop control to replace the old WebBrowser called WebView2 (documentation). What is great with the WebView2 is that not only can you send information to the browser from your application like you could in the old WebBrowser but you can send information from the browser back to your application. This means that we can display all of our UI in a WebView2 using all the niceties that front-end web development bring us, but have all of the business logic and simplicity of VB.NET.

    The first thing you will need to do is install the WebView2 NuGet package, do this from your WinForm application project:
    1. From the solution explorer, right-click on your project.
    2. Click on Manage NuGet Packages.
    3. From the NuGet Package Manager, click on the Browse tab.
    4. Search for: Microsoft.Web.WebView2
    5. Install the most recent version.
    6. Build your project (Ctrl + Shift + B or Build > Build Solution).

    After that the WebView2 control should show up in your toolbox and you can drag and drop the control onto a form.

    I am also using Newtonsoft.Json to handle the data as JSON, follow those same steps above for the Newtonsoft.Json package.

    The second thing you will need to do is essentially setup a mini web server in your desktop project. This is a major step, so I going to do my best to explain how this work.

    I would suggest setting up a file structure in your project to look like this:
    Code:
    /
    └── my-app
        ├── WebAssets
        ├── WebPages
        └── WebServer
            ├── Controllers
            └── Models
                ├── WebViewRequest.vb
                └── WebViewResponse.vb
    • WebAssets would hold things like your third party libraries (Bootstrap) or custom shared code.
    • WebPages will hold only folders and the sub-folders will hold the actual HTML, CSS, and JavaScript pages.
    • The WebServer > Controllers will have files that represent the controllers which handle things like routing.
    • The WebServer > Models hold the request and response models, essentially replicating an HTTP request/response.


    Because I will be showing you how to setup this mini web server using a MVC pattern, let's assume that you have: www.something.com/Users/Index.html and www.something.com/Users/Update.html then this would live in WebPages > Users > Index.html and WebPages > Users > Update.html respectively.

    Because I have already provided you with two code files, WebViewRequest.vb and WebViewResponse.vb, I will go ahead and provide you with their code:
    Code:
    Imports Newtonsoft.Json
    
    Public Class WebViewRequest
    
        Public Property Controller As String
    
        Public Property Route As String
    
        Public Property Data As String
    
        Public Function GetModelFromJson(Of T)() As T
            If (String.IsNullOrWhiteSpace(Data)) Then
                Throw New ArgumentNullException(NameOf(Data))
            End If
    
            Dim model = JsonConvert.DeserializeObject(Of T)(Data)
            Return model
        End Function
    
        Public Function ToJson() As String
            Return JsonConvert.SerializeObject(Me)
        End Function
    
    End Class
    And:
    Code:
    Imports Newtonsoft.Json
    
    Public Class WebViewResponse
    
        Public Property Status As Integer
    
        Public Property Body As String
    
        Public Shared Function BadRequestResponse(Optional message As String = "") As WebViewResponse
            Return New WebViewResponse() With {
                .Status = 400,
                .Body = message
            }
        End Function
    
        Public Shared Function NotFoundResponse() As WebViewResponse
            Return New WebViewResponse() With {
                .Status = 404,
                .Body = String.Empty
            }
        End Function
    
        Public Shared Function InternalServerErrorResponse() As WebViewResponse
            Return New WebViewResponse() With {
                .Status = 500,
                .Body = String.Empty
            }
        End Function
    
        Public Function ToJson() As String
            Return JsonConvert.SerializeObject(Me)
        End Function
    
    End Class
    Continued in next post...
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  2. #2

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,985

    Re: Visual Basic .NET - Using a WebView2 to Render UI for Desktop Applications

    Now that you have the file structure and web request/response classes, it's time to start working on the controllers. Because the controllers will essentially be doing the same thing just for different pages, I would suggest creating a "base" controller class that looks like this:
    Code:
    Public MustInherit Class BaseController
    
        Public Delegate Function Route(data As WebViewRequest) As WebViewResponse
    
        Public MustOverride ReadOnly Property Routes As Dictionary(Of String, Route)
    
    End Class
    What this does is:
    1. Define a class that can only be inherited.
    2. Define a delegate called "Route" that represents a reference of a function that will take in a request and return a response.
    3. Define a key/value collection of strings and Route delegates.


    By setting it up like this, you can then create controller classes that inherit from BaseController that will build the collection of Routes as well as the corresponding business logic. Here is an example of one of my controllers in a project I'm working on:
    Code:
    Imports MyDomainProject.Services
    Imports MyDomainProject.Loggers
    Imports Newtonsoft.Json
    
    Public Class RolesController
        Inherits BaseController
    
        Private ReadOnly _logger As ILogger
        Private ReadOnly _roleService As RoleService
    
        Public Sub New(logger As ILogger, roleService As RoleService)
            _logger = logger
            _roleService = roleService
            _routes = BuildRoutes()
        End Sub
    
        Private ReadOnly _routes As Dictionary(Of String, Route)
        Public Overrides ReadOnly Property Routes As Dictionary(Of String, Route)
            Get
                Return _routes
            End Get
        End Property
    
        Private Function GetById(data As WebViewRequest) As WebViewResponse
            Dim model As RoleGetByIdModel = Nothing
            Try
                model = data.GetModelFromJson(Of RoleGetByIdModel)()
            Catch ex As Exception
                _logger.LogError($"Unable to parse the incoming request's data to a {NameOf(RoleGetByIdModel)}. Exception:{Environment.NewLine}{ex}", True)
                Return WebViewResponse.BadRequestResponse("The data is not an object with RoleId property.")
            End Try
    
            Dim matchedRole = _roleService.GetRecordById(model.RoleId)
            Dim body As String
            Dim status As Integer
            If (matchedRole IsNot Nothing) Then
                status = 200
                body = JsonConvert.SerializeObject(matchedRole)
            Else
                status = 404
                body = $"The role does not exist with id: {model.RoleId}"
            End If
    
            Return New WebViewResponse() With {
                .Status = status,
                .Body = body
            }
        End Function
    
        Private Function GetAll(data As WebViewRequest) As WebViewResponse
            Dim roles = _roleService.GetAll()
    
            Dim body As String
            Dim status As Integer
            If (roles Is Nothing) Then
                status = 500
                body = "Something went wrong getting the roles"
            Else
                status = 200
                body = JsonConvert.SerializeObject(roles)
            End If
    
            Return New WebViewResponse() With {
                .Body = body,
                .Status = status
            }
        End Function
    
        Private Function BuildRoutes() As Dictionary(Of String, Route)
            Dim routes = New Dictionary(Of String, Route) From {
                {"GetAll", New Route(AddressOf GetAll)},
                {"Get", New Route(AddressOf GetById)}
            }
            Return routes
        End Function
    
    End Class
    Now in my Form's code that uses the WebView2, I can build a controller mapping by defining a key/value collection of strings and BaseControllers. Using the same RolesController example from above:
    Code:
    Private Function BuildControllerMaps() As Dictionary(Of String, BaseController)
        Dim rolesService = New RoleService(My.Application.Database, My.Application.Logger)
        Dim rolesController = New RolesController(My.Application.Logger, rolesService)
        Return New Dictionary(Of String, BaseController) From {
            {"Roles", rolesController}
        }
    End Function
    Now when I go to hit the API endpoint /Roles/Get it will find the Roles key from the BuildControllerMaps method and the Get key from the RolesController's BuildRoutes method and so the function that will be executed is RolesController.GetById.

    Continued in next post...
    Last edited by dday9; Feb 12th, 2024 at 12:47 PM.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  3. #3

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,985

    Re: Visual Basic .NET - Using a WebView2 to Render UI for Desktop Applications

    Now that you have the controllers setup, it's time to start working on the actual web pages. I mentioned earlier that the WebPages folder will only hold folders and that these sub-folders will hold the actual HTML, CSS, and JavaScript pages.

    So continuing with the "Roles" example used earlier and assuming I want a search page, create page, and update page, then I would probably setup the following file structure:
    Code:
    /
    └── my-app
        └── WebPages
            └── Roles
                ├── Index.html
                └── Upsert.html
    Now this is very important because if you miss this step then your HTML pages cannot be loaded into the WebView2. After you add your HTML (or CSS, JavaScript, etc.) file, you need to:
    1. Open the file properties.
    2. Change the Build Action to Content.
    3. Change the Copy to Output Directory to Copy if newer.


    There are a couple of key points for the communicate between your webpages and VB.NET code. The first is that if you want to send data from your webpage to your VB.NET code, you will need to invoke the JavaScript window.chrome.webview.postMessage method:
    Code:
    window.chrome.webview.postMessage('...');
    And if you want to send data from your VB.NET code to your webpage, you will need to invoke the VB.NET CoreWebView2.ExecuteScriptAsync method to execute JavaScript code:
    Code:
    Await MyWebView2.CoreWebView2.ExecuteScriptAsync("console.log('...');")
    This is important to know because we need to essentially send requests asynchronously from our JavaScript code and then wait on a response from our VB.NET code. This means that you cannot use typical AJAX requests like fetch. Instead, what I would suggest doing is creating a shared JavaScript function that essentially dispatches an event that indicates we have received data from our VB.NET code:
    Code:
    const dispatchMessageReceivedEvent = (request, response) => {
        const messageReceivedEvent = new CustomEvent('messageReceived', {
            detail: { request, response },
            bubbles: true,
            cancelable: true
        });
        window.dispatchEvent(messageReceivedEvent);
    };
    Now we can handle the messageReceived event and do something with the data being returned.

    Here is an example of my roles search page in a project I'm working on. I am using Bootstrap 5 and my file structure looks like this:
    Code:
    /
    └── my-app
        ├── WebAssets
        │   ├── Bootstrap
        │   │   ├── CSS
        │   │   │   └── bootstrap.min.css
        │   │   └── JavaScript
        │   │       └── bootstrap.bundle.min.js
        │   ├── Images
        │   │   └── loading.gif
        │   └── Shared.js
        ├── WebPages
        │   └── Roles
        │       └── Index.html
        └── WebServer
            ├── Controllers
            │   ├── BaseController.vb
            │   └── RolesController.vb
            └── Models
                ├── WebViewRequest.vb
                └── WebViewResponse.vb
    I will not be including any of the files in my WebAssets except for Shared.js, but you can find Bootstrap 5 here and you can find a loading gif using a search engine.
    This is what my WebAssets/Shared.js file looks like:
    Code:
    const dispatchMessageReceivedEvent = (request, response) => {
        const messageReceivedEvent = new CustomEvent('messageReceived', {
            detail: { request, response },
            bubbles: true,
            cancelable: true
        });
        window.dispatchEvent(messageReceivedEvent);
    };
    
    const buildWebRequest = (controller, route, data) => {
        return {
            Controller: controller,
            Route: route,
            Data: data
        };
    };
    
    const toggleLoader = isVisible => {
        let overlay = document.querySelector('.overlay-loading');
        if (isVisible) {
            if (!overlay) {
                overlay = document.createElement('div');
                overlay.classList.add('position-fixed', 'top-0', 'start-0', 'vw-100', 'vh-100', 'd-flex', 'justify-content-center', 'align-items-center', 'overlay-loading', 'opacity-50');
                overlay.style.zIndex = '9999';
    
                const image = document.createElement('img');
                image.setAttribute('src', 'https://app-assets.local/Images/loading.gif');
                image.classList.add('img-fluid', 'rounded-circle');
                overlay.appendChild(image);
                document.body.appendChild(overlay);
            }
            if (overlay.classList.contains('d-none')) {
                overlay.classList.remove('d-none');
            }
        } else if (overlay && !overlay.classList.contains('d-none')) {
            overlay.classList.add('d-none');
        }
    };
    This is what my WebPages/Roles/Index.html page looks like:
    HTML Code:
    <!doctype html>
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Roles</title>
    
        <link href="https://app-assets.local/Bootstrap/CSS/bootstrap.min.css" rel="stylesheet">
    </head>
    <body class="py-4 bg-body-tertiary">
        <div class="container">
            <h1>Roles</h1>
            <a href="Upsert.html" class="btn btn-primary btn-sm ms-auto">Create</a>
            <hr />
            <div class="dropdown">
                <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdown-sort-by" data-bs-toggle="dropdown" aria-expanded="false">
                    Sort By
                </button>
                <ul class="dropdown-menu" aria-labelledby="dropdown-sort-by">
                    <li>
                        <a class="dropdown-item" href="#" id="button-sort-by-sort-order">Sort Order</a>
                    </li>
                    <li>
                        <a class="dropdown-item" href="#" id="button-sort-by-role-name">Role Name</a>
                    </li>
                </ul>
            </div>
            <div id="list-group-roles" class="list-group mt-3">
            </div>
        </div>
    
        <script src="https://app-assets.local/Bootstrap/JavaScript/bootstrap.bundle.min.js"></script>
        <script src="https://app-assets.local/Shared.js"></script>
        <script>
            const onGetAllMessageReceived = response => {
                const responseBody = response.Body;
                if (response.Status === 200) {
                    const roles = JSON.parse(responseBody);
                    renderRoles(roles);
                } else {
                    alert(responseBody);
                }
            };
    
            const renderRoles = roles => {
                const listGroupRoles = document.querySelector('#list-group-roles');
                listGroupRoles.innerHTML = '';
    
                roles.forEach(role => {
                    const listItemRole = document.createElement('div');
                    listItemRole.className = 'list-group-item d-flex align-items-center';
                    listItemRole.innerHTML = `
                      <div><strong class="sort-order">${role.SortOrder}</strong>. <span class="role-name">${role.RoleName}</span></div>
                      <a href="Upsert.html?id=${role.RoleId}" class="btn btn-primary btn-sm ms-auto">Edit</a>
                    `;
    
                    listGroupRoles.appendChild(listItemRole);
                });
            };
    
            const sortRoles = className => {
                var listGroupRoles = document.getElementById('list-group-roles');
                var listGroupRolesItems = Array.from(listGroupRoles.getElementsByClassName('list-group-item'));
                listGroupRolesItems.sort(function (a, b) {
                    const aElementTextContent = a.querySelector(`.${className}`).textContent;
                    const bElementTextContent = b.querySelector(`.${className}`).textContent;
                    var valA = typeof aElementTextContent === 'string' || aElementTextContent instanceof String ? aElementTextContent.toLowerCase() : parseInt(aElementTextContent, 10);
                    var valB = typeof bElementTextContent === 'string' || bElementTextContent instanceof String ? bElementTextContent.toLowerCase() : parseInt(bElementTextContent, 10);
    
                    if (valA < valB) {
                        return -1;
                    }
                    if (valA > valB) {
                        return 1;
                    }
                    return 0;
                });
    
                listGroupRolesItems.forEach(listGroupRolesItem => {
                    listGroupRoles.appendChild(listGroupRolesItem);
                });
            };
    
            const onSortByRoleOrderClick = e => {
                e.preventDefault();
                sortRoles('sort-order');
            };
    
            const onSortByRoleNameClick = e => {
                e.preventDefault();
                sortRoles('role-name');
            };
    
            const getRoles = () => {
                toggleLoader(true);
                const webMessage = buildWebRequest('Roles', 'GetAll', JSON.stringify({}));
                window.chrome.webview.postMessage(webMessage);
            };
    
            const routeMappings = {
                GetAll: onGetAllMessageReceived
            };
    
            window.addEventListener('load', () => {
                getRoles();
                document.getElementById('button-sort-by-sort-order').addEventListener('click', onSortByRoleOrderClick);
                document.getElementById('button-sort-by-role-name').addEventListener('click', onSortByRoleNameClick);
            }, false);
    
            window.addEventListener('messageReceived', e => {
                toggleLoader(false);
    
                if (e.detail?.request) {
                    const request = e.detail.request;
                    const response = e.detail?.response || {};
                    if (routeMappings.hasOwnProperty(request.Route)) {
                        const callback = routeMappings[request.Route];
                        callback(response);
                    }
                }
            });
        </script>
    </body>
    </html>
    Continued in next post...
    Last edited by dday9; Feb 12th, 2024 at 12:49 PM.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  4. #4

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,985

    Re: Visual Basic .NET - Using a WebView2 to Render UI for Desktop Applications

    In the previous step I included a JavaScript file and HTML file that had some assumptions in it, namely you'll see references to https://app-assets.local/. This is because I setup virtual hosting for my WebView2 which allows me to point a URL (in this case https://app-assets.local/) to a physical directory on the filesystem. To do this, I created a method to "configure" my WebView2 that gets called in the Form's Load event that does the following:
    1. Call the EnsureCoreWebView2Async method, which is something Microsoft requires but to be honest, I'm not sure why.
    2. Assert that the directory I'm pointing to exists.
    3. Set the virtual mapping to the directory using the CoreWebView2.SetVirtualHostNameToFolderMapping method.
    4. Set the CoreWebView2.Settings.IsWebMessageEnabled which allows us to communicate with the WebView2.


    And here is the code:
    Code:
    Private Async Function ConfigureWebView2() As Task
        Await MyWebView2.EnsureCoreWebView2Async(Nothing)
        Dim webAssets = AssertApplicationDirectoryPath("WebAssets")
        MyWebView2.CoreWebView2.SetVirtualHostNameToFolderMapping("app-assets.local", webAssets, CoreWebView2HostResourceAccessKind.Allow)
        MyWebView2.CoreWebView2.Settings.IsWebMessageEnabled = True
    End Function
    
    Private Shared Function AssertApplicationDirectoryPath(ParamArray filePathParts() As String) As String
        Dim paths = {Application.StartupPath}.Concat(filePathParts).ToArray()
        Dim contentPath = Path.Combine(paths)
        If (Not Directory.Exists(contentPath)) Then
            Throw New ArgumentOutOfRangeException(NameOf(filePathParts), $"The following file does not exit: {contentPath}")
        End If
        Return contentPath
    End Function
    Now when the page is loaded in the WebView2, any instance of https://app-assets.local/ knows to point to our application's WebAssets directory. This is useful so that we do not have to rely on relative paths, which in my experience are a pain in the rear to manage.

    Finally, we need to actually load the webpage into our WebView2. This is done by:
    1. Assert that the HTML file we want to load actually exists.
    2. Construct a new URL using the file we want to load's path.
    3. Call the CoreWebView2.Navigate method, passing the URL.


    And here is the code:
    Code:
    Private Sub OpenWebPage(container As WebView2, ParamArray filePathParts() As String)
        Dim fileContentPath = AssertApplicationFilePath(filePathParts)
        Dim url = New Uri(fileContentPath).ToString()
        container.CoreWebView2.Navigate(url)
    End Sub
    
    Private Shared Function AssertApplicationFilePath(ParamArray filePathParts() As String) As String
        Dim filePaths = {Application.StartupPath}.Concat(filePathParts).ToArray()
        Dim contentPath = Path.Combine(filePaths)
        If (Not File.Exists(contentPath)) Then
            Throw New ArgumentOutOfRangeException(NameOf(filePathParts), $"The following file does not exit: {contentPath}")
        End If
    
        Return contentPath
    End Function
    Something else that can be done in between steps 2 and 3 of the last list is to setup virtual hosting for the directory that the HTML file exists so that if you have any resource files (like CSS, JavaScript, etc.) that live in the same directory, those can be referenced too. Here's a modification setting up the virtual hosting tied to https://page-assets.local/:
    Code:
    Private Sub OpenWebPage(container As WebView2, ParamArray filePathParts() As String)
        Dim fileContentPath = AssertApplicationFilePath(filePathParts)
        Dim fileDirectoryPath = Path.GetDirectoryName(fileContentPath)
        Dim url = New Uri(fileContentPath).ToString()
        container.CoreWebView2.SetVirtualHostNameToFolderMapping("page-assets.local", fileDirectoryPath, CoreWebView2HostResourceAccessKind.Allow)
        container.CoreWebView2.Navigate(url)
    End Sub
    Now with all of that being said, here is a slimmed down version of what my MainForm.vb code looks like using the Roles example from earlier:
    Code:
    Public Class FormMain
    
        Private ReadOnly _controllerMaps As Dictionary(Of String, BaseController)
    
        Sub New()
            InitializeComponent()
    
            _controllerMaps = BuildControllerMaps()
        End Sub
    
        Private Async Sub FormMain_Load(sender As Object, e As EventArgs) Handles Me.Load
            Await ConfigureWebView2()
            OpenWebPage(WebView2Container, "WebPages", "Roles", "Index.html")
        End Sub
    
        ' WebView2 specific
        Private Async Sub WebView2Container_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs) Handles WebView2Container.WebMessageReceived
            Await WebView2Container.EnsureCoreWebView2Async(Nothing)
    
            Dim message = e.WebMessageAsJson()
            Dim request As WebViewRequest = Nothing
            Dim response = WebViewResponse.InternalServerErrorResponse()
            Try
                request = JsonConvert.DeserializeObject(Of WebViewRequest)(message)
            Catch
                response = WebViewResponse.BadRequestResponse("The payload is not a valid request.")
            End Try
    
            If (request IsNot Nothing) Then
                If (Not _controllerMaps.ContainsKey(request.Controller)) Then
                    response = WebViewResponse.NotFoundResponse()
                Else
                    Dim controller = _controllerMaps(request.Controller)
                    If (Not controller.Routes.ContainsKey(request.Route)) Then
                        response = WebViewResponse.NotFoundResponse()
                    Else
                        Dim route = controller.Routes(request.Route)
                        response = route(request)
                    End If
                End If
            End If
    
            Await WebView2Container.CoreWebView2.ExecuteScriptAsync($"dispatchMessageReceivedEvent({request.ToJson()}, {response.ToJson()});")
        End Sub
    
        Private Async Function ConfigureWebView2() As Task
            Await WebView2Container.EnsureCoreWebView2Async(Nothing)
            Dim webAssets = AssertApplicationDirectoryPath("WebAssets")
            WebView2Container.CoreWebView2.SetVirtualHostNameToFolderMapping("app-assets.local", webAssets, CoreWebView2HostResourceAccessKind.Allow)
            WebView2Container.CoreWebView2.Settings.IsWebMessageEnabled = True
        End Function
    
        Private Sub OpenWebPage(container As WebView2, ParamArray filePathParts() As String)
            Dim fileContentPath = AssertApplicationFilePath(filePathParts)
            Dim fileDirectoryPath = Path.GetDirectoryName(fileContentPath)
            Dim url = New Uri(fileContentPath).ToString()
            container.CoreWebView2.SetVirtualHostNameToFolderMapping("page-assets.local", fileDirectoryPath, CoreWebView2HostResourceAccessKind.Allow)
            container.CoreWebView2.Navigate(url)
        End Sub
    
        Private Shared Function AssertApplicationFilePath(ParamArray filePathParts() As String) As String
            Dim filePaths = {Application.StartupPath}.Concat(filePathParts).ToArray()
            Dim contentPath = Path.Combine(filePaths)
            If (Not File.Exists(contentPath)) Then
                Throw New ArgumentOutOfRangeException(NameOf(filePathParts), $"The following file does not exit: {contentPath}")
            End If
    
            Return contentPath
        End Function
    
        Private Shared Function AssertApplicationDirectoryPath(ParamArray filePathParts() As String) As String
            Dim paths = {Application.StartupPath}.Concat(filePathParts).ToArray()
            Dim contentPath = Path.Combine(paths)
            If (Not Directory.Exists(contentPath)) Then
                Throw New ArgumentOutOfRangeException(NameOf(filePathParts), $"The following file does not exit: {contentPath}")
            End If
            Return contentPath
        End Function
    
        ' WebServer specific
        Private Function BuildControllerMaps() As Dictionary(Of String, BaseController)
            Dim rolesService = New RoleService(My.Application.Database, My.Application.Logger)
            Dim rolesController = New RolesController(My.Application.Logger, rolesService)
            Return New Dictionary(Of String, BaseController) From {
                {"Roles", rolesController}
            }
        End Function
    
    End Class
    What you'll see is the following:
    1. When the Form is created, it will build the controller mappings (like /Roles/GetAll).
    2. When the Form loads, it configures the WebView2 and opens the /WebPages/Roles/Index.html page.
    3. When the webpage is loaded, it makes a request to /Roles/GetAll with essentially an emtpy body.
    4. The WebView2's WebMessageReceived is handled and we try the following:
      1. Call the EnsureCoreWebView2Async method (again, I don't know why this is required).
      2. Get the request from the window.chrome.webview.postMessage call.
      3. Try to parse the request as a WebViewRequest and if it fails, simply return a 400 bad request response.
      4. Next, try to get the controller by the incoming request's controller property.
      5. If it cannot find a controller then return a 404 not found response.
      6. If it can find the controller, try to get the route from the matched controller.
      7. If it cannot find a route, then return the 404 response.
      8. If it can find the route, then call the Route delegate method passing in the request.
      9. At the end of the method, call the dispatchMessageReceivedEvent passing the request and response as the arguments.
    5. The messageReceived JavaScript event is then triggered that will ultimately call the onGetAllMessageReceived method.


    And that's that. You can build very rich UI desktop applications using front-end web development and the traditional VB.NET code. Eventually I will include a VB.NET Codebank Submission with a sample project.

    Edit - And here is the codebank link: https://www.vbforums.com/showthread....p-UI&p=5633447
    Last edited by dday9; Feb 20th, 2024 at 02:32 PM.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width