Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo mostra como aplicar padrões de arquitetura comprovados aos aplicativos de área de trabalho do WinUI 3 criados com o SDK do Aplicativo Windows. Você aprenderá a configurar a injeção de dependências, gerenciar configurações e estruturar o código para cenários corporativos de linha de negócios (LOB).
Pré-requisitos
- SDK do Aplicativo Windows 1.5 ou posterior
- .NET 8 ou posterior
- Visual Studio 2022 versão 17.10 ou superior com as cargas de trabalho desenvolvimento para área de trabalho .NET e desenvolvimento de aplicativos do Windows
Injeção de dependência
Os aplicativos WinUI 3 para desktop não incluem um contêiner interno de injeção de dependência (DI), como o ASP.NET Core, mas você pode adicionar um usando o mesmo pacote NuGet Microsoft.Extensions.DependencyInjection. A DI torna seu código testável, flexívelmente acoplado e mais fácil de manter.
Configurar um contêiner de DI
Instale o pacote NuGet:
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting
Configure o host e os serviços no seu App.xaml.cs:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
public partial class App : Application
{
public IHost Host { get; }
public static T GetService<T>() where T : class
{
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException(
$"{typeof(T)} needs to be registered in ConfigureServices.");
}
return service;
}
public App()
{
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureServices((context, services) =>
{
// Services
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IDataService, DataService>();
services.AddTransient<IDialogService, DialogService>();
// ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<SettingsViewModel>();
// Views
services.AddTransient<MainPage>();
services.AddTransient<SettingsPage>();
})
.Build();
}
}
Note
Se você esquecer de registrar um serviço, o auxiliar GetService<T>() acima lançará uma ArgumentException em tempo de execução com o nome do tipo ausente. Execute o aplicativo e navegue até cada página durante o desenvolvimento para verificar se todos os registros estão corretos.
Injetar dependências em ViewModels
Com o contêiner configurado, seus ViewModels recebem dependências por meio da injeção de construtor:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel : ObservableObject
{
private readonly IDataService _dataService;
private readonly INavigationService _navigationService;
public MainViewModel(IDataService dataService, INavigationService navigationService)
{
_dataService = dataService;
_navigationService = navigationService;
}
[ObservableProperty]
private string _statusMessage = string.Empty;
[RelayCommand]
private async Task LoadDataAsync()
{
StatusMessage = "Loading...";
var items = await _dataService.GetItemsAsync();
StatusMessage = $"Loaded {items.Count} items";
}
}
Tempo de vida do serviço
Escolha o tempo de vida apropriado ao registrar serviços:
| Tempo de vida | Método | Usar para |
|---|---|---|
| Singleton | AddSingleton<T>() |
Navegação, estado em todo o aplicativo, caches |
| Com escopo | AddScoped<T>() |
Contextos por janela ou por diálogo |
| Transitório | AddTransient<T>() |
ViewModels, serviços sem estado |
Tip
Registre ViewModels como transitório para que cada navegação crie uma nova instância. Registre serviços que mantêm o estado global do aplicativo como Singleton.
Gerenciamento de configuração
Use Microsoft.Extensions.Configuration para gerenciar as configurações do aplicativo em aplicativos da área de trabalho, o mesmo padrão usado em ASP.NET Core.
Adicionar suporte à configuração
Instale os pacotes necessários:
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Options
Crie um appsettings.json arquivo na raiz do projeto. Em Gerenciador de Soluções, clique com o botão direito do mouse no arquivo, selecione Propriedades e defina Copiar para Diretório de Saída para Copiar se for mais recente. Como alternativa, adicione <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> à entrada do arquivo em seu .csproj.
{
"AppSettings": {
"ApiBaseUrl": "https://api.contoso.com/v2",
"MaxRetryCount": 3,
"EnableTelemetry": true
},
"Logging": {
"LogLevel": "Information"
}
}
Vincule a configuração na sua configuração de DI:
.ConfigureServices((context, services) =>
{
// Bind settings to a strongly-typed class
services.Configure<AppSettings>(
context.Configuration.GetSection("AppSettings"));
// Inject IOptions<AppSettings> into services
services.AddSingleton<IApiClient, ApiClient>();
})
Usar configuração em serviços
using Microsoft.Extensions.Options;
public class ApiClient : IApiClient
{
private readonly AppSettings _settings;
private readonly HttpClient _httpClient;
public ApiClient(IOptions<AppSettings> options)
{
_settings = options.Value;
_httpClient = new HttpClient
{
BaseAddress = new Uri(_settings.ApiBaseUrl)
};
}
}
Persistência de configurações do usuário
Para configurações por usuário que sobrevivem às atualizações do aplicativo, use Windows.Storage.ApplicationData (aplicativos empacotados) ou um arquivo JSON local (aplicativos não empacotados):
public class UserSettingsService : IUserSettingsService
{
private readonly string _settingsPath;
public UserSettingsService()
{
var localAppData = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData);
_settingsPath = Path.Combine(localAppData, "Contoso", "MyApp", "settings.json");
}
public async Task SaveAsync<T>(string key, T value)
{
var settings = await LoadAllAsync();
settings[key] = JsonSerializer.Serialize(value);
Directory.CreateDirectory(Path.GetDirectoryName(_settingsPath)!);
await File.WriteAllTextAsync(
_settingsPath, JsonSerializer.Serialize(settings));
}
}
Note
Aplicativos empacotados (MSIX) podem usar ApplicationData.Current.LocalSettings para pares de chave-valor simples. Aplicativos não empacotados devem gerenciar seu próprio local de armazenamento.
Flags de funcionalidade
Implemente flags de funcionalidade para permitir o lançamento gradual e testes A/B sem nova implantação.
Sinalizadores de recursos locais com configuração
public interface IFeatureFlagService
{
bool IsEnabled(string featureName);
}
public class FeatureFlagService : IFeatureFlagService
{
private readonly Dictionary<string, bool> _flags;
public FeatureFlagService(IConfiguration configuration)
{
_flags = configuration.GetSection("FeatureFlags")
.Get<Dictionary<string, bool>>() ?? new();
}
public bool IsEnabled(string featureName) =>
_flags.TryGetValue(featureName, out var enabled) && enabled;
}
Integração com o Configuração de Aplicativos do Azure
Para sinalizadores de funcionalidade gerenciados na nuvem, use o Configuração de Aplicativos do Azure:
dotnet add package Microsoft.Extensions.Configuration.AzureAppConfiguration
dotnet add package Microsoft.FeatureManagement
using Azure.Identity;
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, config) =>
{
config.AddAzureAppConfiguration(options =>
{
options.Connect(
new Uri("https://<your-store>.azconfig.io"),
new DefaultAzureCredential())
.UseFeatureFlags(flagOptions =>
{
flagOptions.CacheExpirationInterval = TimeSpan.FromMinutes(5);
});
});
})
.ConfigureServices((context, services) =>
{
services.AddFeatureManagement(context.Configuration);
})
.Build();
Note
Para desenvolvimento local, você pode usar um cadeia de conexão em vez de DefaultAzureCredential. Armazene a cadeia de conexão em uma variável de ambiente ou no Gerenciador de Credenciais do Windows — nunca no controle de versão:
options.Connect(Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION_STRING"))
Tip
Para a distribuição gradual baseada na Store no nível do pacote, consulte distribuição gradual de pacotes.
Padrões corporativos e de linha de negócios
Os aplicativos de linha de negócios têm requisitos adicionais em relação à identidade, à proteção de dados e ao gerenciamento de dispositivos.
Identidade e acesso condicional
Use a biblioteca MSAL (Biblioteca do Microsoft Authenticator) para autenticação corporativa:
services.AddSingleton<IAuthService>(sp =>
{
var app = PublicClientApplicationBuilder
.Create("your-client-id")
.WithAuthority(AzureCloudInstance.AzurePublic, "your-tenant-id")
.WithRedirectUri("http://localhost")
.Build();
return new AuthService(app);
});
Importante
O http://localhost URI de redirecionamento é adequado para desenvolvimento. Para aplicativos de área de trabalho de produção, use o Windows broker (WAM), que fornece SSO com a conta Windows do usuário e uma proteção de token mais forte.
Os aplicativos empresariais implantados por meio do Intune podem impor políticas de acesso condicional que exigem:
- Conformidade do dispositivo (criptografia, PIN, versão do sistema operacional)
- Autenticação multifator
- Restrições de localização de rede
Dados offline e cache
Os aplicativos LOB para desktop frequentemente precisam funcionar offline. Implementar um padrão de repositório com cache local:
public class CachedRepository<T> : IRepository<T> where T : class, IEntity
{
private readonly IApiClient _apiClient;
private readonly ILocalDatabase _localDb;
public async Task<IReadOnlyList<T>> GetAllAsync(bool forceRefresh = false)
{
if (!forceRefresh)
{
var cached = await _localDb.GetAllAsync<T>();
if (cached.Any())
return cached;
}
try
{
var items = await _apiClient.GetAsync<List<T>>();
await _localDb.UpsertAllAsync(items);
return items;
}
catch (HttpRequestException)
{
// Offline fallback
return await _localDb.GetAllAsync<T>();
}
}
}
Proteção de dados
Use Windows.Security.Cryptography.DataProtection (aplicativos empacotados) ou o DataProtectionProvider do .NET para criptografar dados locais confidenciais.
Instale o pacote necessário:
dotnet add package Microsoft.AspNetCore.DataProtection.Extensions
Em seguida, registre a proteção de dados em seu contêiner de DI:
using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.SetApplicationName("Contoso.LOBApp")
.ProtectKeysWithDpapi();
Arquitetura em camadas
Estruturar seu aplicativo WinUI 3 em camadas para manter as dependências fluindo em uma direção:
┌─────────────────────────────┐
│ Views (XAML + code-behind)│ ← UI layer, no business logic
├─────────────────────────────┤
│ ViewModels (MVVM Toolkit) │ ← Presentation logic, commands
├─────────────────────────────┤
│ Services / Use Cases │ ← Business rules, orchestration
├─────────────────────────────┤
│ Repositories / Data │ ← Data access, API clients, caching
└─────────────────────────────┘
Réguas:
- Cada camada depende apenas da camada diretamente abaixo dela.
- ViewModels nunca fazem referência a tipos de interface do usuário (
Page, ,Window).ContentDialog - Os serviços definem interfaces; as implementações residem na camada de dados.
- Registre todas as dependências entre camadas no contêiner de DI.
Compatibilidade com versões anteriores e controle de versão
Ao lançar novas versões do seu aplicativo, considere:
- Migração de dados: versão do esquema de banco de dados local. Use um executor de migração na inicialização para atualizar de qualquer esquema anterior para o atual.
- Migração de configurações: armazene uma versão de esquema no arquivo de configurações. Ao carregar, aplicar transformações dos formatos antigos para os novos.
- Instalações lado a lado: o MSIX atualiza um pacote em vigor por padrão. Para executar várias versões principais lado a lado, atribua a cada versão um nome de família de pacotes distinto em tempo de design.
public class DatabaseMigrator
{
public async Task MigrateAsync(SqliteConnection db)
{
var currentVersion = await GetSchemaVersionAsync(db);
if (currentVersion < 2)
await ApplyMigration_v2(db);
if (currentVersion < 3)
await ApplyMigration_v3(db);
await SetSchemaVersionAsync(db, LatestVersion);
}
}
Verificar sua configuração
Execute o aplicativo e navegue até cada página para verificar se os serviços são resolvidos corretamente. Se um serviço não estiver registrado, você verá um InvalidOperationException em tempo de execução com o nome do tipo ausente. Confirme também que:
- Os valores de configuração são carregados de
appsettings.json(verifique uma propriedade vinculada no depurador). - Sinalizadores de recursos são avaliados conforme o esperado (alterne um sinalizador e reinicie).
- O cache offline retorna dados quando a rede não está disponível.
Conteúdo relacionado
Windows developer