Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article explique comment appliquer des modèles d’architecture éprouvés aux applications de bureau WinUI 3 créées avec le SDK d'application Windows. Vous allez apprendre à configurer l’injection de dépendances, gérer la configuration et le code de structure pour les scénarios métier d’entreprise.
Logiciels requis
- SDK d'application Windows 1.5 ou version ultérieure
- .NET 8 ou version ultérieure
- Visual Studio 2022 version 17.10 ou ultérieure avec les charges de travail de développement de bureau .NET et de développement d’applications Windows
Injection de dépendances
Les applications de bureau WinUI 3 n'incluent pas de conteneur intégré d'injection de dépendances (DI), comme ASP.NET Core le fait, mais vous pouvez en ajouter un à l'aide du même Microsoft.Extensions.DependencyInjection package NuGet. DI rend votre code testable, faiblement couplé et plus facile à gérer.
Configurer un conteneur DI
Installez le package NuGet :
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting
Configurez l’hôte et les services dans votre 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
Si vous oubliez d’enregistrer un service, l’utilitaire GetService<T>() ci-dessus lève un ArgumentException à l’exécution en indiquant le nom du type manquant. Exécutez l’application et accédez à chaque page pendant le développement pour vérifier que toutes les inscriptions sont correctes.
Injecter des dépendances dans ViewModels
Avec le conteneur configuré, vos ViewModels reçoivent des dépendances via l’injection de constructeur :
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";
}
}
Durées de vie des services
Choisissez la durée de vie appropriée lors de l’inscription des services :
| Durée de vie | Méthode | Utilisé pour |
|---|---|---|
| Singleton | AddSingleton<T>() |
Navigation, état à l’échelle de l’application, caches |
| Délimité | AddScoped<T>() |
Contextes par fenêtre ou par boîte de dialogue |
| Éphémère | AddTransient<T>() |
ViewModels, services sans état |
Tip
Inscrivez ViewModels en tant que temporaire afin que chaque navigation crée une nouvelle instance. Inscrivez les services qui contiennent l’état à l’échelle de l’application en tant que Singleton.
Gestion de la configuration
Permet Microsoft.Extensions.Configuration de gérer les paramètres d’application dans les applications de bureau, le même modèle utilisé dans ASP.NET Core.
Ajoutez la prise en charge de la configuration
Installez les packages nécessaires :
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Options
Créez un appsettings.json fichier à la racine de votre projet. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le fichier, sélectionnez Propriétés, puis définissez Copier dans le répertoire de sortie sur Copier si plus récent. Vous pouvez également ajouter <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> à l’entrée du fichier dans votre .csproj.
{
"AppSettings": {
"ApiBaseUrl": "https://api.contoso.com/v2",
"MaxRetryCount": 3,
"EnableTelemetry": true
},
"Logging": {
"LogLevel": "Information"
}
}
Liez la configuration dans votre configuration 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>();
})
Utiliser la configuration dans les services
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)
};
}
}
Persistance des paramètres utilisateur
Pour les paramètres par utilisateur qui survivent aux mises à jour d’application, utilisez Windows.Storage.ApplicationData (applications empaquetées) ou un fichier JSON local (applications non empaquetées) :
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
Les applications empaquetées (MSIX) peuvent être utilisées ApplicationData.Current.LocalSettings pour des paires clé-valeur simples. Les applications non empaquetées doivent gérer leur propre emplacement de stockage.
Indicateurs de fonctionnalités
Implémentez des indicateurs de fonctionnalité pour activer le déploiement progressif et les tests A/B sans redéploiement.
Indicateurs de fonctionnalité locaux avec configuration
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;
}
intégration de Azure App Configuration
Pour les indicateurs de fonctionnalité gérés par le cloud, utilisez Azure App Configuration :
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
Pour le développement local, vous pouvez utiliser un chaîne de connexion au lieu de DefaultAzureCredential. Stockez la chaîne de connexion dans une variable d’environnement ou dans le Gestionnaire d’identifiants Windows — jamais dans le contrôle de version :
options.Connect(Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION_STRING"))
Tip
Pour un déploiement progressif via le Store au niveau du paquet, consultez Déploiement progressif du paquet.
Modèles d’entreprise et de secteur d’activité
Les applications métier ont des exigences supplémentaires concernant l’identité, la protection des données et la gestion des appareils.
Identité et accès conditionnel
Utilisez MSAL (Microsoft Authentication Library) pour l’authentification d’entreprise :
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);
});
Important
L’URI http://localhost de redirection est adapté au développement. Pour les applications de bureau de production, utilisez plutôt le répartiteur Windows (WAM), qui fournit l'authentification unique avec le compte Windows de l'utilisateur et une protection de jeton plus forte.
Les applications d’entreprise déployées via Intune peuvent appliquer des stratégies d’accès conditionnel qui nécessitent :
- Conformité des appareils (chiffrement, code confidentiel, version du système d’exploitation)
- Authentification multifacteur
- Restrictions relatives à l’emplacement réseau
Données hors connexion et mise en cache
Les applications métier de bureau doivent fréquemment fonctionner hors connexion. Implémentez un modèle de référentiel avec la mise en cache locale :
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>();
}
}
}
Protection de données
Utilisez Windows.Security.Cryptography.DataProtection (applications empaquetées) ou le .NET DataProtectionProvider pour chiffrer des données locales sensibles.
Installez le package requis :
dotnet add package Microsoft.AspNetCore.DataProtection.Extensions
Inscrivez ensuite la protection des données dans votre conteneur d’adresses di :
using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.SetApplicationName("Contoso.LOBApp")
.ProtectKeysWithDpapi();
Architecture en couches
Structurez votre application WinUI 3 dans des couches pour conserver les dépendances qui circulent dans une direction :
┌─────────────────────────────┐
│ 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èglement:
- Chaque couche dépend uniquement de la couche directement en dessous.
- ViewModels ne référence jamais les types d’interface utilisateur (
Page,Window,ContentDialog). - Les services définissent des interfaces ; les implémentations résident dans la couche de données.
- Enregistrez toutes les dépendances entre couches dans le conteneur DI.
Compatibilité descendante et contrôle de version
Lorsque vous publiez de nouvelles versions de votre application, tenez compte des éléments suivants :
- Migration des données : version du schéma de votre base de données locale. Utilisez un exécuteur de migration au démarrage pour effectuer une mise à niveau de n’importe quel schéma précédent vers le schéma actuel.
- Migration des paramètres : stockez une version de schéma dans votre fichier de paramètres. Au chargement, appliquer les transformations des anciens formats vers les nouveaux.
- Installations côte à côte : MSIX met à niveau un package en place par défaut. Pour exécuter plusieurs versions principales côte à côte, affectez à chaque version un nom de famille de packages distinct au moment du 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);
}
}
Vérifier votre configuration
Exécutez l’application et accédez à chaque page pour vérifier que les services sont résolus correctement. Si un service n’est pas inscrit, vous verrez un InvalidOperationException au moment de l’exécution avec le nom de type manquant. Vérifiez également que :
- Les valeurs de configuration sont chargées depuis
appsettings.json(vérifiez une propriété liée dans le débogueur). - Les indicateurs de fonctionnalité sont évalués comme prévu (basculez un indicateur et redémarrez).
- La mise en cache hors connexion retourne des données lorsque le réseau n’est pas disponible.
Contenu connexe
Windows developer