Dela via


Utplaceringslayout för appar som är värdbaserade på ASP.NET Core Blazor WebAssembly

Anmärkning

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Viktigt!

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Den här artikeln beskriver hur du aktiverar värdbaserade Blazor WebAssembly distributioner i miljöer som blockerar nedladdning och körning av DLL-filer (Dynamic Link Library).

Anmärkning

Den här vägledningen tar upp miljöer som blockerar klienter från att ladda ned och köra DLL:er. I .NET 8 eller senare använder Blazor filformatet Webcil för att lösa problemet. För mer information, se Hantera och driftsätta ASP.NET Core Blazor WebAssembly. Flerdelspaket med hjälp av det experimentella NuGet-paketet som beskrivs i den här artikeln stöds inte för Blazor appar i .NET 8 eller senare. Du kan använda vägledningen i den här artikeln för att skapa ett eget NuGet-paket med flera delar för .NET 8 eller senare.

Blazor WebAssembly appar kräver dynamiska länkbibliotek (DLL:er) för att fungera, men vissa miljöer hindrar klienter från att ladda ned och köra DLL:er. Säkerhetsprodukter kan ofta genomsöka innehållet i filer som passerar nätverket och blockera eller placera DLL-filer i karantän. Den här artikeln beskriver en metod för att aktivera Blazor WebAssembly appar i dessa miljöer, där en paketfil med flera delar skapas från appens DLL:er så att DLL:erna kan laddas ned tillsammans genom att kringgå säkerhetsbegränsningar.

Blazor WebAssembly appar kräver dynamiska länkbibliotek (DLL:er) för att fungera, men vissa miljöer hindrar klienter från att ladda ned och köra DLL:er. I en delmängd av dessa miljöer räcker det att ändra filnamnstillägget för DLL-filer (.dll) för att kringgå säkerhetsbegränsningar, men säkerhetsprodukter kan ofta skanna innehållet i filer som passerar nätverket och blockera eller placera DLL-filer i karantän. Den här artikeln beskriver en metod för att aktivera Blazor WebAssembly appar i dessa miljöer, där en paketfil med flera delar skapas från appens DLL:er så att DLL:erna kan laddas ned tillsammans genom att kringgå säkerhetsbegränsningar.

En värdbaserad Blazor WebAssembly app kan anpassa sina publicerade filer och paketering av app-DLL:er med hjälp av följande funktioner:

  • JavaScript-initierare som gör det möjligt att Blazor anpassa startprocessen.
  • MSBuild-utökningsbarhet för att transformera listan över publicerade filer och definiera Blazor Publiceringstillägg. Blazor Publiceringstillägg är filer som definieras under publiceringsprocessen och som ger en alternativ representation för den uppsättning filer som krävs för att köra en publicerad Blazor WebAssembly app. I den här artikeln skapas ett Blazor publiceringstillägg som skapar ett flerdelspaket med alla appens DLL:er packade i en enda fil så att DLL:erna kan laddas ned tillsammans.

Metoden som visas i den här artikeln fungerar som en utgångspunkt för utvecklare att utforma sina egna strategier och anpassade inläsningsprocesser.

Varning

Alla metoder som vidtas för att kringgå en säkerhetsbegränsning måste noggrant övervägas för dess säkerhetskonsekvenser. Vi rekommenderar att du utforskar ämnet ytterligare med organisationens nätverkssäkerhetspersonal innan du använder metoden i den här artikeln. Alternativ att överväga är:

  • Aktivera säkerhetsinstallationer och säkerhetsprogram för att tillåta nätverksklienter att ladda ned och använda de exakta filer som krävs av en Blazor WebAssembly app.
  • Växla från Blazor WebAssembly värdmodellen till Blazor Server värdmodellen, som underhåller appens C#-kod på servern och inte kräver nedladdning av DLL:er till klienter. Blazor Server erbjuder också fördelen med att hålla C#-kod privat utan att kräva användning av webb-API-appar för C#-kodsekretess med Blazor WebAssembly appar.

Experimentellt NuGet-paket och exempelapp

Metoden som beskrivs i den här artikeln används av experimentpaketetMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle (NuGet.org) för appar som riktar sig till .NET 6 eller senare. Paketet innehåller MSBuild-mål för att anpassa publiceringsutdata Blazor och en JavaScript-initierare för att använda en anpassad startresursinläsare, som var och en beskrivs i detalj senare i den här artikeln.

Varning

Experimentella funktioner och förhandsversionsfunktioner tillhandahålls i syfte att samla in feedback och stöds inte för produktionsanvändning.

Senare i den här artikeln ger avsnittet Anpassa Blazor WebAssembly inläsningsprocessen via ett NuGet-paket med dess tre underavsnitt detaljerade förklaringar av konfigurationen och koden i Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle paketet. De detaljerade förklaringarna är viktiga att förstå när du skapar en egen strategi och en anpassad inläsningsprocess för Blazor WebAssembly appar. Utför följande steg för att använda det publicerade, experimentella NuGet-paketet som inte stöds utan anpassning som en lokal demonstration:

  1. Använd en befintlig värdbaserad Blazor WebAssemblylösning eller skapa en ny lösning från projektmallen Blazor WebAssembly med hjälp av Visual Studio eller genom att skicka -ho|--hosted alternativet till dotnet new kommandot (dotnet new blazorwasm -ho). Mer information finns i Verktyg för ASP.NET Core Blazor.

  2. Client Lägg till det experimentella Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle paketet i projektet.

    Anmärkning

    Vägledning om hur du lägger till paket i .NET-appar finns i artiklarna under Installera och hantera paket i arbetsflödet för paketförbrukning (NuGet-dokumentation). Bekräfta rätt paketversioner på NuGet.org.

  3. Server I projektet lägger du till en slutpunkt för att hantera paketfilen (app.bundle). Exempelkod finns i avsnittet Hantera paketet från värdserverappen i den här artikeln.

  4. Publicera appen i Versionskonfiguration.

Blazor WebAssembly Anpassa inläsningsprocessen via ett NuGet-paket

Varning

Vägledningen i det här avsnittet med dess tre underavsnitt handlar om att skapa ett NuGet-paket från grunden för att implementera din egen strategi och anpassade inläsningsprocess. Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle Experimentpaketet (NuGet.org) för .NET 6 och 7 baseras på vägledningen i det här avsnittet. När du använder det tillhandahållna paketet i en lokal demonstration av nedladdningsmetoden för flerapartspaket behöver du inte följa riktlinjerna i det här avsnittet. Mer information om hur du använder det tillhandahållna paketet finns i avsnittet Experimentella NuGet-paket och exempelappar .

Blazorappresurser packas i en paketfil med flera delar och läses in av webbläsaren via en anpassad JavaScript-initierare (JS). För en app som använder paketet med JS-initieraren kräver appen endast att bundle-filen levereras när den efterfrågas. Alla andra aspekter av den här metoden hanteras transparent.

Fyra anpassningar krävs för hur en standardpublicerad Blazor app laddas:

  • En MSBuild-uppgift för att transformera publiceringsfilerna.
  • Ett NuGet-paket med MSBuild-mål som ansluter till Blazor publiceringsprocessen, transformerar utdata och definierar en eller flera Blazor publiceringstilläggsfiler (i det här fallet ett enda paket).
  • En JS initialiserare för att uppdatera återanropet Blazor WebAssembly till resursinläsaren så att den läser in paketet och tillhandahåller appen med de enskilda filerna.
  • En hjälpfunktion i värdappen Server för att säkerställa att paketet levereras till klienter på begäran.

Skapa en MSBuild-uppgift för att anpassa listan över publicerade filer och definiera nya tillägg

Skapa en MSBuild-uppgift som en offentlig C#-klass som kan importeras som en del av en MSBuild-kompilering och som kan interagera med bygget.

Följande krävs för C#-klassen:

Anmärkning

NuGet-paketet för exemplen i den här artikeln namnges efter paketet som tillhandahålls av Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Vägledning om hur du namnger och producerar ditt eget NuGet-paket finns i följande NuGet-artiklar:

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>

Fastställ de senaste paketversionerna för {VERSION} platshållarna på NuGet.org:

Skapa uppgiften MSBuild genom att skapa en offentlig C#-klass som utökar Microsoft.Build.Utilities.Task (inte System.Threading.Tasks.Task) och deklarerar tre egenskaper:

  • PublishBlazorBootStaticWebAsset: Listan över filer som ska publiceras för Blazor appen.
  • BundlePath: Sökvägen där paketet skrivs.
  • Extension: De nya publiceringstillägg som ska ingå i kompileringen.

Följande exempelklass BundleBlazorAssets är en startpunkt för ytterligare anpassning:

  • Execute I metoden skapas paketet från följande tre filtyper:
    • JavaScript-filer (dotnet.js)
    • WebAssembly-filer (Wasm) (dotnet.wasm)
    • App-DLLs (.dll)
  • Ett multipart/form-data paket skapas. Varje fil läggs till i paketet med sina respektive beskrivningar via rubriken Content-Disposition och rubriken Innehållstyp.
  • När paketet har skapats skrivs paketet till en fil.
  • Bygget är konfigurerat för tillägget. Följande kod skapar ett tilläggsobjekt och lägger till det i Extension egenskapen. Varje tilläggsobjekt innehåller tre datadelar:
    • Sökvägen till förlängningsfilen.
    • URL-sökvägen relativ till applikationens Blazor WebAssembly rot.
    • Namnet på tillägget, som grupperar filerna som skapas av ett visst tillägg.

När de föregående målen har uppnåtts skapas MSBuild-uppgiften för att anpassa utdata för publicering Blazor. Blazor tar hand om att samla in tilläggen och se till att tilläggen kopieras till rätt plats i mappen publicera utdata (till exempel bin\Release\net6.0\publish). Samma optimeringar (till exempel komprimering) tillämpas på JavaScript-, Wasm- och DLL-filerna som Blazor gäller för andra filer.

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

Skapa ett NuGet-paket för att automatiskt transformera publiceringsutdata

Generera ett NuGet-paket med MSBuild-mål som inkluderas automatiskt när paketet refereras:

  • Skapa ett nytt Razor RCL-projekt (klassbibliotek).
  • Skapa en målfil efter NuGet-konventioner för att automatiskt importera paketet i förbrukande projekt. Du kan till exempel skapa build\net6.0\{PACKAGE ID}.targets, där {PACKAGE ID} är paketidentifieraren för paketet.
  • Samla in utdata från klassbiblioteket som innehåller MSBuild-aktiviteten och bekräfta att utdata är packade på rätt plats.
  • Lägg till nödvändig MSBuild-kod för att ansluta till pipelinen Blazor och anropa MSBuild-uppgiften för att generera paketet.

Metoden som beskrivs i det här avsnittet använder bara paketet för att leverera mål och innehåll, vilket skiljer sig från de flesta paket där paketet innehåller en biblioteks-DLL.

Varning

Exempelpaketet som beskrivs i det här avsnittet visar hur du anpassar Blazor publiceringsprocessen. NuGet-exempelpaketet är endast till för användning som lokal demonstration. Det går inte att använda det här paketet i produktion.

Anmärkning

NuGet-paketet för exemplen i den här artikeln namnges efter paketet som tillhandahålls av Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Vägledning om hur du namnger och producerar ditt eget NuGet-paket finns i följande NuGet-artiklar:

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>

Anmärkning

Egenskapen <NoWarn>NU5100</NoWarn> i föregående exempel undertrycker varningen om de sammansättningar som finns i tasks mappen. Mer information finns i NuGet Warning NU5100.

Lägg till en .targets-fil för att koppla MSBuild-tasken till byggpipelinjen. I den här filen uppnås följande mål:

  • Importera uppgiften till byggprocessen. Observera att sökvägen till DLL:en är relativ till den ultimata platsen för filen i paketet.
  • Egenskapen ComputeBlazorExtensionsDependsOn kopplar det anpassade målet till pipelinen Blazor WebAssembly .
  • Fånga egenskapen från uppgiftsutdata och lägg till den i Extension för att berätta för BlazorPublishExtension om tillägget. Om du anropar uppgiften i målet skapas paketet. Listan över publicerade filer tillhandahålls av pipelinen Blazor WebAssembly i PublishBlazorBootStaticWebAsset objektgruppen. Paketsökvägen definieras med hjälp av IntermediateOutputPath (vanligtvis i obj-mappen). Slutligen kopieras paketet automatiskt till rätt plats i mappen publicera utdata (till exempel bin\Release\net6.0\publish).

När paketet refereras genererar det en bunt av Blazor filerna under publiceringen.

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>

Starta upp Blazor automatiskt från paketet

NuGet-paketet använder JavaScript-initierare (JS) för att automatiskt starta en Blazor WebAssembly app från paketet i stället för att använda enskilda DLL-filer. JSinitialisatörer används för att ändra Blazorstartresursladdaren och använda paketet.

Om du vill skapa en JS initialiserare lägger du till en JS fil med namnet {NAME}.lib.module.js i wwwroot mappen för paketprojektet, där {NAME} platshållaren är paketidentifieraren. Filen för Microsoft-paketet heter Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.jstill exempel . Exporterade funktioner beforeWebAssemblyStart och afterWebAssemblyStarted hanterar inläsning.

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

Om du vill skapa en JS initialiserare lägger du till en JS fil med namnet {NAME}.lib.module.js i wwwroot mappen för paketprojektet, där {NAME} platshållaren är paketidentifieraren. Filen för Microsoft-paketet heter Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.jstill exempel . Exporterade funktioner beforeStart och afterStarted hanterar inläsning.

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

Hantera paketet från värdserverappen

På grund av säkerhetsbegränsningar betjänar app.bundle ASP.NET Core inte filen. En hjälp för bearbetning av begäran krävs för att hantera filen när den begärs av klienter.

Anmärkning

Eftersom samma optimeringar genomförs på ett transparent sätt på de publiceringstillägg som appliceras på appens filer, skapas app.bundle.gz och app.bundle.br komprimerade resursfiler automatiskt vid publicering.

Placera C#-kod i Program.csServer projektet omedelbart före raden som anger återställningsfilen till index.html (app.MapFallbackToFile("index.html");) för att svara på en begäran om paketfilen (till exempel 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);
});

Innehållstypen matchar den typ som definierades tidigare i byggaktiviteten. Slutpunkten söker efter innehållskodningar som godkänts av webbläsaren och hanterar den optimala filen, Brotli (.br) eller Gzip (.gz).