Arquitetura

Por dentro do WingetEasy

Uma aplicação WinUI 3 em .NET 10 e C# 14 organizada em três camadas, com MVVM, injeção de dependências, SQLite em modo WAL e foco obsessivo em consumo mínimo de recursos.

camadas
3
contratos de serviço
12
tabelas SQLite
6
RAM na bandeja
~11 MB

01

Visão em camadas

Clean Architecture em três projetos. A interface conhece apenas contratos; o domínio não conhece a interface; a persistência implementa os contratos do domínio.

Camadas e direção de dependências
renderizando diagrama…

WingetEasy.App

WinUI 3
Views XAML, ViewModels, bandeja do sistema, diálogos e o contêiner de DI que compõe toda a aplicação.

WingetEasy.Core

Domínio
Interfaces e serviços: interoperação com o winget, fila de atualização, backup, perfis, agendamento e elevação.

WingetEasy.Data

EF Core
AppDbContext, entidades e seis repositórios sobre SQLite, sempre via IDbContextFactory.

Inversão de controle

DI
Todos os serviços são registrados em Microsoft.Extensions.DependencyInjection e injetados por interface — nenhum ViewModel instancia um serviço diretamente.

02

Apresentação MVVM

Views finas, ViewModels testáveis. O estado vive em propriedades observáveis e cada ação é um comando.

  • CommunityToolkit.Mvvm com ObservableObject, [ObservableProperty], RelayCommand e AsyncRelayCommand.
  • Atualizações de tela sempre marshalled para a UI thread via DispatcherQueue; trabalho de I/O roda em Task.Run.
  • UpdatesViewModel é Singleton: a página principal e o painel da bandeja (UpdateFlyout) compartilham o mesmo estado de fila.
  • Listas longas usam virtualização XAML com ItemsStackPanel; a busca tem debounce para não recalcular a cada tecla.

03

Ciclo de vida completo

Do primeiro milissegundo ao descarte na bandeja: Serilog, hooks do Velopack, instância única por Mutex e decisão do modo de execução pelos argumentos.

Diagrama de estados do ciclo de vida
renderizando diagrama…
Sequência: do clique no executável ao trimming de RAM
renderizando diagrama…
Ao fechar a janela, a árvore visual é descartada e a memória cai de ~65 MB para ~11 MB.

04

Modos de execução

O mesmo executável assume quatro papéis, decididos por CliArgumentsParser antes de qualquer janela existir.

Argumento Modo O que acontece
(nenhum) Desktop interativo DI completo, migrations, MainWindow com Mica e coordenadas restauradas.
--minimized Bandeja Sem janela DirectX: só o ícone H.NotifyIcon, verificação silenciosa e 7–15 MB de RAM.
--check · --update-all · --backup · --restore Headless HeadlessRunner executa sem UI e devolve um código de saída padronizado.
--elevated-update <job> Worker elevado Processo filho com UAC lê o job JSON, executa o lote como administrador e grava o resultado.

05

Gestão crítica de memória

Aplicações WinUI 3 self-contained são pesadas por natureza. O WingetEasy trata RAM como recurso de primeira classe.

Contexto de dados efêmero

Cada operação cria e descarta seu AppDbContext via IDbContextFactory, com AsNoTracking() nas leituras — o ChangeTracker nunca acumula.

Árvore XAML descartada

Ao ir para a bandeja: ContentFrame.Content = null e BackStack.Clear() liberam buffers DirectX.

Compactação do LOH

CompactOnce + GC.Collect(2, Aggressive) seguido de P/Invoke EmptyWorkingSet (Psapi).

Imagens decodificadas no tamanho certo

Ícones de pacotes passam por WIC com DecodePixelWidth, evitando bitmaps em resolução total.
Fechamento, persistência de coordenadas e trimming de RAM
renderizando diagrama…

06

Concorrência e resiliência

Separação rígida entre UI thread e workers, cancelamento cooperativo de ponta a ponta e um circuit breaker para verificações em segundo plano.

  • Paralelismo limitado com SemaphoreSlim; cancelamento propagado por CancellationTokenSource até o processo filho do winget (process.Kill()).
  • Streams StandardOutput e StandardError lidos de forma assíncrona para evitar deadlock quando a saída excede o pipe anônimo (~4 KB).
  • SchedulerService usa PeriodicTimer e abre o circuito após 3 falhas seguidas, com backoff exponencial.
Estados do circuit breaker do SchedulerService
renderizando diagrama…

07

Segurança, privilégios e UAC

Princípio do menor privilégio: o app nunca roda elevado. Quando um pacote exige administrador, apenas um processo filho isolado é elevado — uma única vez por lote.

  1. 01

    Sanitização

    Cada PackageId é validado contra ^[A-Za-z0-9._-+]+$ (Source Generator Regex). Aspas, ponto-e-vírgula ou caracteres de controle são rejeitados.

  2. 02

    Job temporário

    A lista de pacotes é serializada (AOT, CoreJsonContext) em %TEMP%WingetEasyjob_<guid>.json.

  3. 03

    Elevação sob demanda

    O próprio executável é iniciado com --elevated-update e Verb = "runas": um único prompt do UAC.

  4. 04

    Captura e limpeza

    O pai lê result_<guid>.json, desserializa os resultados e apaga os dois arquivos.

08

Persistência SQLite

EF Core sobre SQLite em modo WAL: leituras da interface nunca esperam por escritas do modo headless ou de serviços em segundo plano.

Modelo de dados (AppDbContext)
renderizando diagrama…
Inicialização do banco
PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;

await using var db = await _factory.CreateDbContextAsync(ct);
Tabela Repositório Conteúdo
Settings SettingsRepository Chaves tipadas (GetAsync<T>/SetAsync<T>), estado da janela e flags.
UpdateHistories UpdateHistoryRepository Auditoria FromVersion → ToVersion, status e mensagem de erro.
Packages PackageRepository Regras de supressão, pin, versão ignorada e prazo de adiamento.
BackupSnapshots BackupSnapshotRepository Snapshots JSON completos do winget export com metadados da máquina.
Profiles ProfileRepository Perfis temáticos e lista serializada de pacotes.
CheckLogs CheckLogRepository Telemetria de cada verificação: duração, pacotes encontrados, sucesso.

Migrações versionadas pelo EF Core: InitialSchema e AddPackageSuppressionFields.

09

Contratos de serviço

Os “endpoints” internos do WingetEasy: cada capacidade é uma interface em WingetEasy.Core.Interfaces.

Interface Responsabilidade
IWingetService Interoperação direta com winget.exe: verificar, listar instalados, instalar e reparar fontes.
IUpdateService Orquestrador da fila: pré-voo de processos, lote padrão, fallback UAC e auditoria.
IBackupService Snapshots via winget export, restauração granular e geração de scripts .ps1.
IProfilesService CRUD de perfis e instalação apenas dos pacotes ausentes (diff engine).
IRunningProcessService Detecção de apps abertos e fechamento gracioso (WM_CLOSE) ou forçado.
ISchedulerService Verificações periódicas internas com circuit breaker.
ITaskSchedulerService Tarefa WingetEasy_Background_Check no Agendador de Tarefas do Windows.
IToastNotificationService Notificações interativas, respeitando horário silencioso e som.
INotificationService Contrato genérico de notificação, implementado por ToastNotificationService.
IAdminElevationHelper Execução isolada de lotes elevados via processo filho runas.
IAppUpdateService Atualização do próprio WingetEasy via Velopack e GitHub Releases.
ILocalizationService Textos da interface traduzidos, registrado como instância única (LocalizationService.Instance).

10

Stack e dependências

Windows 10 build 19041+ ou Windows 11, .NET 10, C# 14 e Windows App SDK.

Pacote Uso
CommunityToolkit.Mvvm Propriedades observáveis e comandos gerados por source generators.
Microsoft.EntityFrameworkCore.Sqlite Persistência local com migrations e modo WAL.
H.NotifyIcon Ícone, tooltip dinâmico e menu de contexto na bandeja.
Microsoft.Windows.AppNotifications Toasts nativos interativos.
Microsoft.Win32.TaskScheduler Criação da tarefa agendada de verificação.
System.CommandLine Parsing dos argumentos do modo headless.
Serilog Logs estruturados diários em %LocalAppData%.
Velopack Auto-update delta do próprio aplicativo.
Logo WingetEasy

Deixe o winget trabalhar. Você só escolhe os apps.

Baixar para Windows