Partager via


Disposition du déploiement pour les applications ASP.NET Core hébergées Blazor WebAssembly

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 de cet article.

Avertissement

Cette version d'ASP.NET Core n'est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 10 de cet article.

Cet article explique comment activer des déploiements hébergés Blazor WebAssembly dans des environnements qui bloquent le téléchargement et l’exécution de fichiers DLL (Dynamic-Link Library).

Remarque

Ce guide traite les environnements qui empêchent les clients de télécharger et d’exécuter des DLL. Dans .NET 8 ou version ultérieure, Blazor utilise le format de fichier Webcil pour résoudre ce problème. Pour plus d’informations, consultez Héberger et déployer ASP.NET Core Blazor WebAssembly. Le bundle multipart utilisant le package NuGet expérimental décrit dans cet article n'est pas pris en charge pour les applications Blazor dans .NET 8 ou ultérieur. Vous pouvez utiliser les instructions de cet article pour créer votre propre package NuGet multipart pour .NET 8 ou version ultérieure.

Blazor WebAssembly les applications nécessitent des bibliothèques de liens dynamiques (DLL) pour fonctionner, mais certains environnements empêchent les clients de télécharger et d’exécuter des DLL. Les produits de sécurité sont souvent en mesure d’analyser le contenu des fichiers parcourant le réseau et de bloquer ou mettre en quarantaine les fichiers DLL. Cet article décrit une approche pour activer les Blazor WebAssembly applications dans ces environnements, où un fichier groupé à plusieurs parties est créé à partir des DLL de l’application afin que les DLL puissent être téléchargées ensemble en contournant les restrictions de sécurité.

Blazor WebAssembly les applications nécessitent des bibliothèques de liens dynamiques (DLL) pour fonctionner, mais certains environnements empêchent les clients de télécharger et d’exécuter des DLL. Dans un sous-ensemble de ces environnements, la modification de l’extension de nom de fichier des fichiers DLL (.dll) suffit pour contourner les restrictions de sécurité, mais les produits de sécurité sont souvent en mesure d’analyser le contenu des fichiers parcourant le réseau et de bloquer ou mettre en quarantaine les fichiers DLL. Cet article décrit une approche pour activer les Blazor WebAssembly applications dans ces environnements, où un fichier groupé à plusieurs parties est créé à partir des DLL de l’application afin que les DLL puissent être téléchargées ensemble en contournant les restrictions de sécurité.

Une application hébergée Blazor WebAssembly peut personnaliser ses fichiers publiés et empaqueter des DLL d’application à l’aide des fonctionnalités suivantes :

  • Initialiseurs JavaScript qui permettent de personnaliser le processus de Blazor démarrage.
  • Extensibilité MSBuild pour transformer la liste des fichiers publiés et définir Blazor les extensions de publication. Blazor Les extensions de publication sont des fichiers définis pendant le processus de publication qui fournissent une représentation alternative pour l’ensemble de fichiers requis pour exécuter une application publiée Blazor WebAssembly . Dans cet article, une Blazor extension de publication est créée qui produit un bundle à plusieurs parties avec toutes les DLL de l’application empaquetées dans un seul fichier afin que les DLL puissent être téléchargées ensemble.

L’approche illustrée dans cet article sert de point de départ pour les développeurs de concevoir leurs propres stratégies et processus de chargement personnalisés.

Avertissement

Toute approche adoptée pour contourner une restriction de sécurité doit être soigneusement prise en compte pour ses implications en matière de sécurité. Nous vous recommandons d’explorer davantage le sujet avec les professionnels de la sécurité réseau de votre organisation avant d’adopter l’approche décrite dans cet article. Les alternatives à prendre en compte sont les suivantes :

  • Activez les appliances de sécurité et les logiciels de sécurité pour permettre aux clients réseau de télécharger et d’utiliser les fichiers exacts requis par une Blazor WebAssembly application.
  • Passez du Blazor WebAssembly modèle d’hébergement au Blazor Server modèle d’hébergement, qui gère tout le code C# de l’application sur le serveur et ne nécessite pas de téléchargement de DLL sur les clients. Blazor Server offre également l’avantage de conserver le code C# privé sans nécessiter l’utilisation d’applications API web pour la confidentialité du code C# avec les Blazor WebAssembly applications.

Package NuGet expérimental et exemple d’application

L’approche décrite dans cet article est utilisée par le package expérimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle (NuGet.org) pour les applications ciblant .NET 6 ou version ultérieure. Le package contient des cibles MSBuild pour personnaliser la Blazor sortie de publication et un initialiseur JavaScript pour utiliser un chargeur de ressources de démarrage personnalisé, chacun d’eux étant décrit en détail plus loin dans cet article.

Avertissement

Les fonctionnalités expérimentales et en préversion sont fournies pour recueillir des commentaires et ne sont pas prises en charge pour une utilisation en production.

Plus loin dans cet article, la section Blazor WebAssembly avec ses trois sous-sections fournit des explications détaillées sur la configuration et le code dans le package . Les explications détaillées sont importantes pour comprendre quand vous créez votre propre stratégie et le processus de chargement personnalisé pour Blazor WebAssembly les applications. Pour utiliser le package NuGet publié, expérimental et non pris en charge sans personnalisation comme démonstration locale, procédez comme suit :

  1. Utilisez une Blazor WebAssembly hébergée existante ou créez une solution à partir du modèle de projet à l’aide Blazor WebAssembly de Visual Studio ou en passant l’option -ho|--hosted à la dotnet new commande ().dotnet new blazorwasm -ho Pour plus d’informations, consultez Outils pour ASP.NET Core Blazor.

  2. Dans le Client projet, ajoutez le package expérimental Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle .

    Remarque

    Pour obtenir des conseils sur l'ajout de packages à des applications .NET, consultez les articles figurant sous Installer et gérer des packages dans Flux de travail de la consommation des packages (documentation NuGet). Vérifiez les versions du package sur NuGet.org.

  3. Dans le Server projet, ajoutez un point de terminaison pour servir le fichier groupé (app.bundle). Vous trouverez un exemple de code dans la section Servir le paquet depuis l'application du serveur hôte de cet article.

  4. Publiez l’application dans la configuration release.

Personnaliser le processus de Blazor WebAssembly chargement via un package NuGet

Avertissement

Les conseils fournis dans cette section avec ses trois sous-sections concernent la création d’un package NuGet à partir de zéro pour implémenter votre propre stratégie et processus de chargement personnalisé. Le package expérimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle (NuGet.org) pour .NET 6 et 7 est basé sur les instructions de cette section. Lorsque vous utilisez le package fourni dans une démonstration locale de l’approche de téléchargement de bundles à plusieurs parties, vous n’avez pas besoin de suivre les instructions de cette section. Pour obtenir des conseils sur l’utilisation du package fourni, consultez la section Package NuGet expérimental et exemple d’application .

Blazorles ressources d’application sont empaquetées dans un fichier groupé à plusieurs parties et chargées par le navigateur via un initialiseur JavaScript personnalisé (JS). Pour une application consommant le package avec l’initialiseur JS , l’application nécessite uniquement que le fichier groupé soit servi lorsque demandé. Tous les autres aspects de cette approche sont gérés de manière transparente.

Quatre personnalisations sont requises pour le chargement par défaut d’une application publiée Blazor :

  • Tâche MSBuild pour transformer les fichiers de publication.
  • Un package NuGet avec des cibles MSBuild qui s'accroche au processus de Blazor publication, transforme la sortie et définit un ou plusieurs fichiers Blazor Publish Extension (dans ce cas, un seul bundle).
  • Un initialisateur JS pour mettre à jour le callback du chargeur de ressources Blazor WebAssembly afin qu'il charge le bundle et fournisse à l'application les fichiers individuels.
  • Une aide sur l'application hôte Server pour s'assurer que le bundle est servi aux clients sur requête.

Créer une tâche MSBuild pour personnaliser la liste des fichiers publiés et définir de nouvelles extensions

Créez une tâche MSBuild en tant que classe C# publique qui peut être importée dans le cadre d’une compilation MSBuild et qui peut interagir avec la build.

Les éléments suivants sont requis pour la classe C# :

Remarque

Le package NuGet pour les exemples de cet article est nommé après le package fourni par Microsoft. Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle Pour obtenir des conseils sur l’affectation de noms et la production de votre propre package NuGet, consultez les articles NuGet suivants :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="{VERSION}" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="{VERSION}" />
  </ItemGroup>

</Project>

Déterminez les dernières versions des packages pour les espaces réservés {VERSION} sur NuGet.org :

Pour créer la tâche MSBuild, créez une classe C# publique qui étend Microsoft.Build.Utilities.Task (et non System.Threading.Tasks.Task) et déclarez trois propriétés :

  • PublishBlazorBootStaticWebAsset: liste de fichiers à publier pour l’application Blazor .
  • BundlePath: Le chemin où la bundle est écrite.
  • Extension: nouvelles extensions de publication à inclure dans la build.

L’exemple BundleBlazorAssets de classe suivant est un point de départ pour une personnalisation supplémentaire :

  • Dans la Execute méthode, le bundle est créé à partir des trois types de fichiers suivants :
    • Fichiers JavaScript (dotnet.js)
    • Fichiers WebAssembly (Wasm) (dotnet.wasm)
    • DLL d'application (.dll)
  • Un bundle multipart/form-data est créé. Chaque fichier est ajouté au bundle avec ses descriptions respectives via l’en-tête Content-Disposition et l’en-tête Content-Type.
  • Une fois le bundle créé, le bundle est écrit dans un fichier.
  • La build est configurée pour l’extension. Le code suivant crée un élément d’extension et l’ajoute à la Extension propriété. Chaque élément d’extension contient trois éléments de données :
    • Chemin d’accès au fichier d’extension.
    • Le chemin URL relatif à la racine de l'application Blazor WebAssembly.
    • Nom de l’extension, qui regroupe les fichiers générés par une extension donnée.

Après avoir atteint les objectifs précédents, la tâche MSBuild est créée pour personnaliser la sortie de la publication Blazor. Blazor se charge de rassembler les extensions et de s'assurer qu'elles sont copiées au bon endroit dans le dossier de sortie de la publication (par exemple, bin\Release\net6.0\publish). Les mêmes optimisations (par exemple, compression) sont appliquées aux fichiers JavaScript, Wasm et DLL, comme Blazor s’applique à d’autres fichiers.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/BundleBlazorAssets.cs :

using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks
{
    public class BundleBlazorAssets : Task
    {
        [Required]
        public ITaskItem[]? PublishBlazorBootStaticWebAsset { get; set; }

        [Required]
        public string? BundlePath { get; set; }

        [Output]
        public ITaskItem[]? Extension { get; set; }

        public override bool Execute()
        {
            var bundle = new MultipartFormDataContent(
                "--0a7e8441d64b4bf89086b85e59523b7d");

            foreach (var asset in PublishBlazorBootStaticWebAsset)
            {
                var name = Path.GetFileName(asset.GetMetadata("RelativePath"));
                var fileContents = File.OpenRead(asset.ItemSpec);
                var content = new StreamContent(fileContents);
                var disposition = new ContentDispositionHeaderValue("form-data");
                disposition.Name = name;
                disposition.FileName = name;
                content.Headers.ContentDisposition = disposition;
                var contentType = Path.GetExtension(name) switch
                {
                    ".js" => "text/javascript",
                    ".wasm" => "application/wasm",
                    _ => "application/octet-stream"
                };
                content.Headers.ContentType = 
                    MediaTypeHeaderValue.Parse(contentType);
                bundle.Add(content);
            }

            using (var output = File.Open(BundlePath, FileMode.OpenOrCreate))
            {
                output.SetLength(0);
                bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
                    .GetResult();
                output.Flush(true);
            }

            var bundleItem = new TaskItem(BundlePath);
            bundleItem.SetMetadata("RelativePath", "app.bundle");
            bundleItem.SetMetadata("ExtensionName", "multipart");

            Extension = new ITaskItem[] { bundleItem };

            return true;
        }
    }
}

Créer un package NuGet pour transformer automatiquement la sortie de publication

Générez un package NuGet avec des cibles MSBuild qui sont automatiquement incluses lorsque le package est référencé :

  • Créez un Razor projet de bibliothèque de classes (RCL).
  • Créez un fichier cible suivant les conventions NuGet pour importer automatiquement le package dans les projets consommants. Par exemple, créez build\net6.0\{PACKAGE ID}.targets, où {PACKAGE ID} est l’identificateur de package du package.
  • Collectez la sortie de la bibliothèque de classes contenant la tâche MSBuild et vérifiez que la sortie est empaquetée à l’emplacement approprié.
  • Ajoutez le code MSBuild nécessaire pour l’attacher au Blazor pipeline et appelez la tâche MSBuild pour générer le bundle.

L’approche décrite dans cette section utilise uniquement le package pour fournir des cibles et du contenu, qui est différent de la plupart des packages où le package inclut une DLL de bibliothèque.

Avertissement

L’exemple de package décrit dans cette section montre comment personnaliser le processus de Blazor publication. L’exemple de package NuGet est utilisé uniquement comme démonstration locale. L’utilisation de ce package en production n’est pas prise en charge.

Remarque

Le package NuGet pour les exemples de cet article est nommé après le package fourni par Microsoft. Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle Pour obtenir des conseils sur l’affectation de noms et la production de votre propre package NuGet, consultez les articles NuGet suivants :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.csproj :

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <NoWarn>NU5100</NoWarn>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Description>
      Sample demonstration package showing how to customize the Blazor publish 
      process. Using this package in production is not supported!
    </Description>
    <IsPackable>true</IsPackable>
    <IsShipping>true</IsShipping>
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <ItemGroup>
    <None Update="build\**" 
          Pack="true" 
          PackagePath="%(Identity)" />
    <Content Include="_._" 
             Pack="true" 
             PackagePath="lib\net6.0\_._" />
  </ItemGroup>

  <Target Name="GetTasksOutputDlls" 
          BeforeTargets="CoreCompile">
    <MSBuild Projects="..\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj" 
             Targets="Publish;PublishItemsOutputGroup" 
             Properties="Configuration=Release">
      <Output TaskParameter="TargetOutputs" 
              ItemName="_TasksProjectOutputs" />
    </MSBuild>
    <ItemGroup>
      <Content Include="@(_TasksProjectOutputs)" 
               Condition="'%(_TasksProjectOutputs.Extension)' == '.dll'" 
               Pack="true" 
               PackagePath="tasks\%(_TasksProjectOutputs.TargetPath)" 
               KeepMetadata="Pack;PackagePath" />
    </ItemGroup>
  </Target>

</Project>

Remarque

La <NoWarn>NU5100</NoWarn> propriété de l’exemple précédent supprime l’avertissement concernant les assemblages placés dans le tasks répertoire. Pour plus d’informations, consultez NuGet Warning NU5100.

Ajoutez un .targets fichier pour connecter la tâche MSBuild au pipeline de build. Dans ce fichier, les objectifs suivants sont atteints :

  • Importez la tâche dans le processus de génération. Notez que le chemin d’accès à la DLL est relatif à l’emplacement final du fichier dans le package.
  • La propriété ComputeBlazorExtensionsDependsOn attache la cible personnalisée au pipeline Blazor WebAssembly.
  • Capturez la Extension propriété sur la sortie de la tâche et ajoutez-la à BlazorPublishExtension pour informer Blazor de l’extension. L'invocation de la tâche dans la cible produit le bundle. La liste des fichiers publiés est fournie par le Blazor WebAssembly pipeline dans le groupe d’éléments PublishBlazorBootStaticWebAsset . Le chemin d’accès au bundle est défini à l’aide du IntermediateOutputPath (généralement à l’intérieur du obj dossier). En fin de compte, le bundle est copié automatiquement à l'emplacement correct dans le dossier de sortie de la publication (par exemple, bin\Release\net6.0\publish).

Lorsqu'il est référencé, le package génère un ensemble des fichiers Blazor pendant la publication.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/build/net6.0/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.targets :

<Project>
  <UsingTask 
    TaskName="Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.BundleBlazorAssets" 
    AssemblyFile="$(MSBuildThisProjectFileDirectory)..\..\tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.dll" />

  <PropertyGroup>
    <ComputeBlazorExtensionsDependsOn>
      $(ComputeBlazorExtensionsDependsOn);_BundleBlazorDlls
    </ComputeBlazorExtensionsDependsOn>
  </PropertyGroup>

  <Target Name="_BundleBlazorDlls">
    <BundleBlazorAssets
      PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
      BundlePath="$(IntermediateOutputPath)bundle.multipart">
      <Output TaskParameter="Extension" 
              ItemName="BlazorPublishExtension"/>
    </BundleBlazorAssets>
  </Target>

</Project>

Amorcer automatiquement Blazor à partir du bundle

Le package NuGet tire parti des initialiseurs JavaScript (JS) pour démarrer automatiquement une Blazor WebAssembly application à partir de l’offre groupée au lieu d’utiliser des fichiers DLL individuels. JS les initialiseurs sont utilisés pour modifier le Blazorchargeur de ressources de démarrage et utiliser le paquet.

Pour créer un initialisateur JS, ajoutez un fichier JS portant le nom {NAME}.lib.module.js au dossier wwwroot du projet de package, où l'espace réservé {NAME} est l'identifiant du package. Par exemple, le fichier du package Microsoft est nommé Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Les fonctions exportées beforeWebAssemblyStart et afterWebAssemblyStarted gèrent le chargement.

Les initialisateurs JS :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js :

const resources = new Map();

export async function beforeWebAssemblyStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterWebAssemblyStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

Pour créer un initialisateur JS, ajoutez un fichier JS portant le nom {NAME}.lib.module.js au dossier wwwroot du projet de package, où l'espace réservé {NAME} est l'identifiant du package. Par exemple, le fichier du package Microsoft est nommé Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Les fonctions exportées beforeStart et afterStarted gèrent le chargement.

Les initialisateurs JS :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js :

const resources = new Map();

export async function beforeStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

Servir l’offre groupée à partir de l’application serveur hôte

En raison des restrictions de sécurité, ASP.NET Core ne sert pas le app.bundle fichier. Un assistance de traitement des demandes est nécessaire pour traiter le fichier lorsqu’il est demandé par les clients.

Remarque

Étant donné que les mêmes optimisations sont appliquées de manière transparente aux extensions de publication qui sont appliquées aux fichiers de l'application, les fichiers d'actifs compressés app.bundle.gz et app.bundle.br sont générés automatiquement lors de la publication.

Placez le code C# dans Program.cs du projet Server immédiatement avant la ligne qui définit le fichier de repli sur index.html (app.MapFallbackToFile("index.html");) pour répondre à une requête concernant le fichier de bundle (par exemple, app.bundle) :

app.MapGet("app.bundle", (HttpContext context) =>
{
    string? contentEncoding = null;
    var contentType = 
        "multipart/form-data; boundary=\"--0a7e8441d64b4bf89086b85e59523b7d\"";
    var fileName = "app.bundle";

    var acceptEncodings = context.Request.Headers.AcceptEncoding;

    if (Microsoft.Net.Http.Headers.StringWithQualityHeaderValue
        .StringWithQualityHeaderValue
        .TryParseList(acceptEncodings, out var encodings))
    {
        if (encodings.Any(e => e.Value == "br"))
        {
            contentEncoding = "br";
            fileName += ".br";
        }
        else if (encodings.Any(e => e.Value == "gzip"))
        {
            contentEncoding = "gzip";
            fileName += ".gz";
        }
    }

    if (contentEncoding != null)
    {
        context.Response.Headers.ContentEncoding = contentEncoding;
    }

    return Results.File(
        app.Environment.WebRootFileProvider.GetFileInfo(fileName)
            .CreateReadStream(), contentType);
});

Le type de contenu correspond au type défini précédemment dans la tâche de génération. Le point de terminaison vérifie les encodages de contenu acceptés par le navigateur et sert le fichier optimal, Brotli (.br) ou Gzip (.gz).