Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel wird gezeigt, wie bewährte Architekturmuster auf WinUI 3-Desktop-Apps angewendet werden, die mit dem Windows App SDK erstellt wurden. Sie erfahren, wie Sie Abhängigkeitsinjektion einrichten, Konfiguration verwalten und Code für LOB-Geschäftsszenarien in Unternehmen strukturieren.
Voraussetzungen
- Windows App SDK 1.5 oder höher
- .NET 8 oder höher
- Visual Studio 2022, Version 17.10 oder höher, mit den Workloads .NET-Desktopentwicklung und Windows-Anwendungsentwicklung
Abhängigkeitsinjektion
WinUI 3-Desktop-Apps enthalten keinen integrierten Di-Container (Abhängigkeitseinfügung) wie ASP.NET Core, aber Sie können einen Container mit demselben Microsoft.Extensions.DependencyInjection NuGet-Paket hinzufügen. DI macht Ihren Code testbar, locker gekoppelt und einfacher zu warten.
DI-Container einrichten
Installieren Sie das NuGet-Paket:
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting
Konfigurieren Sie den Host und die Dienste in Ihrem 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
Wenn Sie vergessen, einen Dienst zu registrieren, löst das obige Hilfsprogramm GetService<T>() zur Laufzeit eine ArgumentException unter Angabe des fehlenden Typnamens aus. Führen Sie die App aus, und navigieren Sie während der Entwicklung zu jeder Seite, um sicherzustellen, dass alle Registrierungen korrekt sind.
Einfügen von Abhängigkeiten in ViewModels
Nachdem der Container konfiguriert wurde, empfangen Ihre ViewModels Abhängigkeiten über die Konstruktoreinfügung:
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";
}
}
Dienstlebensdauer
Wählen Sie beim Registrieren von Diensten die entsprechende Lebensdauer aus:
| Lebensdauer | Method | Verwendung für |
|---|---|---|
| Singleton | AddSingleton<T>() |
Navigation, appweiter Zustand, Caches |
| Bereichsbezogen | AddScoped<T>() |
Kontexte pro Fenster oder Dialogfeld |
| Flüchtig | AddTransient<T>() |
ViewModels, zustandslose Dienste |
Tip
Registrieren Sie ViewModels als vorübergehend , sodass jede Navigation eine neue Instanz erstellt. Registrieren Sie Dienste, die den anwendungsweiten Zustand verwalten, als Singleton.
Konfigurationsverwaltung
Wird Microsoft.Extensions.Configuration verwendet, um App-Einstellungen in Desktop-Apps zu verwalten, dasselbe Muster, das in ASP.NET Core verwendet wird.
Hinzufügen der Konfigurationsunterstützung
Installieren Sie die erforderlichen Pakete:
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Options
Erstellen Sie eine appsettings.json Datei im Projektstamm. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf die Datei, wählen Sie Eigenschaften aus, und setzen Sie In Ausgabeverzeichnis kopieren auf Kopieren, wenn neuer. Alternativ fügen Sie <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> zum Eintrag der Datei in Ihrer .csproj hinzu.
{
"AppSettings": {
"ApiBaseUrl": "https://api.contoso.com/v2",
"MaxRetryCount": 3,
"EnableTelemetry": true
},
"Logging": {
"LogLevel": "Information"
}
}
Binden Sie die Konfiguration in Ihrem DI-Setup:
.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>();
})
Konfiguration in Diensten verwenden
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)
};
}
}
Persistenz der Benutzereinstellungen
Verwenden Sie Windows.Storage.ApplicationData für Benutzereinstellungen, die App-Updates überleben, (verpackte Apps) oder eine lokale JSON-Datei (entpackte Apps):
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
Verpackte Apps (MSIX) können ApplicationData.Current.LocalSettings für einfache Schlüssel-Wert-Paare verwenden. Entpackte Apps müssen ihren eigenen Speicherort verwalten.
Feature-Flags
Implementieren Sie Featurekennzeichnungen, um schrittweises Rollout und A/B-Tests ohne erneute Bereitstellung zu ermöglichen.
Lokale Featurekennzeichnungen mit Konfiguration
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;
}
Azure App Configuration-Integration
Verwenden Sie für in der Cloud verwaltete Featurekennzeichnungen 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
Für die lokale Entwicklung können Sie anstelle von DefaultAzureCredential eine Verbindungszeichenfolge verwenden. Speichern Sie die Verbindungszeichenfolge in einer Umgebungsvariable oder der Windows-Anmeldeinformationsverwaltung – niemals in der Quellcodeverwaltung:
options.Connect(Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION_STRING"))
Tip
Informationen zur storebasierten schrittweisen Paketeinführung auf Paketebene finden Sie unter Schrittweise Paketeinführung.
Unternehmens- und Line-of-Business-Muster
Branchen-Apps verfügen über zusätzliche Anforderungen hinsichtlich Identität, Datenschutz und Geräteverwaltung.
Identität und bedingter Zugriff
Verwenden Sie MSAL (Microsoft Authentication Library (MSAL)) für die Unternehmensauthentifizierung:
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
Der Umleitungs-URI http://localhost eignet sich für die Entwicklung. Verwenden Sie für Produktionsdesktop-Apps stattdessen den Windows Broker (WAM), der SSO mit dem Windows Konto des Benutzers und einem stärkeren Tokenschutz bereitstellt.
Unternehmens-Apps, die über Intune bereitgestellt werden, können Richtlinien für bedingten Zugriff erzwingen, die Folgendes erfordern:
- Gerätekompatibilität (Verschlüsselung, PIN, Betriebssystemversion)
- Mehrfaktor-Authentifizierung
- Einschränkungen für Netzwerkstandorte
Offlinedaten und Zwischenspeichern
Desktop-LOB-Apps müssen häufig offline funktionieren. Implementieren Eines Repositorymusters mit lokaler Zwischenspeicherung:
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>();
}
}
}
Datenschutz
Verwenden Sie Windows.Security.Cryptography.DataProtection (verpackte Apps) oder die .NET DataProtectionProvider zum Verschlüsseln vertraulicher lokaler Daten.
Installieren Sie das erforderliche Paket:
dotnet add package Microsoft.AspNetCore.DataProtection.Extensions
Registrieren Sie dann den Datenschutz in Ihrem DI-Container:
using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.SetApplicationName("Contoso.LOBApp")
.ProtectKeysWithDpapi();
Mehrschichtige Architektur
Strukturieren Sie Ihre WinUI 3-App in Ebenen, um Abhängigkeiten in einer Richtung zu halten:
┌─────────────────────────────┐
│ 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
└─────────────────────────────┘
Regeln:
- Jede Ebene hängt nur von der Ebene direkt darunter ab.
- ViewModels verweisen niemals auf UI-Typen (
Page,Window,ContentDialog). - Dienste definieren Schnittstellen; Implementierungen befinden sich auf der Datenebene.
- Registrieren Sie alle layerübergreifenden Abhängigkeiten im DI-Container.
Abwärtskompatibilität und Versionsverwaltung
Berücksichtigen Sie bei der Veröffentlichung neuer Versionen Ihrer App Folgendes:
- Datenmigration: Version Ihres lokalen Datenbankschemas. Verwenden Sie einen Migrationsläufer beim Start, um ein Upgrade von einem vorherigen Schema auf das aktuelle Schema durchzuführen.
- Einstellungsmigration: Speichern Sie eine Schemaversion in Ihrer Einstellungsdatei. Beim Laden Transformationen von alten in neue Formate anwenden.
- Parallele Installationen: MSIX aktualisiert standardmäßig ein Paket. Um mehrere Hauptversionen nebeneinander auszuführen, weisen Sie jeder Version zur Entwurfszeit einen eindeutigen Paketfamiliennamen zu.
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);
}
}
Überprüfen Des Setups
Führen Sie die App aus, und navigieren Sie zu jeder Seite, um zu überprüfen, ob die Dienste ordnungsgemäß aufgelöst werden. Wenn ein Dienst nicht registriert ist, wird zur Laufzeit ein InvalidOperationException unter Angabe des fehlenden Typnamens angezeigt. Vergewissern Sie sich außerdem, dass:
- Konfigurationswerte werden aus
appsettings.jsongeladen (prüfen Sie eine gebundene Eigenschaft im Debugger). - Feature-Flags funktionieren wie erwartet (ein Flag umschalten und neu starten).
- Die Offlinezwischenspeicherung gibt Daten zurück, wenn das Netzwerk nicht verfügbar ist.
Verwandte Inhalte
Windows developer