Architektúraminták WinUI 3 asztali alkalmazásokhoz

Ez a cikk bemutatja, hogyan alkalmazhat bevált architektúramintákat a Windows App SDK készült WinUI 3 asztali alkalmazásokra. Megtudhatja, hogyan állíthat be függőséginjektálást, kezelheti a konfigurációt és a vállalati üzletági (LOB) forgatókönyvekhez tartozó struktúrakódot.

Prerequisites

  • Windows App SDK 1,5-ös vagy újabb verzió
  • .NET 8 vagy újabb
  • Visual Studio 2022 17.10-es vagy újabb verziója a .NET asztali fejlesztés és a Windows-alkalmazásfejlesztés munkaterhelésekkel

Függőséginjektálás

A WinUI 3-alapú asztali alkalmazások nem tartalmaznak az ASP.NET Core-hoz hasonló beépített függőséginjektálási (DI-)tárolót, de ugyanazzal a Microsoft.Extensions.DependencyInjection NuGet-csomaggal hozzáadhat egyet. A DI tesztelhetővé, lazán összekapcsolhatóvá és könnyebben karbantarthatóvá teszi a kódot.

DI-tároló beállítása

Telepítse a NuGet-csomagot:

dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting

Konfigurálja a gazdagépet és a szolgáltatásokat a(z) App.xaml.cs elemben:

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();
    }
}

Megjegyzés:

Ha elfelejt regisztrálni egy szolgáltatást, a GetService<T>() fenti segítő futásidőben dob egy ArgumentException hiányzó típusnevet. Futtassa az alkalmazást, és keresse meg az egyes lapokat a fejlesztés során, és ellenőrizze, hogy az összes regisztráció helyes-e.

Függőségek injektálása a ViewModelsbe

A tároló konfigurálásával a ViewModels konstruktorinjektáláson keresztül függőségeket kap:

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";
    }
}

Szolgáltatási élettartamok

Válassza ki a megfelelő élettartamot a szolgáltatások regisztrálásakor:

Lifetime Módszer Felhasználási területek:
Singleton AddSingleton<T>() Navigáció, alkalmazásszintű állapot, gyorsítótárak
Hatókör AddScoped<T>() Ablakonkénti vagy párbeszédpanelenkénti környezetek
Transient AddTransient<T>() ViewModels, állapot nélküli szolgáltatások

Jótanács

Regisztrálja a ViewModelleket Transientként, hogy minden navigáláskor új példány jöjjön létre. Regisztrálja Singleton-ként azokat a szolgáltatásokat, amelyek az alkalmazás egészére kiterjedő állapotot tárolnak.

Konfigurációkezelés

A(z) Microsoft.Extensions.Configuration használatával kezelheti az alkalmazásbeállításokat az asztali alkalmazásokban, ugyanazzal a mintával, amelyet az ASP.NET Core is használ.

Konfigurációs támogatás hozzáadása

Telepítse a szükséges csomagokat:

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Options

Hozzon létre egy appsettings.json fájlt a projekt gyökérkönyvtárában. A Megoldáskezelőben kattintson a jobb gombbal a fájlra, válassza a Tulajdonságok lehetőséget, majd állítsa a Másolás a kimeneti könyvtárba értékét erre: Másolás, ha újabb. Másik lehetőségként adja hozzá a(z) <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> elemet a fájl bejegyzéséhez a(z) .csproj fájlban.

{
  "AppSettings": {
    "ApiBaseUrl": "https://api.contoso.com/v2",
    "MaxRetryCount": 3,
    "EnableTelemetry": true
  },
  "Logging": {
    "LogLevel": "Information"
  }
}

Kösd össze a konfigurációt a DI-beállításban:

.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>();
})

Konfiguráció használata a szolgáltatásokban

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)
        };
    }
}

Felhasználói beállítások megőrzése

Az alkalmazásfrissítéseket túlélő felhasználónkénti beállításokhoz használja Windows.Storage.ApplicationData a (csomagolt alkalmazásokat) vagy egy helyi JSON-fájlt (kicsomagolt alkalmazások):

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));
    }
}

Megjegyzés:

A csomagolt alkalmazások (MSIX) egyszerű kulcs-érték párokhoz használhatók ApplicationData.Current.LocalSettings . A csomagolatlan alkalmazásoknak saját tárhelyet kell kezelniük.

Funkciójelzők

A funkciójelzők implementálása a fokozatos bevezetés és az A/B tesztelés újbóli üzembe helyezés nélküli engedélyezéséhez.

Helyi funkciójelzők konfigurációval

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;
}

Az Azure App Configuration integrációja

A felhőben felügyelt funkciójelzőkhöz használja az Azure App Configurationt:

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();

Megjegyzés:

A helyi fejlesztéshez a DefaultAzureCredential helyett kapcsolati sztringet használhat. A kapcsolati karakterláncot környezeti változóban vagy a Windows Hitelesítőadat-kezelőben tárolja – soha ne a verziókezelőben:

options.Connect(Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION_STRING"))

Jótanács

Az Áruház-alapú fokozatos bevezetést a csomag szintjén a fokozatos bevezetés című témakörben találhatja meg.

Vállalati és LOB-minták

Az üzletági alkalmazások további követelményeket támasztanak az identitással, az adatvédelemmel és az eszközkezeléssel szemben.

Identitás és feltételes hozzáférés

Az MSAL (Microsoft Authentication Library) használata vállalati hitelesítéshez:

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

Az http://localhost átirányítási URI fejlesztésre alkalmas. Éles használatra szánt asztali alkalmazásokhoz inkább a Windows-közvetítőt (WAM) használja, amely a felhasználó Windows-fiókjával való egyszeri bejelentkezést, valamint erősebb jogkivonat-védelmet biztosít.

Az Intune-on keresztül üzembe helyezett vállalati alkalmazások az alábbiakat igénylő feltételes hozzáférési szabályzatokat kényszeríthetnek ki:

  • Eszközmegfelelés (titkosítás, PIN-kód, operációs rendszer verziója)
  • Többtényezős hitelesítés
  • Hálózati helykorlátozások

Offline adatok és gyorsítótárazás

Az asztali LOB-alkalmazásoknak gyakran offline állapotban kell működnie. Tárházminta implementálása helyi gyorsítótárazással:

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>();
        }
    }
}

Adatvédelem

Bizalmas helyi adatok titkosításához használja Windows.Security.Cryptography.DataProtection a (csomagolt alkalmazásokat) vagy a .NETDataProtectionProvider.

Telepítse a szükséges csomagot:

dotnet add package Microsoft.AspNetCore.DataProtection.Extensions

Ezután regisztrálja az adatvédelmet a DI-tárolóban:

using Microsoft.AspNetCore.DataProtection;

services.AddDataProtection()
    .SetApplicationName("Contoso.LOBApp")
    .ProtectKeysWithDpapi();

Rétegzett architektúra

Strukturálja a WinUI 3 alkalmazást rétegekbe, hogy a függőségek egy irányba haladjanak:

┌─────────────────────────────┐
│   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
└─────────────────────────────┘

Szabályok:

  • Minden réteg csak az alatta lévő rétegtől függ.
  • A ViewModels soha nem hivatkozik felhasználói felületi típusokra (Page, Window, ). ContentDialog
  • A szolgáltatások interfészeket definiálnak; az implementációk az adatrétegben élnek.
  • Regisztrálja az összes rétegközi függőséget a DI-tárolóban.

Visszamenőleges kompatibilitás és verziószámozás

Az alkalmazás új verzióinak kiadásakor vegye figyelembe a következő szempontokat:

  • Adatmigrálás: A helyi adatbázisséma verziója. Az indításkor egy migrálási futó használatával frissítsen az előző sémáról az aktuálisra.
  • Beállítások migrálása: A sémaverzió tárolása a beállításfájlban. Betöltés esetén alkalmazza az átalakításokat a régi formátumokról az újakra.
  • Egymás melletti telepítések: Az MSIX alapértelmezés szerint frissíti a csomagokat. Ha több főverziót szeretne egymás mellett futtatni, minden verzióhoz rendeljen külön csomagcsaládnevet a tervezéskor.
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);
    }
}

A beállítás ellenőrzése

Futtassa az alkalmazást, és navigáljon az egyes oldalakra a szolgáltatások helyes feloldásának ellenőrzéséhez. Ha egy szolgáltatás nincs regisztrálva, futásidőben egy InvalidOperationException jelenik meg, amely tartalmazza a hiányzó típus nevét. Győződjön meg arról is, hogy:

  • A konfigurációs értékek a(z) appsettings.json elemből töltődnek be (ellenőrizzen egy kötött tulajdonságot a hibakeresőben).
  • A funkciókapcsolók az elvártaknak megfelelően működnek (kapcsoljon át egy jelölőt, majd indítsa újra).
  • Az offline gyorsítótárazás akkor ad vissza adatokat, ha a hálózat nem érhető el.