Integrar WebView2 no .NET Framework 4.6 WinForms – sem NuGet e sem pipeline de build moderna

Um relatório em PDF deve ser substituído por uma interface web interativa em um sistema ERP baseado em WinForms sem pipeline de build. Um relatório sobre 4 obstáculos que podem surgir nesse processo.

Existem projetos nos quais gostaríamos de simplesmente digitar Install-Package Microsoft.Web.WebView2 no Console do Gerenciador de Pacotes e pronto. Mas também há projetos como o nosso: um ERP desenvolvido ao longo de anos com base em um framework WinForms proprietário no .NET Framework 4.6, distribuído via Terminal Server (RDS) para dezenas de usuários simultaneamente. Para formulários customizados, sem fluxo de trabalho moderno com NuGet e sem acesso livre à estrutura do projeto no Visual Studio. A tarefa: substituir relatórios impressos há anos por interfaces interativas em Angular – embutidas diretamente no cliente WinForms. O caminho levou pelo WebView2. Mas como implantar os componentes necessários se não é possível simplesmente recriar o projeto de instalação?

Quem já inseriu o WebView2 em um projeto “normal” .NET conhece o caminho padrão: instalar o pacote NuGet, a runtime geralmente já está no cliente, basta criar um WebView2Environment e pronto. Em um ambiente legado, sem acesso ao gerenciador de pacotes e com dezenas de sessões RDS no mesmo servidor, praticamente todos esses passos são diferentes.

Por que o caminho padrão não funciona aqui

Três condições determinam toda a solução:

  1. Sem NuGet / Sem processo de build. Os formulários customizados não são compilados, mas disponibilizados como código-fonte C# em uma pasta e carregados em tempo de execução. Assim, os assemblies do WebView2 precisam ser incorporados como referências estáticas de DLL.
  2. Ambiente multiusuário no RDS. Vários usuários trabalham simultaneamente no mesmo servidor terminal. O WebView2 normalmente cria uma pasta userDataFolder – sem uma separação clara, as sessões sobrescreveriam os cookies, cache e estado umas das outras.
  3. Sem garantia de que a runtime do WebView2 está pré-instalada em todos os clientes. Para uma imagem RDS distribuída centralmente, é necessária uma implantação controlada e fixa por versão, em vez da runtime “Evergreen” que se atualiza sozinha, representando um risco imprevisível para um sistema ERP produtivo.

Passo 1: Fixed Version Runtime em vez de Evergreen

Para ambientes RDS, a runtime Evergreen é a escolha errada – ela se atualiza automaticamente em segundo plano, o que pode levar a comportamentos inconsistentes entre sessões em um servidor terminal centralizado. Em vez disso, utiliza-se a Fixed Version Runtime: uma versão específica da runtime WebView2 é baixada uma vez, salva localmente no servidor e referenciada explicitamente no código.

var environmentOptions = new CoreWebView2EnvironmentOptions();
var browserExecutableFolder = @"C:\ErpSystem\WebView2Runtime\";
var environment = await CoreWebView2Environment.CreateAsync(
    browserExecutableFolder,
    userDataFolder,
    environmentOptions);

O browserExecutableFolder deve apontar exatamente para a pasta onde a Fixed Version Runtime foi extraída – não para um diretório pai ou para o arquivo .exe em si. Um caminho incorreto aqui é um dos erros mais comuns e geralmente resulta em uma COMException pouco informativa ao criar a instância do ambiente.

Passo 2: Referência estática de DLL sem NuGet

Como não podemos usar um gerenciador de pacotes, os assemblies necessários do WebView2 (Microsoft.Web.WebView2.Core.dll, Microsoft.Web.WebView2.WinForms.dll, Microsoft.Web.WebView2.Wpf.dll, se necessário, e o WebView2Loader.dll correspondente à arquitetura-alvo) devem ser extraídos manualmente de um pacote NuGet e adicionados ao projeto como referências clássicas. Atenção para:

  • A arquitetura (x86/x64) do WebView2Loader.dll deve corresponder à plataforma-alvo do projeto – uma incompatibilidade causará um BadImageFormatException só em tempo de execução, não ao compilar.
  • As DLLs devem estar marcadas como “Copy to Output Directory: Copy if newer”, para que realmente sejam incluídas na implantação.

Passo 3: Isolamento de sessão via LocalApplicationData

Como vários usuários trabalham simultaneamente no mesmo host RDS, cada sessão precisa de sua própria userDataFolder isolada. A solução: criar dinamicamente a pasta para cada usuário do Windows sob LocalApplicationData.

string userDataFolder = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "ErpSystemWebView2",
    Environment.UserName);

Directory.CreateDirectory(userDataFolder);

Um null ou userDataFolder vazio aqui resulta, invariavelmente, em um E_ACCESSDENIED assim que o WebView2 tenta gravar em um diretório para o qual o processo, com a identidade específica do usuário RDS, não possui permissão de escrita. Especificar o caminho por usuário resolve isso de forma confiável.

Passo 4: Bloqueio Mark-of-the-Web

Um problema que aparece especialmente em cenários de implantação: o Windows marca arquivos copiados pela rede ou da internet com o chamado “Mark of the Web” (MOTW) – um fluxo alternativo do NTFS, sinalizando o arquivo como potencialmente inseguro. O WebView2 pode se recusar a carregar corretamente conteúdos locais marcados dessa forma.

A solução mais confiável é remover explicitamente o MOTW dos arquivos relevantes após a implantação (por exemplo, usando Unblock-File em um script PowerShell) em vez de tentar contornar essa marcação de segurança no código.

Passo 5: Comunicação bidirecional entre C# e Angular

O objetivo principal – a interface interativa – requer um canal de comunicação bidirecional. De C# para JavaScript, isso funciona via PostWebMessageAsJson:

webView.CoreWebView2.PostWebMessageAsJson(jsonPayload);

Para transferir volumes maiores de dados (no nosso caso cerca de 200 pedidos, aproximadamente 100 KB) de uma DataTable, ExecuteScriptAsync após o evento NavigationCompleted provou ser mais confiável do que PostWebMessageAsJson, pois assim temos certeza que a página Angular está realmente pronta para receber os dados.

O retorno de JavaScript para C# é feito com objetos visíveis via COM:

webView.CoreWebView2.AddHostObjectToScript("host", hostObject);

Importante para .NET Framework 4.6: para serialização, utiliza-se JavaScriptSerializer em vez de System.Text.Json, já que este último só está disponível a partir do .NET Core/5+ ou versões posteriores do .NET Framework.

No lado Angular, o acesso ao objeto host é iniciado por pull no ngOnInit, ao invés de esperar por eventos push – isso torna o estado de carregamento mais determinístico e fácil de depurar.

Resumo

O setup do WebView2 ao final parecia insignificante à primeira vista – um controle de navegador embutido que troca dados com o Angular. Mas o caminho até aqui não foi: Fixed-Version-Runtime em vez de Evergreen, referências de DLLs estáticas em vez de NuGet, userDataFolder específico para cada usuário em vez de caminho padrão, e o Mark-of-the-Web removido manualmente. Nenhuma dessas etapas é difícil individualmente – mas cada uma pode quebrar a implantação ou lançar um erro em tempo de execução caso seja copiada de um tutorial moderno de .NET para um ambiente legado RDS sem considerar as circunstâncias. O relatório impresso e estático agora é uma Web-UI interativa em design de cartões, que parece muito mais moderna e responsiva do que controles WinForms tradicionais.

 

Aviso de cookies do WordPress by Real Cookie Banner