aspnetcore/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs

928 lines
51 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.IO.Compression;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.NET.Sdk.BlazorWebAssembly;
using Microsoft.AspNetCore.Testing;
using Xunit;
using static Microsoft.AspNetCore.Razor.Design.IntegrationTests.ServiceWorkerAssert;
using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary<string, string>;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class WasmPublishIntegrationTest
{
private static readonly string DotNetJsFileName = $"dotnet.{BuildVariables.MicrosoftNETCoreAppRuntimeVersion}.js";
[Fact]
public async Task Publish_MinimalApp_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm-minimal");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm-minimal.dll");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly);
VerifyBootManifestHashes(result, blazorPublishDirectory);
}
[Fact]
public async Task Publish_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary", "LinkBaseToWebRoot" });
project.Configuration = "Debug";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify link item assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "js", "LinkedScript.js");
var cssFile = Assert.FileExists(result, blazorPublishDirectory, "css", "app.css");
Assert.FileContains(result, cssFile, ".publish");
Assert.FileDoesNotExist(result, "dist", "Fake-License.txt");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly);
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
VerifyTypeGranularTrimming(result, blazorPublishDirectory);
}
[Fact]
public async Task Publish_WithScopedCss_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary", "LinkBaseToWebRoot" });
File.WriteAllText(Path.Combine(project.DirectoryPath, "App.razor.css"), "h1 { font-size: 16px; }");
project.Configuration = "Debug";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify scoped css
Assert.FileExists(result, blazorPublishDirectory, "_framework", "scoped.styles.css");
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify link item assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "js", "LinkedScript.js");
var cssFile = Assert.FileExists(result, blazorPublishDirectory, "css", "app.css");
Assert.FileContains(result, cssFile, ".publish");
Assert.FileDoesNotExist(result, "dist", "Fake-License.txt");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly);
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_InRelease_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary", "LinkBaseToWebRoot" });
project.Configuration = "Release";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify link item assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "js", "LinkedScript.js");
var cssFile = Assert.FileExists(result, blazorPublishDirectory, "css", "app.css");
Assert.FileContains(result, cssFile, ".publish");
Assert.FileDoesNotExist(result, "dist", "Fake-License.txt");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly);
}
[Fact]
public async Task Publish_WithExistingWebConfig_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary", "LinkBaseToWebRoot" });
project.Configuration = "Release";
var webConfigContents = "test webconfig contents";
AddFileToProject(project, "web.config", webConfigContents);
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
Assert.FileContains(result, Path.Combine(publishDirectory, "web.config"), webConfigContents);
}
[Fact]
public async Task Publish_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary" });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
VerifyCompression(result, blazorPublishDirectory);
}
private static void VerifyCompression(MSBuildResult result, string blazorPublishDirectory)
{
var original = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
var compressed = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json.br");
using var brotliStream = new BrotliStream(File.OpenRead(compressed), CompressionMode.Decompress);
using var textReader = new StreamReader(brotliStream);
var uncompressedText = textReader.ReadToEnd();
var originalText = File.ReadAllText(original);
Assert.Equal(originalText, uncompressedText);
}
[Fact]
public async Task Publish_WithTrimmingdDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary" });
project.AddProjectFileContent(
@"<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br"); //
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
// Verify assemblies are not trimmed
var loggingAssemblyPath = Path.Combine(blazorPublishDirectory, "_framework", "Microsoft.Extensions.Logging.Abstractions.dll");
Assert.AssemblyContainsType(result, loggingAssemblyPath, "Microsoft.Extensions.Logging.Abstractions.NullLogger");
}
[Fact]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23756")]
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<!-- Workaround for https://github.com/mono/linker/issues/1390 -->
<PublishTrimmed>false</PublishTrimmed>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
VerifyBootManifestHashes(result, blazorPublishDirectory);
}
[Fact]
[QuarantinedTest]
public async Task Publish_HostedApp_DefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
// Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects
// referenced by the Hosted project appear in the publish directory
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
Assert.FileExists(result, publishDirectory, "blazorwasm.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify project references appear as static web assets
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll");
// Also verify project references to the server project appear in the publish output
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
VerifyTypeGranularTrimming(result, blazorPublishDirectory);
}
[Fact]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23756")]
public async Task Publish_HostedApp_ProducesBootJsonDataWithExpectedContent()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
var wwwroot = Path.Combine(project.DirectoryPath, "..", "blazorwasm", "wwwroot");
File.WriteAllText(Path.Combine(wwwroot, "appsettings.json"), "Default settings");
File.WriteAllText(Path.Combine(wwwroot, "appsettings.development.json"), "Development settings");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var bootJsonPath = Path.Combine(project.PublishOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
var bootJsonData = ReadBootJsonData(result, bootJsonPath);
var runtime = bootJsonData.resources.runtime.Keys;
Assert.Contains(DotNetJsFileName, runtime);
Assert.Contains("dotnet.wasm", runtime);
var assemblies = bootJsonData.resources.assembly.Keys;
Assert.Contains("blazorwasm.dll", assemblies);
Assert.Contains("RazorClassLibrary.dll", assemblies);
Assert.Contains("System.Text.Json.dll", assemblies);
// No pdbs
Assert.Null(bootJsonData.resources.pdb);
Assert.Null(bootJsonData.resources.satelliteResources);
Assert.Contains("appsettings.json", bootJsonData.config);
Assert.Contains("appsettings.development.json", bootJsonData.config);
}
[Fact]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23397")]
public async Task Publish_HostedApp_WithSatelliteAssemblies()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", "classlibrarywithsatelliteassemblies", });
var wasmProject = project.GetSibling("blazorwasm");
wasmProject.AddProjectFileContent(
@"
<PropertyGroup>
<!-- Workaround for https://github.com/mono/linker/issues/1390 -->
<PublishTrimmed>false</PublishTrimmed>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var resxfileInProject = Path.Combine(wasmProject.DirectoryPath, "Resources.ja.resx.txt");
File.Move(resxfileInProject, Path.Combine(wasmProject.DirectoryPath, "Resource.ja.resx"));
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/restore");
Assert.BuildPassed(result);
var bootJsonPath = Path.Combine(project.PublishOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
var bootJsonData = ReadBootJsonData(result, bootJsonPath);
var publishOutputDirectory = project.PublishOutputDirectory;
Assert.FileExists(result, publishOutputDirectory, "wwwroot", "_framework", "blazorwasm.dll");
Assert.FileExists(result, publishOutputDirectory, "wwwroot", "_framework", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, publishOutputDirectory, "wwwroot", "_framework", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, publishOutputDirectory, "wwwroot", "_framework", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
// Regression test for https://github.com/dotnet/aspnetcore/issues/18752
public async Task Publish_HostedApp_WithoutTrimming_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
AddWasmProjectContent(project, @"
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>");
// VS builds projects individually and then a publish with BuildDependencies=false, but building the main project is a close enough approximation for this test.
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
Assert.BuildPassed(result);
// Publish
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildDependencies=false");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
// Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects
// referenced by the Hosted project appear in the publish directory
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
Assert.FileExists(result, publishDirectory, "blazorwasm.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify project references appear as static web assets
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll");
// Also verify project references to the server project appear in the publish output
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_HostedApp_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_HostedApp_VisualStudio()
{
// Simulates publishing the same way VS does by setting BuildProjectReferences=false.
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
project.Configuration = "Release";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build", "/p:BuildInsideVisualStudio=true");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildProjectReferences=false /p:BuildInsideVisualStudio=true");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_HostedAppWithScopedCss_VisualStudio()
{
// Simulates publishing the same way VS does by setting BuildProjectReferences=false.
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
File.WriteAllText(Path.Combine(project.SolutionPath, "blazorwasm", "App.razor.css"), "h1 { font-size: 16px; }");
project.Configuration = "Release";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build", "/p:BuildInsideVisualStudio=true");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildProjectReferences=false /p:BuildInsideVisualStudio=true");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify scoped css
Assert.FileExists(result, blazorPublishDirectory, "_framework", "scoped.styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
// Regression test to verify satellite assemblies from the blazor app are copied to the published app's wwwroot output directory as
// part of publishing in VS
[Fact]
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23397")]
public async Task Publish_HostedApp_VisualStudio_WithSatelliteAssemblies()
{
// Simulates publishing the same way VS does by setting BuildProjectReferences=false.
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
var blazorwasmProjectDirectory = Path.Combine(project.DirectoryPath, "..", "blazorwasm");
// Setup the blazorwasm app to have it's own satellite assemblies as well as transitively imported satellite assemblies.
var resxfileInProject = Path.Combine(blazorwasmProjectDirectory, "Resources.ja.resx.txt");
File.Move(resxfileInProject, Path.Combine(blazorwasmProjectDirectory, "Resource.ja.resx"));
var blazorwasmProjFile = Path.Combine(blazorwasmProjectDirectory, "blazorwasm.csproj");
var existing = File.ReadAllText(blazorwasmProjFile);
var updatedContent = @"
<PropertyGroup>
<!-- Workaround for https://github.com/mono/linker/issues/1390 -->
<PublishTrimmed>false</PublishTrimmed>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>";
var updated = existing.Replace("<!-- Test Placeholder -->", updatedContent);
File.WriteAllText(blazorwasmProjFile, updated);
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build", $"/restore");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildProjectReferences=false");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "ja", "blazorwasm.resources.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "fr", "Microsoft.CodeAnalysis.resources.dll");
var bootJson = Path.Combine(project.DirectoryPath, blazorPublishDirectory, "_framework", "blazor.boot.json");
var bootJsonFile = JsonSerializer.Deserialize<BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var satelliteResources = bootJsonFile.resources.satelliteResources;
Assert.Contains("es-ES", satelliteResources.Keys);
Assert.Contains("es-ES/classlibrarywithsatelliteassemblies.resources.dll", satelliteResources["es-ES"].Keys);
Assert.Contains("fr", satelliteResources.Keys);
Assert.Contains("fr/Microsoft.CodeAnalysis.CSharp.resources.dll", satelliteResources["fr"].Keys);
Assert.Contains("ja", satelliteResources.Keys);
Assert.Contains("ja/blazorwasm.resources.dll", satelliteResources["ja"].Keys);
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_HostedApp_WithRidSpecifiedInCLI_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted-rid", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
project.RuntimeIdentifier = "linux-x64";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:RuntimeIdentifier=linux-x64");
AssertRIDPublishOuput(project, result);
}
[Fact]
public async Task Publish_HostedApp_WithRid_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted-rid", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
project.RuntimeIdentifier = "linux-x64";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
AssertRIDPublishOuput(project, result);
}
private static void AssertRIDPublishOuput(ProjectDirectory project, MSBuildResult result)
{
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted-rid.dll");
Assert.FileExists(result, publishDirectory, "libhostfxr.so"); // Verify that we're doing a self-contained deployment
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
Assert.FileExists(result, publishDirectory, "blazorwasm.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "_framework", DotNetJsFileName);
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
// Verify project references appear as static web assets
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll");
// Also verify project references to the server project appear in the publish output
Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
VerifyBootManifestHashes(result, blazorPublishDirectory);
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
assetsManifestPath: "custom-service-worker-assets.js");
}
[Fact]
public async Task Publish_WithInvariantGlobalizationEnabled_DoesNotCopyGlobalizationData()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm-minimal");
project.AddProjectFileContent(
@"
<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishOutputDirectory = project.PublishOutputDirectory;
var bootJsonPath = Path.Combine(publishOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
var bootJsonData = ReadBootJsonData(result, bootJsonPath);
var runtime = bootJsonData.resources.runtime.Keys;
Assert.Contains("dotnet.wasm", runtime);
Assert.DoesNotContain("icudt.dat", runtime);
Assert.FileExists(result, publishOutputDirectory, "wwwroot", "_framework", "dotnet.wasm");
Assert.FileDoesNotExist(result, publishOutputDirectory, "wwwroot", "_framework", "icudt.dat");
}
private static void AddWasmProjectContent(ProjectDirectory project, string content)
{
var path = Path.Combine(project.SolutionPath, "blazorwasm", "blazorwasm.csproj");
var existing = File.ReadAllText(path);
var updated = existing.Replace("<!-- Test Placeholder -->", content);
File.WriteAllText(path, updated);
}
private static void AddFileToProject(ProjectDirectory project, string filename, string content)
{
var path = Path.Combine(project.DirectoryPath, filename);
File.WriteAllText(path, content);
}
private static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory)
{
var bootManifestResolvedPath = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
var bootManifestJson = File.ReadAllText(bootManifestResolvedPath);
var bootManifest = JsonSerializer.Deserialize<BootJsonData>(bootManifestJson);
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.assembly);
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.runtime);
if (bootManifest.resources.pdb != null)
{
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.pdb);
}
if (bootManifest.resources.satelliteResources != null)
{
foreach (var resourcesForCulture in bootManifest.resources.satelliteResources.Values)
{
VerifyBootManifestHashes(result, blazorPublishDirectory, resourcesForCulture);
}
}
static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory, ResourceHashesByNameDictionary resources)
{
foreach (var (name, hash) in resources)
{
var relativePath = Path.Combine(blazorPublishDirectory, "_framework", name);
Assert.FileHashEquals(result, relativePath, ParseWebFormattedHash(hash));
}
}
static string ParseWebFormattedHash(string webFormattedHash)
{
Assert.StartsWith("sha256-", webFormattedHash);
return webFormattedHash.Substring(7);
}
}
private void VerifyTypeGranularTrimming(MSBuildResult result, string blazorPublishDirectory)
{
var componentsShimAssemblyPath = Path.Combine(blazorPublishDirectory, "_framework", "Microsoft.AspNetCore.Razor.Test.ComponentShim.dll");
Assert.FileExists(result, componentsShimAssemblyPath);
// RouteView is referenced by the app, so we expect it to be preserved
Assert.AssemblyContainsType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.RouteView");
// RouteData is referenced by RouteView so we expect it to be preserved.
Assert.AssemblyContainsType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.RouteData");
// CascadingParameterAttribute is not referenced by the app, and should be trimmed.
Assert.AssemblyDoesNotContainType(result, componentsShimAssemblyPath, "Microsoft.AspNetCore.Components.CascadingParameterAttribute");
}
private static BootJsonData ReadBootJsonData(MSBuildResult result, string path)
{
return JsonSerializer.Deserialize<BootJsonData>(
File.ReadAllText(Path.Combine(result.Project.DirectoryPath, path)),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
}
}