Espandere l'app con servizi, estensioni e pacchetti

Windows offre diverse tecnologie che consentono all'app di fornire funzionalità ad altre app o utilizzare componenti aggiuntivi di terze parti. Questo articolo confronta le opzioni di estendibilità disponibili per le app desktop SDK per app di Windows.

Panoramica delle opzioni di estendibilità

Tecnologia Description Identità del pacchetto richiesta Sistema operativo minimo
Servizi app Comunicazione di richiesta/risposta tra app tramite AppServiceConnection Yes Windows 10 1607
Estensioni dell'app Modello di plug-in: l'app host individua il contenuto dai pacchetti di estensione Yes Windows 10 1607
Estensioni del pacchetto Estendibilità a livello di pacchetto più ampia con uap17:PackageExtension Yes Windows 11
Pacchetti facoltativi Pacchetti di contenuto aggiuntivi che integrano un'app principale Yes Windows 10 1709
Pacchetti di risorse Risorse di linguaggio, scalabilità e accessibilità separate dal mercato Yes Windows 10

Scelta della tecnologia giusta

Usare i servizi app quando

  • È necessaria una comunicazione bidirezionale tra app separate.
  • L'app consumer invia una richiesta e attende una risposta.
  • Si vuole esporre un'interfaccia simile all'API ad altre app.

Esempio: servizio di traduzione che altre app possono chiamare per tradurre il testo.

Usa le estensioni dell'app quando

  • L'app richiede un modello plug-in in in cui terze parti forniscono contenuto, temi o componenti aggiuntivi.
  • Le estensioni vengono individuate in fase di esecuzione dai pacchetti installati.
  • Le estensioni forniscono dati o configurazioni, non codice eseguibile (l'esecuzione del codice deve usare i servizi app).

Esempio: editor di immagini che individua i pacchetti di filtro dai pacchetti di estensione installati.

Usa le estensioni del pacchetto quando

  • È necessaria un'estendibilità a livello di pacchetto più ampia in Windows 11.
  • Le estensioni richiedono l'accesso a più contenuti del pacchetto di quanti ne consenta il modello PublicFolder.

Utilizzare pacchetti opzionali quando

  • Sono disponibili contenuti aggiuntivi (DLC, funzionalità Premium) distribuiti come pacchetti separati.
  • Il contenuto viene creato dallo stesso editore.

Modelli di architettura

Servizio app con individuazione delle estensioni

Combinare le estensioni dell'app con i servizi app per un'architettura plug-in completa:

  1. L'app host usa AppExtensionCatalog per individuare le estensioni installate.
  2. Ogni estensione dichiara le proprietà che ne descrivono le funzionalità.
  3. Quando l'utente attiva un'estensione, l'app host si connette al servizio app dell'estensione per la comunicazione bidirezionale.
┌─────────────────┐      ┌──────────────────┐
│   Host app       │      │  Extension app    │
│                  │      │                   │
│ AppExtension     │◄────►│ AppExtension      │
│   Catalog        │      │   declaration     │
│                  │      │                   │
│ AppService       │◄────►│ AppService        │
│   Connection     │      │   provider        │
└─────────────────┘      └──────────────────┘

Solo estensione del contenuto

Per scenari più semplici in cui le estensioni forniscono contenuto statico (temi, modelli, file di dati):

  1. L'app host individua le estensioni tramite AppExtensionCatalog.
  2. Legge i file dall'estensione PublicFolder.
  3. Non è necessario alcun servizio app.

Differenze rispetto all'estendibilità UWP

Le tecnologie di estendibilità descritte qui funzionano allo stesso modo in SDK per app di Windows app desktop come fanno in UWP, con un requisito: identità del pacchetto MSIX. Tutte le funzionalità di estendibilità si basano sul manifesto del pacchetto per le dichiarazioni e sul catalogo dei pacchetti per l'individuazione.

Se l'app desktop non è in pacchetto, non puoi usare queste tecnologie di estendibilità. Considerare approcci alternativi, ad esempio:

  • Interfacce plug-in basate su COM
  • Rilevamento delle estensioni basato sul file system
  • Pipe con nome o altri meccanismi IPC

Individuazione di plug-in basati su file per app non in pacchetto

Per le app WinUI 3 senza pacchetti, puoi implementare un sistema di plug-in usando .NET AssemblyLoadContext per caricare le estensioni da una cartella nota:

public class PluginLoader
{
    private readonly string _pluginDirectory;

    public PluginLoader(string pluginDirectory)
    {
        _pluginDirectory = pluginDirectory;
    }

    public IEnumerable<T> LoadPlugins<T>() where T : class
    {
        if (!Directory.Exists(_pluginDirectory))
            yield break;

        foreach (var dll in Directory.GetFiles(_pluginDirectory, "*.dll"))
        {
            var context = new PluginLoadContext(dll);
            var assembly = context.LoadFromAssemblyPath(Path.GetFullPath(dll));

            foreach (var type in assembly.GetTypes()
                .Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract))
            {
                if (Activator.CreateInstance(type) is T plugin)
                    yield return plugin;
            }
        }
    }
}

// Custom AssemblyLoadContext to isolate plugin dependencies
public class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;

    public PluginLoadContext(string pluginPath) : base(isCollectible: true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var path = _resolver.ResolveAssemblyToPath(assemblyName);
        return path != null ? LoadFromAssemblyPath(path) : null;
    }
}

Warning

Il caricamento di assembly dal disco senza convalida è un rischio per la sicurezza. Nell'ambiente di produzione verificare le firme di assembly (ad esempio Authenticode) prima del caricamento, limitare le autorizzazioni ACL della directory del plug-in e prendere in considerazione l'esecuzione di plug-in in un processo separato con privilegi ridotti.

Definire un contratto di interfaccia condivisa in un assembly separato a cui fanno riferimento sia l'host che i plug-in:

// Contoso.App.Contracts (shared assembly)
public interface IPluginExtension
{
    string Name { get; }
    string Description { get; }
    void Execute(IServiceProvider services);
}

Note

L'uso di isCollectible: true in AssemblyLoadContext consente di scaricare i plug-in in fase di esecuzione. Questo approccio evita i problemi di controllo delle versioni che MEF (Managed Extensibility Framework) possono introdurre nelle app desktop.