Merge branch 'blazor-wasm' into prkrishn/merge-blazor-wasm

This commit is contained in:
Pranav K 2020-05-15 10:32:07 -07:00
commit 8efeefb3d1
No known key found for this signature in database
GPG Key ID: F748807460A27E91
588 changed files with 32616 additions and 13028 deletions

View File

@ -7,9 +7,9 @@ trigger:
batch: true
branches:
include:
- blazor-wasm
- master
- release/*
- internal/release/3.*
# Run PR validation on all branches
pr:
@ -80,18 +80,11 @@ variables:
value: test
- name: _PublishArgs
value: ''
<<<<<<< HEAD
=======
# used for post-build phases, internal builds only
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- group: DotNet-AspNet-SDLValidation-Params
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
stages:
- stage: build
displayName: Build
jobs:
<<<<<<< HEAD
# Code check
- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
- template: jobs/default-build.yml
@ -116,8 +109,6 @@ stages:
publishOnError: true
includeForks: true
=======
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
# Build Windows (x64/x86)
- template: jobs/default-build.yml
parameters:
@ -151,13 +142,12 @@ stages:
-arch x64
-pack
-all
-NoBuildNative
-buildNative
/bl:artifacts/log/build.x64.binlog
$(_BuildArgs)
$(_InternalRuntimeDownloadArgs)
displayName: Build x64
<<<<<<< HEAD
# Build the x86 shared framework
# TODO: make it possible to build for one Windows architecture at a time
# This is going to actually build x86 native assets. See https://github.com/aspnet/AspNetCore/issues/7196
@ -183,8 +173,6 @@ stages:
$(_InternalRuntimeDownloadArgs)
displayName: Build SiteExtension
=======
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
# This runs code-signing on all packages, zips, and jar files as defined in build/CodeSign.targets. If https://github.com/dotnet/arcade/issues/1957 is resolved,
# consider running code-signing inline with the other previous steps.
# Sign check is disabled because it is run in a separate step below, after installers are built.
@ -212,6 +200,16 @@ stages:
/p:PublishInstallerBaseVersion=true
displayName: Build Installers
# A few files must also go to the VS package feed.
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- task: NuGetCommand@2
displayName: Push Visual Studio packages
inputs:
command: push
packagesToPush: 'artifacts/packages/**/VS.Redist.Common.AspNetCore.*.nupkg'
nuGetFeedType: external
publishFeedCredentials: 'DevDiv - VS package feed'
artifacts:
- name: Windows_Logs
path: artifacts/log/
@ -220,7 +218,6 @@ stages:
- name: Windows_Packages
path: artifacts/packages/
<<<<<<< HEAD
# Build Windows ARM
- template: jobs/default-build.yml
parameters:
@ -515,8 +512,6 @@ stages:
parameters:
inputName: Linux_musl_arm64
=======
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
# Test jobs
- template: jobs/default-build.yml
parameters:
@ -661,7 +656,6 @@ stages:
publishOnError: true
includeForks: true
<<<<<<< HEAD
# Source build
- job: Source_Build
displayName: 'Test: Linux Source Build'
@ -720,17 +714,23 @@ stages:
artifactType: Container
parallel: true
=======
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
# Publish to the BAR
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- template: /eng/common/templates/job/publish-build-assets.yml
parameters:
dependsOn:
- Windows_build
- Windows_arm_build
- CodeSign_Xplat_MacOS_x64
- CodeSign_Xplat_Linux_x64
- CodeSign_Xplat_Linux_arm
- CodeSign_Xplat_Linux_arm64
- CodeSign_Xplat_Linux_musl_x64
- CodeSign_Xplat_Linux_musl_arm64
# In addition to the dependencies above, ensure the build was successful overall.
- Linux_Test
- MacOS_Test
- Source_Build
- Windows_Templates_Test
- Windows_Test
pool:

View File

@ -3,32 +3,20 @@
<packageSources>
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<<<<<<< HEAD
<add key="darc-pub-dotnet-corefx-8a3ffed" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-8a3ffed5/nuget/v3/index.json" />
<add key="darc-pub-dotnet-corefx-66409e3" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-66409e39/nuget/v3/index.json" />
<add key="darc-pub-dotnet-blazor-cc44960" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-blazor-cc449601/nuget/v3/index.json" />
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet3.1" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json" />
<add key="dotnet3.1-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
=======
<add key="darc-pub-dotnet-core-setup-65f04fb" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-core-setup-65f04fb6/nuget/v3/index.json" />
<add key="darc-pub-dotnet-corefx-0f7f38c" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-0f7f38c4/nuget/v3/index.json" />
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="aspnet-blazor" value="https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json" />
<add key="aspnet-extensions" value="https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json" />
<add key="aspnet-entityframeworkcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json" />
<add key="aspnet-aspnetcore-tooling" value="https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json" />
<add key="aspnet-stable" value="https://dotnetfeed.blob.core.windows.net/dotnet-core-3-1-rtm-014727/index.json" />
<add key="grpc-nuget-dev" value="https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev" />
<add key="roslyn" value="https://dotnet.myget.org/F/roslyn/api/v3/index.json" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet3.1" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json" />
<add key="dotnet3.1-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="aspnetcore-dev" value="https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json" />
<add key="aspnetcore-tools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
<add key="roslyn-tools" value="https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json" />
<add key="blazor-wasm" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json" />
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
</packageSources>
</configuration>

View File

@ -18,6 +18,13 @@
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
</PropertyGroup>
<!-- Package: Microsoft.Authentication.WebAssembly.Msal-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="[3.2.0, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.ApiAuthorization.IdentityServer-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
@ -240,6 +247,48 @@
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" />
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.JSInterop.WebAssembly" Version="[3.2.0, )" />
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.Extensions.Configuration.Json" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Build-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' AND '$(TargetFramework)' == 'any' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="[3.2.0, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Server-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' AND '$(TargetFramework)' == 'netcoreapp3.1' " />
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Authentication-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.HttpHandler-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' AND '$(TargetFramework)' == 'netstandard2.1' " />
<!-- Package: Microsoft.JSInterop.WebAssembly-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.ConcurrencyLimiter-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>

View File

@ -8,6 +8,7 @@ Update this list when preparing for a new patch.
<Package Id="AspNetCoreRuntime.3.0.x64" Version="3.0.3" />
<Package Id="AspNetCoreRuntime.3.0.x86" Version="3.0.3" />
<Package Id="dotnet-sql-cache" Version="3.1.4" />
<Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.4" />
@ -36,6 +37,12 @@ Update this list when preparing for a new patch.
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="3.2.0" />
<Package Id="Microsoft.JSInterop.WebAssembly" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" />

View File

@ -34,8 +34,8 @@
$(RepoRoot)src\Installers\**\*.*proj;
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
$(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
@ -50,11 +50,6 @@
" />
</ItemGroup>
<PropertyGroup>
<!-- For the Blazor WASM branch, only build a subset of projects -->
<ProjectToBuild>$(RepoRoot)src\Components\**\*.csproj;$(RepoRoot)src\ProjectTemplates\**\*.csproj</ProjectToBuild>
</PropertyGroup>
<Choose>
<!-- Project selection can be overridden on the command line by passing in -projects -->
<When Condition="'$(ProjectToBuild)' != ''">

View File

@ -92,6 +92,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
<LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
<LatestPackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonPackageVersion)" />
<LatestPackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
<LatestPackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
@ -122,7 +123,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.1" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />

View File

@ -55,12 +55,14 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" />
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Build" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" />
<ProjectReferenceProvider Include="blazor-brotli" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" />
<ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Abstractions" ProjectPath="$(RepoRoot)src\DataProtection\Abstractions\src\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Abstractions\ref\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Cryptography.Internal" ProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\src\Microsoft.AspNetCore.Cryptography.Internal.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\ref\Microsoft.AspNetCore.Cryptography.Internal.csproj" />

View File

@ -9,9 +9,13 @@
-->
<Dependencies>
<ProductDependencies>
<Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.1.0-preview4.19605.1" Pinned="true">
<Uri>https://github.com/aspnet/Blazor</Uri>
<Sha>7868699de745fd30a654c798a99dc541b77b95c0</Sha>
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0">
<Uri>https://github.com/dotnet/blazor</Uri>
<Sha>cc449601d638ffaab58ae9487f0fd010bb178a12</Sha>
</Dependency>
<Dependency Name="System.Net.Http.Json" Version="3.2.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha>
</Dependency>
<Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.4">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore-tooling</Uri>

View File

@ -10,6 +10,11 @@
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
<AspNetCorePatchVersion>5</AspNetCorePatchVersion>
<PreReleasePreviewNumber>0</PreReleasePreviewNumber>
<ComponentsWebAssemblyMajorVersion>3</ComponentsWebAssemblyMajorVersion>
<ComponentsWebAssemblyMinorVersion>2</ComponentsWebAssemblyMinorVersion>
<ComponentsWebAssemblyPatchVersion>1</ComponentsWebAssemblyPatchVersion>
<!--
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
-->
@ -19,9 +24,6 @@
<IncludePreReleaseLabelInPackageVersion Condition=" '$(DotNetFinalVersionKind)' == 'release' ">false</IncludePreReleaseLabelInPackageVersion>
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
<PreReleaseBrandingLabel>Servicing</PreReleaseBrandingLabel>
<!-- Blazor Client packages will not RTM with 3.1 -->
<BlazorClientPreReleasePreviewNumber>4</BlazorClientPreReleasePreviewNumber>
<BlazorClientPreReleaseVersionLabel>preview$(BlazorClientPreReleasePreviewNumber)</BlazorClientPreReleaseVersionLabel>
<AspNetCoreMajorMinorVersion>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)</AspNetCoreMajorMinorVersion>
<!-- The following property may need to be updated if ingesting new versions of Extensions.Refs package. The package override version is used to create PackageOverrides.txt in the targeting pack. -->
<MicrosoftInternalExtensionsRefsPackageOverrideVersion>3.1.0</MicrosoftInternalExtensionsRefsPackageOverrideVersion>
@ -30,6 +32,7 @@
<!-- Servicing builds have different characteristics for the way dependencies, framework references, and versions are handled. -->
<IsServicingBuild Condition=" '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
<VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix>
<ComponentsWebAssemblyVersionPrefix>$(ComponentsWebAssemblyMajorVersion).$(ComponentsWebAssemblyMinorVersion).$(ComponentsWebAssemblyPatchVersion)</ComponentsWebAssemblyVersionPrefix>
<!-- TargetingPackVersionPrefix is used by projects, like .deb and .rpm, which use slightly different version formats. -->
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
<!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. -->
@ -92,12 +95,13 @@
<SystemServiceProcessServiceControllerPackageVersion>4.7.0</SystemServiceProcessServiceControllerPackageVersion>
<SystemTextEncodingsWebPackageVersion>4.7.1</SystemTextEncodingsWebPackageVersion>
<SystemTextJsonPackageVersion>4.7.2</SystemTextJsonPackageVersion>
<SystemNetHttpJsonPackageVersion>3.2.0</SystemNetHttpJsonPackageVersion>
<SystemThreadingChannelsPackageVersion>4.7.1</SystemThreadingChannelsPackageVersion>
<SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion>
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
<MicrosoftNETCorePlatformsPackageVersion>3.1.1</MicrosoftNETCorePlatformsPackageVersion>
<!-- Packages from aspnet/Blazor -->
<MicrosoftAspNetCoreBlazorMonoPackageVersion>3.1.0-preview4.19605.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
<!-- Packages from aspnet/Extensions -->
<InternalAspNetCoreAnalyzersPackageVersion>3.1.4-servicing.20221.11</InternalAspNetCoreAnalyzersPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.4-servicing.20221.11</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>

View File

@ -257,7 +257,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
if ($msbuildCmd -ne $null) {
# Workaround for https://github.com/dotnet/roslyn/issues/35793
# Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0])
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0])
if ($msbuildVersion -ge $vsMinVersion) {
return $global:_MSBuildExe = $msbuildCmd.Path

View File

@ -170,11 +170,10 @@ try {
& $PSScriptRoot\GenerateReferenceAssemblies.ps1 -ci:$ci
}
# Temporarily disable package baseline generation while we stage for publishing
# Write-Host "Re-generating package baselines"
# Invoke-Block {
# & dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/"
# }
Write-Host "Re-generating package baselines"
Invoke-Block {
& dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/"
}
Write-Host "Run git diff to check for pending changes"

View File

@ -1,16 +1,9 @@
{
"sdk": {
<<<<<<< HEAD
"version": "3.1.103"
},
"tools": {
"dotnet": "3.1.103",
=======
"version": "3.1.100"
},
"tools": {
"dotnet": "3.1.100",
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
"runtimes": {
"dotnet/x64": [
"$(MicrosoftNETCoreAppInternalPackageVersion)"
@ -32,12 +25,7 @@
},
"msbuild-sdks": {
"Yarn.MSBuild": "1.15.2",
<<<<<<< HEAD
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20213.4",
"Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20213.4"
=======
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19577.5",
"Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19577.5"
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
}
}

View File

@ -1,8 +0,0 @@
<Project>
<PropertyGroup>
<!-- Override version labels -->
<VersionPrefix>3.2.0</VersionPrefix>
<PreReleaseVersionLabel>preview1</PreReleaseVersionLabel>
<DotNetFinalVersionKind />
</PropertyGroup>
</Project>

View File

@ -1,69 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Blazor
{
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public static partial class JSInteropMethods
{
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
public static void NotifyLocationChanged(string uri, bool isInterceptedLink) { }
}
}
namespace Microsoft.AspNetCore.Blazor.Hosting
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct RootComponentMapping
{
private readonly object _dummy;
public RootComponentMapping(System.Type componentType, string selector) { throw null; }
public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string Selector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
public partial class RootComponentMappingCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.Blazor.Hosting.RootComponentMapping>
{
public RootComponentMappingCollection() { }
public void Add(System.Type componentType, string selector) { }
public void AddRange(System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Blazor.Hosting.RootComponentMapping> items) { }
public void Add<TComponent>(string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { }
}
public sealed partial class WebAssemblyHost : System.IAsyncDisposable
{
internal WebAssemblyHost() { }
public Microsoft.Extensions.Configuration.IConfiguration Configuration { get { throw null; } }
public System.IServiceProvider Services { get { throw null; } }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
public System.Threading.Tasks.Task RunAsync() { throw null; }
}
public sealed partial class WebAssemblyHostBuilder
{
internal WebAssemblyHostBuilder() { }
public Microsoft.Extensions.Configuration.IConfigurationBuilder Configuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Blazor.Hosting.RootComponentMappingCollection RootComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHost Build() { throw null; }
public static Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHostBuilder CreateDefault(string[] args = null) { throw null; }
}
}
namespace Microsoft.AspNetCore.Blazor.Http
{
public enum FetchCredentialsOption
{
Omit = 0,
SameOrigin = 1,
Include = 2,
}
public static partial class WebAssemblyHttpMessageHandlerOptions
{
public static Microsoft.AspNetCore.Blazor.Http.FetchCredentialsOption DefaultCredentials { get { throw null; } set { } }
}
}
namespace Microsoft.AspNetCore.Blazor.Rendering
{
public static partial class WebAssemblyEventDispatcher
{
[Microsoft.JSInterop.JSInvokableAttribute("DispatchEvent")]
public static System.Threading.Tasks.Task DispatchEvent(Microsoft.AspNetCore.Components.RenderTree.WebEventDescriptor eventDescriptor, string eventArgsJson) { throw null; }
}
}

View File

@ -1,112 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
/// <summary>
/// A builder for configuring and creating a <see cref="WebAssemblyHost"/>.
/// </summary>
public sealed class WebAssemblyHostBuilder
{
/// <summary>
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common
/// conventions and settings.
/// </summary>
/// <param name="args">The argument passed to the application's main method.</param>
/// <returns>A <see cref="WebAssemblyHostBuilder"/>.</returns>
public static WebAssemblyHostBuilder CreateDefault(string[] args = default)
{
// We don't use the args for anything right now, but we want to accept them
// here so that it shows up this way in the project templates.
args ??= Array.Empty<string>();
var builder = new WebAssemblyHostBuilder();
// Right now we don't have conventions or behaviors that are specific to this method
// however, making this the default for the template allows us to add things like that
// in the future, while giving `new WebAssemblyHostBuilder` as an opt-out of opinionated
// settings.
return builder;
}
/// <summary>
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> with the minimal configuration.
/// </summary>
private WebAssemblyHostBuilder()
{
// Private right now because we don't have much reason to expose it. This can be exposed
// in the future if we want to give people a choice between CreateDefault and something
// less opinionated.
Configuration = new ConfigurationBuilder();
RootComponents = new RootComponentMappingCollection();
Services = new ServiceCollection();
InitializeDefaultServices();
}
/// <summary>
/// Gets an <see cref="IConfigurationBuilder"/> that can be used to customize the application's
/// configuration sources.
/// </summary>
public IConfigurationBuilder Configuration { get; }
/// <summary>
/// Gets the collection of root component mappings configured for the application.
/// </summary>
public RootComponentMappingCollection RootComponents { get; }
/// <summary>
/// Gets the service collection.
/// </summary>
public IServiceCollection Services { get; }
/// <summary>
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
/// </summary>
/// <returns>A <see cref="WebAssemblyHost"/> object.</returns>
public WebAssemblyHost Build()
{
// Intentionally overwrite configuration with the one we're creating.
var configuration = Configuration.Build();
Services.AddSingleton<IConfiguration>(configuration);
// A Blazor application always runs in a scope. Since we want to make it possible for the user
// to configure services inside *that scope* inside their startup code, we create *both* the
// service provider and the scope here.
var services = Services.BuildServiceProvider();
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
}
private void InitializeDefaultServices()
{
Services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
Services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
Services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>)));
Services.AddSingleton<HttpClient>(s =>
{
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var navigationManager = s.GetRequiredService<NavigationManager>();
return new HttpClient
{
BaseAddress = new Uri(navigationManager.BaseUri)
};
});
}
}
}

View File

@ -1,59 +0,0 @@
// 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;
using System.Reflection;
namespace Microsoft.AspNetCore.Blazor.Http
{
/// <summary>
/// Configures options for the WebAssembly HTTP message handler.
/// </summary>
public static class WebAssemblyHttpMessageHandlerOptions
{
/// <summary>
/// Gets or sets the default value of the 'credentials' option on outbound HTTP requests.
/// Defaults to <see cref="FetchCredentialsOption.SameOrigin"/>.
/// </summary>
public static FetchCredentialsOption DefaultCredentials
{
get
{
var valueString = MonoDefaultCredentialsGetter.Value();
var result = default(FetchCredentialsOption);
if (valueString != null)
{
Enum.TryParse(valueString, out result);
}
return result;
}
set
{
MonoDefaultCredentialsSetter.Value(value.ToString());
}
}
static Func<Type> MonoWasmHttpMessageHandlerType = ()
=> Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
static Func<Type> MonoFetchCredentialsOptionType = ()
=> Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.FetchCredentialsOption");
static Lazy<PropertyInfo> MonoDefaultCredentialsProperty = new Lazy<PropertyInfo>(
() => MonoWasmHttpMessageHandlerType()?.GetProperty("DefaultCredentials", BindingFlags.Public | BindingFlags.Static));
static Lazy<Func<string>> MonoDefaultCredentialsGetter = new Lazy<Func<string>>(() =>
{
return () => MonoDefaultCredentialsProperty.Value?.GetValue(null).ToString();
});
static Lazy<Action<string>> MonoDefaultCredentialsSetter = new Lazy<Action<string>>(() =>
{
var fetchCredentialsOptionsType = MonoFetchCredentialsOptionType();
return value => MonoDefaultCredentialsProperty.Value?.SetValue(null, Enum.Parse(fetchCredentialsOptionsType, value));
});
}
}

View File

@ -1,26 +0,0 @@
// 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.ComponentModel;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor
{
/// <summary>
/// Contains methods called by interop. Intended for framework use only, not supported for use in application
/// code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class JSInteropMethods
{
/// <summary>
/// For framework use only.
/// </summary>
[JSInvokable(nameof(NotifyLocationChanged))]
public static void NotifyLocationChanged(string uri, bool isInterceptedLink)
{
WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink);
}
}
}

View File

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description>
<IsShippingPackage>false</IsShippingPackage>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.WebAssembly.Interop" />
<Reference Include="Microsoft.AspNetCore.Components.Web" />
<Reference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" />
</ItemGroup>
</Project>

View File

@ -1,34 +0,0 @@
// 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;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Blazor.Services
{
internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger
{
public IDisposable BeginScope<TState>(TState state)
{
return NoOpDisposable.Instance;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= LogLevel.Warning;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var formattedMessage = formatter(state, exception);
Console.WriteLine($"[{logLevel}] {formattedMessage}");
}
private class NoOpDisposable : IDisposable
{
public static NoOpDisposable Instance = new NoOpDisposable();
public void Dispose() { }
}
}
}

View File

@ -1,34 +0,0 @@
// 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 Microsoft.AspNetCore.Components;
using Mono.WebAssembly.Interop;
namespace Microsoft.AspNetCore.Blazor.Services
{
internal sealed class WebAssemblyJSRuntime : MonoWebAssemblyJSRuntime
{
private static readonly WebAssemblyJSRuntime _instance = new WebAssemblyJSRuntime();
private static bool _initialized;
public WebAssemblyJSRuntime()
{
JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter());
}
public static WebAssemblyJSRuntime Instance
{
get
{
if (!_initialized)
{
// This is executing in MonoWASM. Consequently we do not to have concern ourselves with thread safety.
_initialized = true;
Initialize(_instance);
}
return _instance;
}
}
}
}

View File

@ -1,23 +0,0 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Blazor.Services
{
internal class WebAssemblyLoggerFactory : ILoggerFactory
{
public void AddProvider(ILoggerProvider provider)
{
// No-op
}
public ILogger CreateLogger(string categoryName)
=> new WebAssemblyConsoleLogger<object>();
public void Dispose()
{
// No-op
}
}
}

View File

@ -1,103 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
public class WebAssemblyHostBuilderTest
{
[Fact]
public void Build_AllowsConfiguringConfiguration()
{
// Arrange
var builder = WebAssemblyHostBuilder.CreateDefault();
builder.Configuration.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("key", "value"),
});
// Act
var host = builder.Build();
// Assert
Assert.Equal("value", host.Configuration["key"]);
}
[Fact]
public void Build_AllowsConfiguringServices()
{
// Arrange
var builder = WebAssemblyHostBuilder.CreateDefault();
// This test also verifies that we create a scope.
builder.Services.AddScoped<StringBuilder>();
// Act
var host = builder.Build();
// Assert
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
}
[Fact]
public void Build_AddsConfigurationToServices()
{
// Arrange
var builder = WebAssemblyHostBuilder.CreateDefault();
builder.Configuration.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("key", "value"),
});
// Act
var host = builder.Build();
// Assert
var configuration = host.Services.GetRequiredService<IConfiguration>();
Assert.Equal("value", configuration["key"]);
}
private static IReadOnlyList<Type> DefaultServiceTypes
{
get
{
return new Type[]
{
typeof(IJSRuntime),
typeof(NavigationManager),
typeof(INavigationInterception),
typeof(ILoggerFactory),
typeof(HttpClient),
typeof(ILogger<>),
};
}
}
[Fact]
public void Constructor_AddsDefaultServices()
{
// Arrange & Act
var builder = WebAssemblyHostBuilder.CreateDefault();
// Assert
Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count);
foreach (var type in DefaultServiceTypes)
{
Assert.Single(builder.Services, d => d.ServiceType == type);
}
}
}
}

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. -->
<Reference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@ -1,3 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1,86 +0,0 @@
// 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;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class GenerateBlazorBootJson : Task
{
[Required]
public string AssemblyPath { get; set; }
[Required]
public ITaskItem[] References { get; set; }
[Required]
public bool LinkerEnabled { get; set; }
[Required]
public string OutputPath { get; set; }
public override bool Execute()
{
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
using var fileStream = File.Create(OutputPath);
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
return true;
static string GetUriPath(ITaskItem item)
{
var outputPath = item.GetMetadata("RelativeOutputPath");
if (string.IsNullOrEmpty(outputPath))
{
outputPath = Path.GetFileName(item.ItemSpec);
}
return outputPath.Replace('\\', '/');
}
}
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
{
var data = new BootJsonData
{
entryAssembly = entryAssemblyName,
assemblies = assemblies,
linkerEnabled = linkerEnabled,
};
var serializer = new DataContractJsonSerializer(typeof(BootJsonData));
serializer.WriteObject(stream, data);
}
/// <summary>
/// Defines the structure of a Blazor boot JSON file
/// </summary>
#pragma warning disable IDE1006 // Naming Styles
public class BootJsonData
{
/// <summary>
/// Gets the name of the assembly with the application entry point
/// </summary>
public string entryAssembly { get; set; }
/// <summary>
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
/// </summary>
public string[] assemblies { get; set; }
/// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool linkerEnabled { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
}

View File

@ -1,3 +0,0 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
</Project>

View File

@ -1,49 +0,0 @@
<Project>
<!-- Require rebuild if the targets change -->
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<PropertyGroup>
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir>
<_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM>
<_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM>
<BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath>
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- By default, enable debugging for debug builds. -->
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
</PropertyGroup>
<Import Project="Blazor.MonoRuntime.targets" />
<Import Project="Publish.targets" />
<Import Project="StaticWebAssets.targets" />
<Target Name="GenerateBlazorMetadataFile"
BeforeTargets="GetCopyToOutputDirectoryItems">
<PropertyGroup>
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
</PropertyGroup>
<ItemGroup>
<_BlazorConfigContent Include="$(MSBuildProjectFullPath)" />
<_BlazorConfigContent Include="$(TargetPath)" />
<_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" />
</ItemGroup>
<WriteLinesToFile
File="$(BlazorMetadataFilePath)"
Lines="@(_BlazorConfigContent)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,20 +0,0 @@
<Project>
<PropertyGroup>
<BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath>
</PropertyGroup>
<PropertyGroup Label="Blazor build outputs">
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
<BaseBlazorDistPath>dist\</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath>
<BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath>
<BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath>
<BlazorWebRootName>wwwroot\</BlazorWebRootName>
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
</PropertyGroup>
</Project>

View File

@ -1,336 +0,0 @@
<Project>
<PropertyGroup>
<BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild>
</PropertyGroup>
<PropertyGroup>
<!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention -->
<DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''">
<!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds -->
<DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<Target
Name="_BlazorCopyFilesToOutputDirectory"
DependsOnTargets="PrepareBlazorOutputs"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
<!-- Copy the blazor output files -->
<Copy
SourceFiles="@(BlazorOutputWithTargetPath)"
DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
</Copy>
<ItemGroup>
<FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" />
</ItemGroup>
<ItemGroup>
<_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" />
</ItemGroup>
<Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" />
</Target>
<Target
Name="PrepareBlazorOutputs"
DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson">
<ItemGroup>
<MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" />
<BlazorJSFile Include="$(BlazorJSPath)" />
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
<ItemGroup Label="Static content supplied by NuGet packages">
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
<TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
</_BlazorPackageContentOutput>
<BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" />
</ItemGroup>
</Target>
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
<PropertyGroup>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
<_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
<BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
<_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache>
<_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile>
</PropertyGroup>
<ItemGroup>
<_WebAssemblyBCLFolder Include="
$(DotNetWebAssemblyBCLPath);
$(DotNetWebAssemblyBCLFacadesPath);
$(DotNetWebAssemblyFrameworkPath)" />
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
</ItemGroup>
<!--
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
-->
<ItemGroup>
<!-- Assemblies from packages -->
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
<!-- Assemblies from other references -->
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
</ItemGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
<Error
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
<ItemGroup>
<!--
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
-->
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<!--
Linker enabled part of the pipeline:
* If there are no descriptors defined, generate a new linker descriptor.
* Invoke the linker and write linked files to a well-known directory.
* Collect the outputs of the linker.
-->
<Target
Name="_ResolveBlazorOutputsWhenLinked"
Condition="'$(BlazorLinkOnBuild)' == 'true'"
DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication">
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
</Target>
<Target Name="_PrepareBlazorLinkerInputs">
<ItemGroup>
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
<!--
Any assembly from a package reference that starts with System. file name is allowed to be linked.
Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity.
-->
<_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
<Target Name="_GenerateBlazorLinkerDescriptor"
Inputs="@(IntermediateAssembly)"
Outputs="$(GeneratedBlazorLinkerDescriptor)"
Condition="'@(BlazorLinkerDescriptor)' == ''">
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
<BlazorCreateRootDescriptorFile
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
<ItemGroup>
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" />
<Target Name="_GenerateTypeGranularLinkerDescriptor"
Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
Outputs="$(_TypeGranularityLinkerDescriptor)">
<GenerateTypeGranularityLinkingConfig
Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
OutputPath="$(_TypeGranularityLinkerDescriptor)" />
<ItemGroup>
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" />
<FileWrites Include="$(_TypeGranularityLinkerDescriptor)" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_LinkBlazorApplication"
Inputs="$(ProjectAssetsFile);
@(_BlazorManagedRuntimeAssemby);
@(BlazorLinkerDescriptor);
$(MSBuildAllProjects)"
Outputs="$(_BlazorLinkerOutputCache)">
<PropertyGroup>
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
</PropertyGroup>
<ItemGroup>
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
</ItemGroup>
<Delete Files="@(_OldLinkedFile)" />
<!--
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
In this case, explicitly specify the path to the dotnet host.
-->
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
</PropertyGroup>
<BlazorILLink
ILLinkPath="$(MonoLinkerPath)"
AssemblyPaths="@(_BlazorAssemblyToLink)"
RootAssemblyNames="@(_BlazorLinkerRoot)"
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
ToolExe="$(_DotNetHostFileName)"
ToolPath="$(_DotNetHostDirectory)" />
<ItemGroup>
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" />
</ItemGroup>
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
</Target>
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_ResolveBlazorOutputsWhenNotLinked"
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
Condition="'$(BlazorLinkOnBuild)' != 'true'">
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
</Target>
<Target
Name="_ResolveBlazorRuntimeDependencies"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorManagedRuntimeAssemby)"
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
<!--
At this point we have decided not to run the linker and instead to just copy the assemblies
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
-->
<ResolveBlazorRuntimeDependencies
EntryPoint="@(IntermediateAssembly)"
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
</ResolveBlazorRuntimeDependencies>
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
<ItemGroup>
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_GenerateBlazorBootJson"
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
<ItemGroup>
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
</ItemGroup>
<GenerateBlazorBootJson
AssemblyPath="@(IntermediateAssembly)"
References="@(_BlazorRuntimeFile)"
LinkerEnabled="$(BlazorLinkOnBuild)"
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
<ItemGroup>
<BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" />
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,70 +0,0 @@
<Project>
<PropertyGroup>
<BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild>
<BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir>
<!-- Disable unwanted parts of the default publish process -->
<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
<PreserveCompilationContext>false</PreserveCompilationContext>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
<GenerateDependencyFile>false</GenerateDependencyFile>
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
</PropertyGroup>
<Target Name="BlazorGetCopyToPublishDirectoryItems"
BeforeTargets="GetCopyToPublishDirectoryItems"
DependsOnTargets="PrepareBlazorOutputs"
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
<ItemGroup>
<!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. -->
<ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" />
<!-- Move wwwroot files to output root -->
<ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))">
<TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath>
</ContentWithTargetPath>
<!-- Publish all the 'dist' files -->
<_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)">
<TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath>
</_BlazorGCTPDI>
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">
<TargetPath>%(TargetPath)</TargetPath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ContentWithTargetPath>
</ItemGroup>
<!-- Replace the .blazor.config contents with what we need to serve in production -->
<PropertyGroup>
<_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath>
</PropertyGroup>
<ItemGroup>
<_BlazorPublishConfigContent Include="." />
<_BlazorPublishConfigContent Include="$(AssemblyName)/" />
</ItemGroup>
<WriteLinesToFile
File="$(_BlazorConfigPath)"
Lines="@(_BlazorPublishConfigContent)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
</Target>
<!-- The following target runs only for standalone publishing -->
<Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory">
<!-- Add a suitable web.config file if there isn't one already -->
<ItemGroup>
<_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/>
</ItemGroup>
<WriteLinesToFile
Condition="!Exists('$(PublishDir)web.config')"
File="$(PublishDir)web.config"
Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" />
<!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing -->
<Delete Files="$(PublishDir)$(AssemblyName).blazor.config" />
</Target>
</Project>

View File

@ -1,37 +0,0 @@
<Project>
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>
$(ResolveStaticWebAssetsInputsDependsOn);
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
</ResolveStaticWebAssetsInputsDependsOn>
<GetCurrentProjectStaticWebAssetsDependsOn>
$(GetCurrentProjectStaticWebAssetsDependsOn);
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
</GetCurrentProjectStaticWebAssetsDependsOn>
</PropertyGroup>
<Target Name="_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
</ItemGroup>
</Target>
<Target Name="BlazorStaticWebAssetsComputeFilesToPublish"
AfterTargets="_StaticWebAssetsComputeFilesToPublish">
<ItemGroup>
<!-- We need to update the external static web assets to follow the blazor publish output convention that puts them inside $(TargetName)/dist instead of wwwroot -->
<_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''">
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))'))</RelativePath>
</_StandaloneExternalPublishStaticWebAsset>
<!-- Update doesn't work inside targets so we need to remove the items and re-add them. See https://github.com/microsoft/msbuild/issues/2835 for details -->
<ResolvedFileToPublish Remove="@(_StandaloneExternalPublishStaticWebAsset)" />
<ResolvedFileToPublish Include="@(_StandaloneExternalPublishStaticWebAsset)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,534 +0,0 @@
// 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;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class BindRazorIntegrationTest : RazorIntegrationTestBase
{
public BindRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public int Value { get; set; }
[Parameter]
public Action<int> ValueChanged { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public int Value { get; set; }
[Parameter]
public Action<int> OnChanged { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2));
}
[Fact]
public void Render_BindToElement_WritesAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", null, ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
var component = CompileToComponent(@"
<div @bind=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 3, 0),
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
}
[Fact]
public void Render_BindToElementWithSuffix_WritesAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
var component = CompileToComponent(@"
<div @bind-value=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 3, 0),
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
}
[Fact]
public void Render_BindDuplicates_ReportsDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
// Act
var result = CompileToCSharp(@"
<div @bind-value=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Assert
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ9989", diagnostic.Id);
Assert.Equal(
"The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
"Test.BindAttributes" + Environment.NewLine +
"Test.BindAttributes",
diagnostic.GetMessage());
}
[Fact]
public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""@ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", "42", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/>
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/>
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
public string Format { get; set; } = ""MM/dd/yyyy"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", "42", 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""checkbox"" @bind=""@Enabled"" />
@code {
public bool Enabled { get; set; }
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "type", "checkbox", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BindToElementFallback_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", "42", 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BindToElementFallback_WithFormat_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" />
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute()
{
// Arrange
var component = CompileToComponent(@"
<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 5, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "visible", 2),
frame => AssertFrame.Attribute(frame, "value", "42", 3),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
}
[Fact] // See https://github.com/aspnet/Blazor/issues/703
public void Workaround_703()
{
// Arrange
var component = CompileToComponent(@"
<input @bind-value=""@ParentValue"" @bind-value:event=""onchange"" type=""text"" visible />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
//
// The workaround for 703 is that the value attribute MUST be after the type
// attribute.
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 5, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "visible", 2),
frame => AssertFrame.Attribute(frame, "value", "42", 3),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
}
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_BodyContent()
{
// Arrange
var component = CompileToComponent(@"
<div @bind-value=""@ParentValue"" @bind-value:event=""onchange"">
<span>@(42.ToString())</span>
</div>
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 7, 0),
frame => AssertFrame.Attribute(frame, "value", "42", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2),
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Element(frame, "span", 2, 4),
frame => AssertFrame.Text(frame, "42", 5),
frame => AssertFrame.MarkupWhitespace(frame, 6));
}
[Fact]
public void Render_BindFallback_InvalidSyntax_TooManyParts()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" @bind-first-second-third=""Text"" />
@code {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
[Fact]
public void Render_BindFallback_InvalidSyntax_TrailingDash()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" @bind-first-=""Text"" />
@code {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
}
}

View File

@ -1,41 +0,0 @@
// 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.Text.Json;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BootJsonWriterTest
{
[Fact]
public async Task ProducesJsonReferencingAssemblyAndDependencies()
{
// Arrange/Act
var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
using var stream = new MemoryStream();
// Act
GenerateBlazorBootJson.WriteBootJson(
stream,
"MyApp.Entrypoint.dll",
assemblyReferences,
linkerEnabled: true);
// Assert
stream.Position = 0;
using var parsedContent = await JsonDocument.ParseAsync(stream);
var rootElement = parsedContent.RootElement;
Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString());
var assembliesElement = rootElement.GetProperty("assemblies");
Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength());
for (var i = 0; i < assemblyReferences.Length; i++)
{
Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString());
}
Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean());
}
}
}

View File

@ -1,40 +0,0 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIncrementalismTest
{
[Fact]
public async Task Build_WithLinker_IsIncremental()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
// Act
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
// Assert
for (var i = 0; i < 3; i++)
{
result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
for (var j = 0; j < thumbPrint.Count; j++)
{
Assert.Equal(thumbPrint[j], newThumbPrint[j]);
}
}
}
}
}

View File

@ -1,135 +0,0 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIntegrationTest
{
[Fact]
public async Task Build_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
[Fact]
public async Task Build_Hosted_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "netcoreapp3.1";
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
var blazorConfig = Path.Combine(buildOutputDirectory, "standalone.blazor.config");
Assert.FileExists(result, blazorConfig);
var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll"));
Assert.FileContains(result, blazorConfig, path);
Assert.FileDoesNotExist(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
}
[Fact]
public async Task Build_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
[Fact]
public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
}
}

View File

@ -1,224 +0,0 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class PublishIntegrationTest
{
[Fact]
public async Task Publish_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", 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, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify static web assets from referenced projects are copied.
// Uncomment once https://github.com/aspnet/AspNetCore/issues/17426 is resolved.
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<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, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Publish_HostedApp_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "netcoreapp3.1";
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");
var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "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 static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
var blazorConfig = Path.Combine(result.Project.DirectoryPath, publishDirectory, "standalone.blazor.config");
var blazorConfigLines = File.ReadAllLines(blazorConfig);
Assert.Equal(".", blazorConfigLines[0]);
Assert.Equal("standalone/", blazorConfigLines[1]);
}
[Fact]
public async Task Publish_HostedApp_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "netcoreapp3.1";
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, "standalone");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify static web assets from referenced projects are copied.
// Uncomment once https://github.com/aspnet/AspNetCore/issues/17426 is resolved.
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
}
}

View File

@ -1,407 +0,0 @@
// 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 Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class ChildContentRazorIntegrationTest : RazorIntegrationTestBase
{
private readonly CSharpSyntaxTree RenderChildContentComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class RenderChildContent : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, ChildContent);
}
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
");
private readonly CSharpSyntaxTree RenderChildContentStringComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class RenderChildContentString : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, ChildContent, Value);
}
[Parameter]
public RenderFragment<string> ChildContent { get; set; }
[Parameter]
public string Value { get; set; }
}
}
");
private readonly CSharpSyntaxTree RenderMultipleChildContent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class RenderMultipleChildContent : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, Header, Name);
builder.AddContent(1, ChildContent, Value);
builder.AddContent(2, Footer);
}
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment<string> Header { get; set; }
[Parameter]
public RenderFragment<string> ChildContent { get; set; }
[Parameter]
public RenderFragment Footer { get; set; }
[Parameter]
public string Value { get; set; }
}
}
");
public ChildContentRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_BodyChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
<RenderChildContent>
<div></div>
</RenderChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
frame => AssertFrame.Markup(frame, "\n <div></div>\n", 2));
}
[Fact]
public void Render_BodyChildContent_Generic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
var component = CompileToComponent(@"
<RenderChildContentString Value=""HI"">
<div>@context.ToLowerInvariant()</div>
</RenderChildContentString>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", "HI", 1),
frame => AssertFrame.Attribute(frame, "ChildContent", 2),
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "hi", 5),
frame => AssertFrame.MarkupWhitespace(frame, 6));
}
[Fact]
public void Render_ExplicitChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
<RenderChildContent>
<ChildContent>
<div></div>
</ChildContent>
</RenderChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 2));
}
[Fact]
public void Render_BodyChildContent_Recursive()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
<RenderChildContent>
<RenderChildContent>
<div></div>
</RenderChildContent>
</RenderChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
frame => AssertFrame.MarkupWhitespace(frame, 2),
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 3),
frame => AssertFrame.Attribute(frame, "ChildContent", 4),
frame => AssertFrame.MarkupWhitespace(frame, 6),
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 5));
}
[Fact]
public void Render_AttributeChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template(""HI"")"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hi", 1));
}
[Fact]
public void Render_AttributeChildContent_RenderFragmentOfString()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContentString ChildContent=""@template"" Value=""HI"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
frame => AssertFrame.Attribute(frame, "Value", "HI", 4),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hi", 1));
}
[Fact]
public void Render_AttributeChildContent_NoArgTemplate()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
@{ RenderFragment template = @<div>@(""HI"".ToLowerInvariant())</div>; }
<RenderChildContent ChildContent=""@template"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hi", 1));
}
[Fact]
public void Render_AttributeChildContent_IgnoresEmptyBody()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template(""HI"")""></RenderChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hi", 1));
}
[Fact]
public void Render_AttributeChildContent_IgnoresWhitespaceBody()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template(""HI"")"">
</RenderChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hi", 1));
}
[Fact]
public void Render_MultipleChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
var component = CompileToComponent(@"
@{ RenderFragment<string> header = context => @<div>@context.ToLowerInvariant()</div>; }
<RenderMultipleChildContent Name=""billg"" Header=@header Value=""HI"">
<ChildContent>Some @context.ToLowerInvariant() Content</ChildContent>
<Footer>Bye!</Footer>
</RenderMultipleChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 2),
frame => AssertFrame.Attribute(frame, "Name", "billg", 3),
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 4),
frame => AssertFrame.Attribute(frame, "Value", "HI", 5),
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "billg", 1),
frame => AssertFrame.Text(frame, "Some ", 7),
frame => AssertFrame.Text(frame, "hi", 8),
frame => AssertFrame.Text(frame, " Content", 9),
frame => AssertFrame.Text(frame, "Bye!", 11));
}
[Fact]
public void Render_MultipleChildContent_ContextParameterOnComponent()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
var component = CompileToComponent(@"
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
<Header><div>@item.ToLowerInvariant()</div></Header>
<ChildContent Context=""Context"">Some @Context.ToLowerInvariant() Content</ChildContent>
<Footer>Bye!</Footer>
</RenderMultipleChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "billg", 5),
frame => AssertFrame.Text(frame, "Some ", 7),
frame => AssertFrame.Text(frame, "hi", 8),
frame => AssertFrame.Text(frame, " Content", 9),
frame => AssertFrame.Text(frame, "Bye!", 11));
}
// Verifies that our check for reuse of parameter names isn't too aggressive.
[Fact]
public void Render_MultipleChildContent_ContextParameterOnComponent_SetsSameName()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
var component = CompileToComponent(@"
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
<Header><div>@item.ToLowerInvariant()</div></Header>
<ChildContent Context=""item"">Some @item.ToLowerInvariant() Content</ChildContent>
<Footer>Bye!</Footer>
</RenderMultipleChildContent>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "billg", 5),
frame => AssertFrame.Text(frame, "Some ", 7),
frame => AssertFrame.Text(frame, "hi", 8),
frame => AssertFrame.Text(frame, " Content", 9),
frame => AssertFrame.Text(frame, "Bye!", 11));
}
}
}

View File

@ -1,616 +0,0 @@
// 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;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
{
public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_ChildComponent_Simple()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
var component = CompileToComponent(@"
<MyComponent/>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
}
[Fact]
public void Render_ChildComponent_WithParameters()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class SomeType
{
}
public class MyComponent : ComponentBase
{
[Parameter] public int IntProperty { get; set; }
[Parameter] public bool BoolProperty { get; set; }
[Parameter] public string StringProperty { get; set; }
[Parameter] public SomeType ObjectProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent
IntProperty=""123""
BoolProperty=""true""
StringProperty=""My string""
ObjectProperty=""new SomeType()"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
frame =>
{
AssertFrame.Attribute(frame, "ObjectProperty", 4);
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
});
}
[Fact]
public void Render_ChildComponent_TriesToSetNonParamter()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
public int IntProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent IntProperty=""123"" />");
// Act
var ex = Assert.Throws<InvalidOperationException>(() => GetRenderTree(component));
// Assert
Assert.Equal(
"Object of type 'Test.MyComponent' has a property matching the name 'IntProperty', " +
"but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.",
ex.Message);
}
[Fact]
public void Render_ChildComponent_WithExplicitStringParameter()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public string StringProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent StringProperty=""@(42.ToString())"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1));
}
[Fact]
public void Render_ChildComponent_WithNonPropertyAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}
"));
var component = CompileToComponent(@"
<MyComponent some-attribute=""foo"" another-attribute=""@(42.ToString())"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "some-attribute", "foo", 1),
frame => AssertFrame.Attribute(frame, "another-attribute", "42", 2));
}
[Theory]
[InlineData("e => Increment(e)")]
[InlineData("(e) => Increment(e)")]
[InlineData("@(e => Increment(e))")]
[InlineData("@(e => { Increment(e); })")]
[InlineData("Increment")]
[InlineData("@Increment")]
[InlineData("@(Increment)")]
public void Render_ChildComponent_WithEventHandler(string expression)
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public Action<MouseEventArgs> OnClick { get; set; }
}
}
"));
var component = CompileToComponent($@"
@using Microsoft.AspNetCore.Components.Web
<MyComponent OnClick=""{expression}""/>
@code {{
private int counter;
private void Increment(MouseEventArgs e) {{
counter++;
}}
}}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "OnClick", 1);
// The handler will have been assigned to a lambda
var handler = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
});
}
[Fact]
public void Render_ChildComponent_WithExplicitEventHandler()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public Action<EventArgs> OnClick { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent OnClick=""@Increment""/>
@code {
private int counter;
private void Increment(EventArgs e) {
counter++;
}
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "OnClick", 1);
// The handler will have been assigned to a lambda
var handler = Assert.IsType<Action<EventArgs>>(frame.AttributeValue);
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
Assert.Equal("Increment", handler.Method.Name);
});
}
[Fact]
public void Render_ChildComponent_WithMinimizedBoolAttribute()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public bool BoolProperty { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent BoolProperty />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1));
}
[Fact]
public void Render_ChildComponent_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public string MyAttr { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text @(""Hello"")</some-child></MyComponent>");
// Act
var frames = GetRenderTree(component);
// Assert: component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
frame => AssertFrame.Attribute(frame, "ChildContent", 2));
// Assert: Captured ChildContent frames are correct
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
Assert.Collection(
childFrames.AsEnumerable(),
frame => AssertFrame.Text(frame, "Some text", 3),
frame => AssertFrame.Element(frame, "some-child", 4, 4),
frame => AssertFrame.Attribute(frame, "a", "1", 5),
frame => AssertFrame.Text(frame, "Nested text ", 6),
frame => AssertFrame.Text(frame, "Hello", 7));
}
[Fact]
public void Render_ChildComponent_Nested()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent><MyComponent>Some text</MyComponent></MyComponent>");
// Act
var frames = GetRenderTree(component);
// Assert: outer component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "ChildContent", 1));
// Assert: first level of ChildContent is correct
// Note that we don't really need the sequence numbers to continue on from the
// sequence numbers at the parent level. All that really matters is that they are
// correct relative to each other (i.e., incrementing) within the nesting level.
// As an implementation detail, it happens that they do follow on from the parent
// level, but we could change that part of the implementation if we wanted.
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).AsEnumerable().ToArray();
Assert.Collection(
innerFrames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3));
// Assert: second level of ChildContent is correct
Assert.Collection(
GetFrames((RenderFragment)innerFrames[1].AttributeValue).AsEnumerable(),
frame => AssertFrame.Text(frame, "Some text", 4));
}
[Fact] // https://github.com/aspnet/Blazor/issues/773
public void Regression_773()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class SurveyPrompt : ComponentBase
{
[Parameter] public string Title { get; set; }
}
}
"));
var component = CompileToComponent(@"
@page ""/""
<SurveyPrompt Title=""<div>Test!</div>"" />
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.SurveyPrompt", 2, 0),
frame => AssertFrame.Attribute(frame, "Title", "<div>Test!</div>", 1));
}
[Fact]
public void Regression_784()
{
// Arrange
// Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<p @onmouseover=""OnComponentHover"" style=""background: @ParentBgColor;"" />
@code {
public string ParentBgColor { get; set; } = ""#FFFFFF"";
public void OnComponentHover(MouseEventArgs e)
{
}
}
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "p", 3, 0),
frame => AssertFrame.Attribute(frame, "onmouseover", 1),
frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2));
}
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6185")]
public void Render_Component_HtmlEncoded()
{
// Arrange
var component = CompileToComponent(@"&lt;span&gt;Hi&lt;/span&gt;");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Text(frame, "<span>Hi</span>"));
}
[Fact]
public void Render_Component_HtmlBlockEncoded()
{
// Arrange
var component = CompileToComponent(@"<div>&lt;span&gt;Hi&lt/span&gt;</div>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Markup(frame, "<div>&lt;span&gt;Hi&lt/span&gt;</div>"));
}
// Integration test for HTML block rewriting
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6183")]
public void Render_HtmlBlock_Integration()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<html>
<head><meta><meta></head>
<body>
<MyComponent>
<div><span></span><span></span></div>
<div>@(""hi"")</div>
<div><span></span><span></span></div>
<div></div>
<div>@(""hi"")</div>
<div></div>
</MyComponent>
</body>
</html>");
// Act
var frames = GetRenderTree(component);
// Assert: component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "html", 9, 0),
frame => AssertFrame.MarkupWhitespace(frame, 1),
frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2),
frame => AssertFrame.Element(frame, "body", 5, 3),
frame => AssertFrame.MarkupWhitespace(frame, 4),
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5),
frame => AssertFrame.Attribute(frame, "ChildContent", 6),
frame => AssertFrame.MarkupWhitespace(frame, 16),
frame => AssertFrame.MarkupWhitespace(frame, 17));
// Assert: Captured ChildContent frames are correct
var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue);
Assert.Collection(
childFrames.AsEnumerable(),
frame => AssertFrame.MarkupWhitespace(frame, 7),
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8),
frame => AssertFrame.Element(frame, "div", 2, 9),
frame => AssertFrame.Text(frame, "hi", 10),
frame => AssertFrame.MarkupWhitespace(frame, 11),
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12),
frame => AssertFrame.Element(frame, "div", 2, 13),
frame => AssertFrame.Text(frame, "hi", 14),
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15));
}
[Fact]
public void RazorTemplate_CanBeUsedFromComponent()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class Repeater : ComponentBase
{
[Parameter] public int Count { get; set; }
[Parameter] public RenderFragment<string> Template { get; set; }
[Parameter] public string Value { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
for (var i = 0; i < Count; i++)
{
builder.AddContent(i, Template, Value);
}
}
}
}
"));
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
<Repeater Count=3 Value=""Hello, World!"" Template=""template"" />
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.Repeater", 4, 2),
frame => AssertFrame.Attribute(frame, "Count", typeof(int), 3),
frame => AssertFrame.Attribute(frame, "Value", typeof(string), 4),
frame => AssertFrame.Attribute(frame, "Template", typeof(RenderFragment<string>), 5),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1));
}
}
}

View File

@ -1,173 +0,0 @@
// 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;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
// Integration tests for Blazor's directives
public class DirectiveRazorIntegrationTest : RazorIntegrationTestBase
{
public DirectiveRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void ComponentsDoNotHaveLayoutAttributeByDefault()
{
// Arrange/Act
var component = CompileToComponent($"Hello");
// Assert
Assert.Null(component.GetType().GetCustomAttribute<LayoutAttribute>());
}
[Fact]
public void SupportsLayoutDeclarations()
{
// Arrange/Act
var testComponentTypeName = FullTypeName<TestLayout>();
var component = CompileToComponent(
$"@layout {testComponentTypeName}\n" +
$"Hello");
var frames = GetRenderTree(component);
// Assert
var layoutAttribute = component.GetType().GetCustomAttribute<LayoutAttribute>();
Assert.NotNull(layoutAttribute);
Assert.Equal(typeof(TestLayout), layoutAttribute.LayoutType);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello"));
}
[Fact]
public void SupportsImplementsDeclarations()
{
// Arrange/Act
var testInterfaceTypeName = FullTypeName<ITestInterface>();
var component = CompileToComponent(
$"@implements {testInterfaceTypeName}\n" +
$"Hello");
var frames = GetRenderTree(component);
// Assert
Assert.IsAssignableFrom<ITestInterface>(component);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello"));
}
[Fact]
public void SupportsMultipleImplementsDeclarations()
{
// Arrange/Act
var testInterfaceTypeName = FullTypeName<ITestInterface>();
var testInterfaceTypeName2 = FullTypeName<ITestInterface2>();
var component = CompileToComponent(
$"@implements {testInterfaceTypeName}\n" +
$"@implements {testInterfaceTypeName2}\n" +
$"Hello");
var frames = GetRenderTree(component);
// Assert
Assert.IsAssignableFrom<ITestInterface>(component);
Assert.IsAssignableFrom<ITestInterface2>(component);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello"));
}
[Fact]
public void SupportsInheritsDirective()
{
// Arrange/Act
var testBaseClassTypeName = FullTypeName<TestBaseClass>();
var component = CompileToComponent(
$"@inherits {testBaseClassTypeName}" + Environment.NewLine +
$"Hello");
var frames = GetRenderTree(component);
// Assert
Assert.IsAssignableFrom<TestBaseClass>(component);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello"));
}
[Fact]
public void SupportsInjectDirective()
{
// Arrange/Act 1: Compilation
var componentType = CompileToComponent(
$"@inject {FullTypeName<IMyService1>()} MyService1\n" +
$"@inject {FullTypeName<IMyService2>()} MyService2\n" +
$"Hello from @MyService1 and @MyService2").GetType();
// Assert 1: Compiled type has correct properties
var propertyFlags = BindingFlags.Instance | BindingFlags.NonPublic;
var injectableProperties = componentType.GetProperties(propertyFlags)
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null);
Assert.Collection(injectableProperties.OrderBy(p => p.Name),
property =>
{
Assert.Equal("MyService1", property.Name);
Assert.Equal(typeof(IMyService1), property.PropertyType);
Assert.False(property.GetMethod.IsPublic);
Assert.False(property.SetMethod.IsPublic);
},
property =>
{
Assert.Equal("MyService2", property.Name);
Assert.Equal(typeof(IMyService2), property.PropertyType);
Assert.False(property.GetMethod.IsPublic);
Assert.False(property.SetMethod.IsPublic);
});
// Arrange/Act 2: DI-supplied component has correct behavior
var serviceProvider = new TestServiceProvider();
serviceProvider.AddService<IMyService1>(new MyService1Impl());
serviceProvider.AddService<IMyService2>(new MyService2Impl());
var componentFactory = new ComponentFactory();
var component = componentFactory.InstantiateComponent(serviceProvider, componentType);
var frames = GetRenderTree(component);
// Assert 2: Rendered component behaves correctly
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello from "),
frame => AssertFrame.Text(frame, typeof(MyService1Impl).FullName),
frame => AssertFrame.Text(frame, " and "),
frame => AssertFrame.Text(frame, typeof(MyService2Impl).FullName));
}
public class TestLayout : IComponent
{
[Parameter]
public RenderFragment Body { get; set; }
public void Attach(RenderHandle renderHandle)
{
}
public Task SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
public interface ITestInterface { }
public interface ITestInterface2 { }
public class TestBaseClass : ComponentBase { }
public interface IMyService1 { }
public interface IMyService2 { }
public class MyService1Impl : IMyService1 { }
public class MyService2Impl : IMyService2 { }
}
}

View File

@ -1,314 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class GenericComponentRazorIntegrationTest : RazorIntegrationTestBase
{
private readonly CSharpSyntaxTree GenericContextComponent = Parse(@"
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class GenericContext<TItem> : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var items = (IReadOnlyList<TItem>)Items ?? Array.Empty<TItem>();
for (var i = 0; i < items.Count; i++)
{
if (ChildContent == null)
{
builder.AddContent(i, Items[i]);
}
else
{
builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], });
}
}
}
[Parameter]
public List<TItem> Items { get; set; }
[Parameter]
public RenderFragment<Context> ChildContent { get; set; }
public class Context
{
public int Index { get; set; }
public TItem Item { get; set; }
}
}
}
");
private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class MultipleGenericParameter<TItem1, TItem2, TItem3> : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, Item1);
builder.AddContent(1, Item2);
builder.AddContent(2, Item3);
}
[Parameter]
public TItem1 Item1 { get; set; }
[Parameter]
public TItem2 Item2 { get; set; }
[Parameter]
public TItem3 Item3 { get; set; }
}
}
");
public GenericComponentRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_GenericComponent_WithoutChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" />");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_WithRef()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" />
@code {
GenericContext<int> _my;
void Foo() { GC.KeepAlive(_my); }
}");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"">
<div>@(context.Item * context.Index)</div>
</GenericContext>");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Attribute(frame, "ChildContent", 2),
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "0", 5),
frame => AssertFrame.MarkupWhitespace(frame, 6),
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "2", 5),
frame => AssertFrame.MarkupWhitespace(frame, 6));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithRef()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
<GenericContext Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" />
@code {
GenericContext<int> _my;
void Foo() { GC.KeepAlive(_my); }
}");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithRef_Recursive()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var assembly = CompileToAssembly("Test.cshtml", @"
@typeparam TItem
<GenericContext Items=""@MyItems"" @ref=""_my"" />
@code {
[Parameter] public List<TItem> MyItems { get; set; }
GenericContext<TItem> _my;
void Foo() { GC.KeepAlive(_my); }
}");
var componentType = assembly.Assembly.DefinedTypes
.Where(t => t.Name == "Test`1")
.Single()
.MakeGenericType(typeof(int));
var component = (IComponent)Activator.CreateInstance(componentType);
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = assembly.Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithoutChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
<GenericContext Items=""@(new List<int>() { 1, 2, })"" />");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_MultipleParameters_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
var component = CompileToComponent(@"
<MultipleGenericParameter
TItem1=""int""
TItem2=""string""
TItem3=long
Item1=3
Item2=""@(""FOO"")""
Item3=39L/>");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "MultipleGenericParameter`3")
.Single()
.MakeGenericType(typeof(int), typeof(string), typeof(long));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 4, 0),
frame => AssertFrame.Attribute(frame, "Item1", 3, 1),
frame => AssertFrame.Attribute(frame, "Item2", "FOO", 2),
frame => AssertFrame.Attribute(frame, "Item3", 39L, 3),
frame => AssertFrame.Text(frame, "3", 0),
frame => AssertFrame.Text(frame, "FOO", 1),
frame => AssertFrame.Text(frame, "39", 2));
}
}
}

View File

@ -1,54 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<!-- Exclude the TestFiles directory from default wildcards -->
<DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**\*</DefaultItemExcludes>
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
</PropertyGroup>
<ItemGroup>
<!-- Embed test files so they can be referenced in tests -->
<EmbeddedResource Include="TestFiles\**" />
</ItemGroup>
<PropertyGroup Condition="'$(GenerateBaselines)'=='true'">
<DefineConstants>GENERATE_BASELINES;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor.Build" />
<Reference Include="Microsoft.AspNetCore.Blazor.Mono" />
<Reference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" />
<Reference Include="Microsoft.AspNetCore.Razor.Language" />
<Reference Include="Microsoft.CodeAnalysis.Razor" />
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<Reference Include="Microsoft.AspNetCore.Components" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\testassets\StandaloneApp\StandaloneApp.csproj" />
<Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)test\**\*.cs" LinkBase="Helpers" />
</ItemGroup>
<!-- A bit of msbuild magic to support reference resolver tests -->
<Target Name="CreateReferenceHintPathsList" AfterTargets="Build">
<ItemGroup>
<_BclDirectory Include="$(MonoBaseClassLibraryPath)" />
<_BclDirectory Include="$(MonoBaseClassLibraryFacadesPath)" />
<_BclDirectory Include="$(MonoWasmFrameworkPath)" />
</ItemGroup>
<WriteLinesToFile Lines="@(ReferencePath)" File="$(TargetDir)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
<WriteLinesToFile Lines="@(_BclDirectory)" File="$(TargetDir)bclLocations.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
</Target>
</Project>

View File

@ -1,40 +0,0 @@
// 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;
using System.IO;
namespace Microsoft.AspNetCore.Razor.Language
{
/// <summary>
/// A <see cref="RazorProjectItem"/> that does not exist.
/// </summary>
internal class NotFoundProjectItem : RazorProjectItem
{
/// <summary>
/// Initializes a new instance of <see cref="NotFoundProjectItem"/>.
/// </summary>
/// <param name="basePath">The base path.</param>
/// <param name="path">The path.</param>
public NotFoundProjectItem(string basePath, string path)
{
BasePath = basePath;
FilePath = path;
}
/// <inheritdoc />
public override string BasePath { get; }
/// <inheritdoc />
public override string FilePath { get; }
/// <inheritdoc />
public override bool Exists => false;
/// <inheritdoc />
public override string PhysicalPath => throw new NotSupportedException();
/// <inheritdoc />
public override Stream Read() => throw new NotSupportedException();
}
}

View File

@ -1,89 +0,0 @@
// 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;
using System.IO;
using System.Reflection;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class TestFile
{
private TestFile(string resourceName, Assembly assembly)
{
Assembly = assembly;
ResourceName = Assembly.GetName().Name + "." + resourceName.Replace('/', '.').Replace('\\', '.');
}
public Assembly Assembly { get; }
public string ResourceName { get; }
public static TestFile Create(string resourceName, Type type)
{
return new TestFile(resourceName, type.GetTypeInfo().Assembly);
}
public static TestFile Create(string resourceName, Assembly assembly)
{
return new TestFile(resourceName, assembly);
}
public Stream OpenRead()
{
var stream = Assembly.GetManifestResourceStream(ResourceName);
if (stream == null)
{
Assert.True(false, string.Format("Manifest resource: {0} not found", ResourceName));
}
return stream;
}
public bool Exists()
{
var resourceNames = Assembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
{
// Resource names are case-sensitive.
if (string.Equals(ResourceName, resourceName, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
public string ReadAllText()
{
using (var reader = new StreamReader(OpenRead()))
{
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
// since all the unit tests rely on the assumption that the files will have \r\n endings.
return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
}
}
/// <summary>
/// Saves the file to the specified path.
/// </summary>
public void Save(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (var outStream = File.Create(filePath))
{
using (var inStream = OpenRead())
{
inStream.CopyTo(outStream);
}
}
}
}
}

View File

@ -1,48 +0,0 @@
// 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;
using System.IO;
namespace Microsoft.AspNetCore.Razor.Language
{
public static class TestProject
{
public static string GetProjectDirectory(Type type)
{
var solutionDir = GetSolutionRootDirectory("Components");
var assemblyName = type.Assembly.GetName().Name;
var projectDirectory = Path.Combine(solutionDir, "test", assemblyName);
if (!Directory.Exists(projectDirectory))
{
throw new InvalidOperationException(
$@"Could not locate project directory for type {type.FullName}.
Directory probe path: {projectDirectory}.");
}
return projectDirectory;
}
public static string GetSolutionRootDirectory(string solution)
{
var applicationBasePath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln"));
if (projectFileInfo.Exists)
{
return projectFileInfo.DirectoryName;
}
directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null);
throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories.");
}
}
}

View File

@ -1,224 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class VirtualRazorProjectFileSystem : RazorProjectFileSystem
{
private readonly DirectoryNode _root = new DirectoryNode("/");
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
basePath = NormalizeAndEnsureValidPath(basePath);
var directory = _root.GetDirectory(basePath);
return directory?.EnumerateItems() ?? Enumerable.Empty<RazorProjectItem>();
}
[Obsolete("Use GetItem(string path, string fileKind)] instead")]
public override RazorProjectItem GetItem(string path)
{
return GetItem(path, fileKind: null);
}
public override RazorProjectItem GetItem(string path, string fileKind)
{
// We ignore fileKind here because the _root is pre-filled with project items that already have fileKinds defined. This is
// a unique circumstance where the RazorProjectFileSystem is actually pre-filled with all of its project items on construction.
path = NormalizeAndEnsureValidPath(path);
return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path);
}
public void Add(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath);
_root.AddFile(new FileNode(filePath, projectItem));
}
// Internal for testing
[DebuggerDisplay("{Path}")]
internal class DirectoryNode
{
public DirectoryNode(string path)
{
Path = path;
}
public string Path { get; }
public List<DirectoryNode> Directories { get; } = new List<DirectoryNode>();
public List<FileNode> Files { get; } = new List<FileNode>();
public void AddFile(FileNode fileNode)
{
var filePath = fileNode.Path;
if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
{
var message = "Error";
throw new InvalidOperationException(message);
}
// Look for the first / that appears in the path after the current directory path.
var directoryPath = GetDirectoryPath(filePath);
var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true);
Debug.Assert(directory != null);
directory.Files.Add(fileNode);
}
public DirectoryNode GetDirectory(string path)
{
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
{
var message = "Error";
throw new InvalidOperationException(message);
}
return GetOrAddDirectory(this, path);
}
public IEnumerable<RazorProjectItem> EnumerateItems()
{
foreach (var file in Files)
{
yield return file.ProjectItem;
}
foreach (var directory in Directories)
{
foreach (var file in directory.EnumerateItems())
{
yield return file;
}
}
}
public RazorProjectItem GetItem(string path)
{
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Error");
}
var directoryPath = GetDirectoryPath(path);
var directory = GetOrAddDirectory(this, directoryPath);
if (directory == null)
{
return null;
}
foreach (var file in directory.Files)
{
var filePath = file.Path;
var directoryLength = directory.Path.Length;
// path, filePath -> /Views/Home/Index.cshtml
// directory.Path -> /Views/Home/
// We only need to match the file name portion since we've already matched the directory segment.
if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0)
{
return file.ProjectItem;
}
}
return null;
}
private static string GetDirectoryPath(string path)
{
// /dir1/dir2/file.cshtml -> /dir1/dir2/
var fileNameIndex = path.LastIndexOf('/');
if (fileNameIndex == -1)
{
return path;
}
return path.Substring(0, fileNameIndex + 1);
}
private static DirectoryNode GetOrAddDirectory(
DirectoryNode directory,
string path,
bool createIfNotExists = false)
{
Debug.Assert(!string.IsNullOrEmpty(path));
if (path[path.Length - 1] != '/')
{
path += '/';
}
int index;
while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length)
{
var subDirectory = FindSubDirectory(directory, path);
if (subDirectory == null)
{
if (createIfNotExists)
{
var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash
subDirectory = new DirectoryNode(directoryPath);
directory.Directories.Add(subDirectory);
}
else
{
return null;
}
}
directory = subDirectory;
}
return directory;
}
private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path)
{
for (var i = 0; i < parentDirectory.Directories.Count; i++)
{
// ParentDirectory.Path -> /Views/Home/
// CurrentDirectory.Path -> /Views/Home/SubDir/
// Path -> /Views/Home/SubDir/MorePath/File.cshtml
// Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file.
var currentDirectory = parentDirectory.Directories[i];
var directoryPath = currentDirectory.Path;
var startIndex = parentDirectory.Path.Length;
var directoryNameLength = directoryPath.Length - startIndex;
if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0)
{
return currentDirectory;
}
}
return null;
}
}
// Internal for testing
[DebuggerDisplay("{Path}")]
internal struct FileNode
{
public FileNode(string path, RazorProjectItem projectItem)
{
Path = path;
ProjectItem = projectItem;
}
public string Path { get; }
public RazorProjectItem ProjectItem { get; }
}
}
}

View File

@ -1,47 +0,0 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class VirtualProjectItem : RazorProjectItem
{
private readonly byte[] _content;
public VirtualProjectItem(
string basePath,
string filePath,
string physicalPath,
string relativePhysicalPath,
string fileKind,
byte[] content)
{
BasePath = basePath;
FilePath = filePath;
PhysicalPath = physicalPath;
RelativePhysicalPath = relativePhysicalPath;
_content = content;
// Base class will detect based on file-extension.
FileKind = fileKind ?? base.FileKind;
}
public override string BasePath { get; }
public override string RelativePhysicalPath { get; }
public override string FileKind { get; }
public override string FilePath { get; }
public override string PhysicalPath { get; }
public override bool Exists => true;
public override Stream Read()
{
return new MemoryStream(_content);
}
}
}

View File

@ -1,542 +0,0 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class RazorIntegrationTestBase
{
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test";
internal const string ArbitraryMacLinuxPath = "/dir/subdir/Test";
// Creating the initial compilation + reading references is on the order of 250ms without caching
// so making sure it doesn't happen for each test.
private static readonly CSharpCompilation BaseCompilation;
private static CSharpParseOptions CSharpParseOptions { get; }
static RazorIntegrationTestBase()
{
var referenceAssemblyRoots = new[]
{
typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime
typeof(ComponentBase).Assembly,
typeof(RazorIntegrationTestBase).Assembly, // Reference this assembly, so that we can refer to test component types
};
var referenceAssemblies = referenceAssemblyRoots
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() }))
.Distinct()
.Select(Assembly.Load)
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
.ToList();
BaseCompilation = CSharpCompilation.Create(
"TestAssembly",
Array.Empty<SyntaxTree>(),
referenceAssemblies,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.Preview);
}
public RazorIntegrationTestBase(ITestOutputHelper output)
{
_output.Value = output;
AdditionalSyntaxTrees = new List<SyntaxTree>();
AdditionalRazorItems = new List<RazorProjectItem>();
Configuration = RazorConfiguration.Create(RazorLanguageVersion.Latest, "MVC-3.0", Array.Empty<RazorExtension>());
FileKind = FileKinds.Component; // Treat input files as components by default.
FileSystem = new VirtualRazorProjectFileSystem();
PathSeparator = Path.DirectorySeparatorChar.ToString();
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
// Many of the rendering tests include line endings in the output.
LineEnding = "\n";
NormalizeSourceLineEndings = true;
DefaultRootNamespace = "Test"; // Matches the default working directory
DefaultFileName = "TestComponent.cshtml";
}
internal List<RazorProjectItem> AdditionalRazorItems { get; }
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
internal virtual RazorConfiguration Configuration { get; }
internal virtual string DefaultRootNamespace { get; }
internal virtual string DefaultFileName { get; }
internal virtual bool DesignTime { get; }
internal virtual string FileKind { get; }
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
// Used to force a specific style of line-endings for testing. This matters
// for the baseline tests that exercise line mappings. Even though we normalize
// newlines for testing, the difference between platforms affects the data through
// the *count* of characters written.
internal virtual string LineEnding { get; }
internal virtual string PathSeparator { get; }
internal virtual bool NormalizeSourceLineEndings { get; }
internal virtual bool UseTwoPhaseCompilation { get; }
internal virtual string WorkingDirectory { get; }
// Intentionally private, we don't want tests messing with this because it's fragile.
private RazorProjectEngine CreateProjectEngine(MetadataReference[] references)
{
return RazorProjectEngine.Create(Configuration, FileSystem, b =>
{
b.SetRootNamespace(DefaultRootNamespace);
// Turn off checksums, we're testing code generation.
b.Features.Add(new SuppressChecksum());
if (LineEnding != null)
{
b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding));
}
// Including MVC here so that we can find any issues that arise from mixed MVC + Components.
Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions.Register(b);
// Features that use Roslyn are mandatory for components
Microsoft.CodeAnalysis.Razor.CompilerFeatures.Register(b);
b.Features.Add(new CompilationTagHelperFeature());
b.Features.Add(new DefaultMetadataReferenceFeature()
{
References = references,
});
});
}
internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent)
{
var fullPath = WorkingDirectory + PathSeparator + cshtmlRelativePath;
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
var filePath = cshtmlRelativePath.Replace('\\', '/');
if (!filePath.StartsWith('/'))
{
filePath = '/' + filePath;
}
if (NormalizeSourceLineEndings)
{
cshtmlContent = cshtmlContent.Replace("\r", "").Replace("\n", LineEnding);
}
return new VirtualProjectItem(
WorkingDirectory,
filePath,
fullPath,
cshtmlRelativePath,
FileKind,
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
}
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
{
return CompileToCSharp(DefaultFileName, cshtmlContent);
}
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
{
if (UseTwoPhaseCompilation)
{
// The first phase won't include any metadata references for component discovery. This mirrors
// what the build does.
var projectEngine = CreateProjectEngine(Array.Empty<MetadataReference>());
RazorCodeDocument codeDocument;
foreach (var item in AdditionalRazorItems)
{
// Result of generating declarations
codeDocument = projectEngine.ProcessDeclarationOnly(item);
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
AdditionalSyntaxTrees.Add(syntaxTree);
}
// Result of generating declarations
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
codeDocument = projectEngine.ProcessDeclarationOnly(projectItem);
var declaration = new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
CodeDocument = codeDocument,
Code = codeDocument.GetCSharpDocument().GeneratedCode,
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
};
// Result of doing 'temp' compilation
var tempAssembly = CompileToAssembly(declaration);
// Add the 'temp' compilation as a metadata reference
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
projectEngine = CreateProjectEngine(references);
// Now update the any additional files
foreach (var item in AdditionalRazorItems)
{
// Result of generating declarations
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(item) : projectEngine.Process(item);
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
// Replace the 'declaration' syntax tree
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
AdditionalSyntaxTrees.RemoveAll(st => st.FilePath == item.FilePath);
AdditionalSyntaxTrees.Add(syntaxTree);
}
// Result of real code generation for the document under test
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
_output.Value.WriteLine("Use this output when opening an issue");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(projectItem));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
foreach (var item in AdditionalRazorItems)
{
_output.Value.WriteLine($"### Additional source file ({item.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(item));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
}
_output.Value.WriteLine("## Generated C#:");
_output.Value.WriteLine("```C#");
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
_output.Value.WriteLine("```");
return new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
CodeDocument = codeDocument,
Code = codeDocument.GetCSharpDocument().GeneratedCode,
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
};
}
else
{
// For single phase compilation tests just use the base compilation's references.
// This will include the built-in Blazor components.
var projectEngine = CreateProjectEngine(BaseCompilation.References.ToArray());
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
// Log the generated code for test results.
_output.Value.WriteLine("Use this output when opening an issue");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(projectItem));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine("## Generated C#:");
_output.Value.WriteLine("```C#");
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
_output.Value.WriteLine("```");
return new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
CodeDocument = codeDocument,
Code = codeDocument.GetCSharpDocument().GeneratedCode,
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
};
}
}
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
{
var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent);
return CompileToAssembly(cSharpResult);
}
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, bool throwOnFailure = true)
{
if (cSharpResult.Diagnostics.Any())
{
var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray());
throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}");
}
var syntaxTrees = new[]
{
Parse(cSharpResult.Code),
};
var compilation = cSharpResult.BaseCompilation.AddSyntaxTrees(syntaxTrees);
var diagnostics = compilation
.GetDiagnostics()
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
if (diagnostics.Any() && throwOnFailure)
{
throw new CompilationFailedException(compilation);
}
else if (diagnostics.Any())
{
return new CompileToAssemblyResult
{
Compilation = compilation,
Diagnostics = diagnostics,
};
}
using (var peStream = new MemoryStream())
{
compilation.Emit(peStream);
return new CompileToAssemblyResult
{
Compilation = compilation,
Diagnostics = diagnostics,
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
};
}
}
protected IComponent CompileToComponent(string cshtmlSource)
{
var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource);
var componentFullTypeName = $"{DefaultRootNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
return CompileToComponent(assemblyResult, componentFullTypeName);
}
protected IComponent CompileToComponent(CompileToCSharpResult cSharpResult, string fullTypeName)
{
return CompileToComponent(CompileToAssembly(cSharpResult), fullTypeName);
}
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
{
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
if (componentType == null)
{
throw new XunitException(
$"Failed to find component type '{fullTypeName}'. Found types:" + Environment.NewLine +
string.Join(Environment.NewLine, assemblyResult.Assembly.ExportedTypes.Select(t => t.FullName)));
}
return (IComponent)Activator.CreateInstance(componentType);
}
protected static CSharpSyntaxTree Parse(string text, string path = null)
{
return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions, path: path);
}
protected static string FullTypeName<T>() => typeof(T).FullName.Replace('+', '.');
protected RenderTreeFrame[] GetRenderTree(IComponent component)
{
var renderer = new TestRenderer();
return GetRenderTree(renderer, component);
}
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
{
renderer.AttachComponent(component);
var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterView.Empty));
// we will have to change this method if we add a test that does actual async work.
Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted));
if (task.IsFaulted)
{
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
}
return renderer.LatestBatchReferenceFrames;
}
protected ArrayRange<RenderTreeFrame> GetFrames(RenderFragment fragment)
{
var builder = new RenderTreeBuilder();
fragment(builder);
return builder.GetFrames();
}
protected static void AssertSourceEquals(string expected, CompileToCSharpResult generated)
{
// Normalize the paths inside the expected result to match the OS paths
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var windowsPath = Path.Combine(ArbitraryWindowsPath, generated.CodeDocument.Source.RelativePath).Replace('/', '\\');
expected = expected.Replace(windowsPath, generated.CodeDocument.Source.FilePath);
}
expected = expected.Trim();
Assert.Equal(expected, generated.Code.Trim(), ignoreLineEndingDifferences: true);
}
private static string ReadProjectItem(RazorProjectItem item)
{
using (var reader = new StreamReader(item.Read()))
{
return reader.ReadToEnd();
}
}
protected class CompileToCSharpResult
{
// A compilation that can be used *with* this code to compile an assembly
public Compilation BaseCompilation { get; set; }
public RazorCodeDocument CodeDocument { get; set; }
public string Code { get; set; }
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
}
protected class CompileToAssemblyResult
{
public Assembly Assembly { get; set; }
public Compilation Compilation { get; set; }
public string VerboseLog { get; set; }
public IEnumerable<Diagnostic> Diagnostics { get; set; }
}
protected class TestRenderer : Renderer
{
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{
}
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; }
public void AttachComponent(IComponent component)
=> AssignRootComponentId(component);
protected override void HandleException(Exception exception)
{
ExceptionDispatchInfo.Capture(exception).Throw();
}
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
{
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.AsEnumerable().ToArray();
return Task.CompletedTask;
}
}
private class CompilationFailedException : XunitException
{
public CompilationFailedException(Compilation compilation)
{
Compilation = compilation;
}
public Compilation Compilation { get; }
public override string Message
{
get
{
var builder = new StringBuilder();
builder.AppendLine("Compilation failed: ");
var diagnostics = Compilation.GetDiagnostics();
var syntaxTreesWithErrors = new HashSet<SyntaxTree>();
foreach (var diagnostic in diagnostics)
{
builder.AppendLine(diagnostic.ToString());
if (diagnostic.Location.IsInSource)
{
syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree);
}
}
if (syntaxTreesWithErrors.Any())
{
builder.AppendLine();
builder.AppendLine();
foreach (var syntaxTree in syntaxTreesWithErrors)
{
builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:");
builder.AppendLine(syntaxTree.GetText().ToString());
}
}
return builder.ToString();
}
}
}
private class SuppressChecksum : IConfigureRazorCodeGenerationOptionsFeature
{
public int Order => 0;
public RazorEngine Engine { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
options.SuppressChecksum = true;
}
}
private class ForceLineEndingPhase : RazorEnginePhaseBase
{
public ForceLineEndingPhase(string lineEnding)
{
LineEnding = lineEnding;
}
public string LineEnding { get; }
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var field = typeof(CodeRenderingContext).GetField("NewLineString", BindingFlags.Static | BindingFlags.NonPublic);
var key = field.GetValue(null);
codeDocument.Items[key] = LineEnding;
}
}
}
}

View File

@ -1,744 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
// Integration tests for the end-to-end of successful Razor compilation of component definitions
// Includes running the component code to verify the output.
public class RenderingRazorIntegrationTest : RazorIntegrationTestBase
{
public RenderingRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void SupportsPlainText()
{
// Arrange/Act
var component = CompileToComponent("Some plain text");
var frames = GetRenderTree(component);
// Assert
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Some plain text", 0));
}
[Fact]
public void SupportsCSharpExpressions()
{
// Arrange/Act
var component = CompileToComponent(@"
@(""Hello"")
@((object)null)
@(123)
@(new object())
");
// Assert
var frames = GetRenderTree(component);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "Hello", 0),
frame => AssertFrame.MarkupWhitespace(frame, 1),
frame => AssertFrame.TextWhitespace(frame, 2), // @((object)null)
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Text(frame, "123", 4),
frame => AssertFrame.MarkupWhitespace(frame, 5),
frame => AssertFrame.Text(frame, new object().ToString(), 6));
}
[Fact]
public void SupportsCSharpFunctionsBlock()
{
// Arrange/Act
var component = CompileToComponent(@"
@foreach(var item in items) {
@item
}
@code {
string[] items = new[] { ""First"", ""Second"", ""Third"" };
}
");
// Assert
var frames = GetRenderTree(component);
Assert.Collection(frames,
frame => AssertFrame.Text(frame, "First", 0),
frame => AssertFrame.Text(frame, "Second", 0),
frame => AssertFrame.Text(frame, "Third", 0));
}
[Fact]
public void SupportsElementsWithDynamicContent()
{
// Arrange/Act
var component = CompileToComponent("<myelem>Hello @(\"there\")</myelem>");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "myelem", 3, 0),
frame => AssertFrame.Text(frame, "Hello ", 1),
frame => AssertFrame.Text(frame, "there", 2));
}
[Fact]
public void SupportsElementsAsStaticBlock()
{
// Arrange/Act
var component = CompileToComponent("<myelem>Hello</myelem>");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Markup(frame, "<myelem>Hello</myelem>", 0));
}
[Fact]
public void CreatesSeparateMarkupFrameForEachTopLevelStaticElement()
{
// The JavaScript-side rendering code does not rely on this behavior. It supports
// inserting markup frames with arbitrary markup (e.g., multiple top-level elements
// or none). This test exists only as an observation of the current behavior rather
// than a promise that we never want to change it.
// Arrange/Act
var component = CompileToComponent(
"<root>@(\"Hi\") <child1>a</child1> <child2><another>b</another></child2> </root>");
// Assert
var frames = GetRenderTree(component);
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "root", 5, 0),
frame => AssertFrame.Text(frame, "Hi", 1),
frame => AssertFrame.Text(frame, " ", 2),
frame => AssertFrame.Markup(frame, "<child1>a</child1> ", 3),
frame => AssertFrame.Markup(frame, "<child2><another>b</another></child2> ", 4));
}
[Fact]
public void RendersMarkupStringAsMarkupFrame()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var someMarkup = new MarkupString(\"<div>Hello</div>\"); }"
+ "<p>@someMarkup</p>");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "p", 2, 0),
frame => AssertFrame.Markup(frame, "<div>Hello</div>", 1));
}
[Fact]
public void SupportsSelfClosingElementsWithDynamicContent()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <myelem myattr=@(\"val\") />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0),
frame => AssertFrame.Element(frame, "myelem", 2, 1),
frame => AssertFrame.Attribute(frame, "myattr", "val", 2));
}
[Fact]
public void SupportsSelfClosingElementsAsStaticBlock()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <input attr='123' />");
// Assert
Assert.Collection(
GetRenderTree(component),
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <input attr=\"123\">", 0));
}
[Fact]
public void SupportsVoidHtmlElements()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <img>");
// Assert
Assert.Collection(
GetRenderTree(component),
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <img>", 0));
}
[Fact]
public void SupportsComments()
{
// Arrange/Act
var component = CompileToComponent("Start<!-- My comment -->End");
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Markup(frame, "StartEnd", 0));
}
[Fact]
public void SupportsAttributesWithLiteralValues()
{
// Arrange/Act
var component = CompileToComponent("<elem attrib-one=\"Value 1\" a2='v2'>@(\"Hello\")</elem>");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 4, 0),
frame => AssertFrame.Attribute(frame, "attrib-one", "Value 1", 1),
frame => AssertFrame.Attribute(frame, "a2", "v2", 2),
frame => AssertFrame.Text(frame, "Hello", 3));
}
[Fact]
public void SupportsAttributesWithStringExpressionValues()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var myValue = \"My string\"; }"
+ "<elem attr=@myValue />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 2, 0),
frame => AssertFrame.Attribute(frame, "attr", "My string", 1));
}
[Fact]
public void SupportsAttributesWithNonStringExpressionValues()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var myValue = 123; }"
+ "<elem attr=@myValue />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 2, 0),
frame => AssertFrame.Attribute(frame, "attr", "123", 1));
}
[Fact]
public void SupportsAttributesWithInterpolatedStringExpressionValues()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var myValue = \"world\"; var myNum=123; }"
+ "<elem attr=\"Hello, @myValue.ToUpperInvariant() with number @(myNum*2)!\" />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 2, 0),
frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1));
}
[Fact]
public void SupportsAttributesWithInterpolatedTernaryExpressionValues()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var myValue = \"world\"; }"
+ "<elem attr=\"Hello, @(true ? myValue : \"nothing\")!\" />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 2, 0),
frame => AssertFrame.Attribute(frame, "attr", "Hello, world!", 1));
}
[Fact]
public void SupportsHyphenedAttributesWithCSharpExpressionValues()
{
// Arrange/Act
var component = CompileToComponent(
"@{ var myValue = \"My string\"; }"
+ "<elem abc-def=@myValue />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 2, 0),
frame => AssertFrame.Attribute(frame, "abc-def", "My string", 1));
}
[Fact]
public void SupportsDataDashAttributes()
{
// Arrange/Act
var component = CompileToComponent(@"
@{
var myValue = ""Expression value"";
}
<elem data-abc=""Literal value"" data-def=""@myValue"" />");
// Assert
Assert.Collection(
GetRenderTree(component),
frame => AssertFrame.Element(frame, "elem", 3, 0),
frame => AssertFrame.Attribute(frame, "data-abc", "Literal value", 1),
frame => AssertFrame.Attribute(frame, "data-def", "Expression value", 2));
}
[Fact]
public void SupportsUsingStatements()
{
// Arrange/Act
var component = CompileToComponent(
@"@using System.Collections.Generic
@(typeof(List<string>).FullName)");
var frames = GetRenderTree(component);
// Assert
Assert.Collection(frames,
frame => AssertFrame.Text(frame, typeof(List<string>).FullName, 0));
}
[Fact]
public async Task SupportsTwoWayBindingForTextboxes()
{
// Arrange/Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""MyValue"" />
@code {
public string MyValue { get; set; } = ""Initial value"";
}");
var myValueProperty = component.GetType().GetProperty("MyValue");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.Equal("Modified value", myValueProperty.GetValue(component));
}
[Fact]
public async Task SupportsTwoWayBindingForTextareas()
{
// Arrange/Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<textarea @bind=""MyValue"" ></textarea>
@code {
public string MyValue { get; set; } = ""Initial value"";
}");
var myValueProperty = component.GetType().GetProperty("MyValue");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "textarea", 3, 0),
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.Equal("Modified value", myValueProperty.GetValue(component));
}
[Fact]
public async Task SupportsTwoWayBindingForDateValues()
{
// Arrange/Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""MyDate"" />
@code {
public DateTime MyDate { get; set; } = new DateTime(2018, 3, 4, 1, 2, 3);
}");
var myDateProperty = component.GetType().GetProperty("MyDate");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = newDateValue.ToString(), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
}
[Fact]
public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
{
// Arrange/Act
var testDateFormat = "ddd yyyy-MM-dd";
var component = CompileToComponent($@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""@MyDate"" @bind:format=""{testDateFormat}"" />
@code {{
public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4);
}}");
var myDateProperty = component.GetType().GetProperty("MyDate");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component));
}
[Fact] // In this case, onclick is just a normal HTML attribute
public void SupportsEventHandlerWithString()
{
// Arrange
var component = CompileToComponent(@"
<button onclick=""function(){console.log('hello');};"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(frames,
frame => AssertFrame.Markup(frame, "<button onclick=\"function(){console.log('hello');};\"></button>", 0));
}
[Fact]
public void SupportsEventHandlerWithLambda()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<button @onclick=""x => Clicked = true"" />
@code {
public bool Clicked { get; set; }
}");
var clicked = component.GetType().GetProperty("Clicked");
var renderer = new TestRenderer();
// Act
var frames = GetRenderTree(renderer, component);
// Assert
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "button", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "onclick", 1);
var func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
Assert.False((bool)clicked.GetValue(component));
func(new MouseEventArgs());
Assert.True((bool)clicked.GetValue(component));
});
}
[Fact]
public void SupportsEventHandlerWithMethodGroup()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<button @onclick=""OnClick"" />
@code {
public void OnClick(MouseEventArgs e) { Clicked = true; }
public bool Clicked { get; set; }
}");
var clicked = component.GetType().GetProperty("Clicked");
var renderer = new TestRenderer();
// Act
var frames = GetRenderTree(renderer, component);
// Assert
Action<MouseEventArgs> func = default; // Since this is a method group, we don't need to create an EventCallback
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "button", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "onclick", 1);
func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
Assert.False((bool)clicked.GetValue(component));
});
func.Invoke(new MouseEventArgs());
Assert.True((bool)clicked.GetValue(component));
}
[Fact]
public async Task SupportsTwoWayBindingForBoolValues()
{
// Arrange/Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""MyValue"" />
@code {
public bool MyValue { get; set; } = true;
}");
var myValueProperty = component.GetType().GetProperty("MyValue");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", true, 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs() { Value = false, }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.False((bool)myValueProperty.GetValue(component));
}
[Fact]
public async Task SupportsTwoWayBindingForEnumValues()
{
// Arrange/Act
var myEnumType = FullTypeName<MyEnum>();
var component = CompileToComponent($@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""MyValue"" />
@code {{
public {myEnumType} MyValue {{ get; set; }} = {myEnumType}.{nameof(MyEnum.FirstValue)};
}}");
var myValueProperty = component.GetType().GetProperty("MyValue");
var renderer = new TestRenderer();
// Assert
EventCallback setter = default;
var frames = GetRenderTree(renderer, component);
Assert.Collection(frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
frame =>
{
AssertFrame.Attribute(frame, "onchange", 2);
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
});
// Trigger the change event to show it updates the property
//
// This should always complete synchronously.
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = MyEnum.SecondValue.ToString(), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task;
Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component));
}
public enum MyEnum { FirstValue, SecondValue }
[Fact]
public void RazorTemplate_NonGeneric_CanBeUsedFromRazorCode()
{
// Arrange
var component = CompileToComponent(@"
@{ RenderFragment template = @<div>@(""Hello, World!"".ToLower())</div>; }
@for (var i = 0; i < 3; i++)
{
@template;
}
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1));
}
[Fact]
public void RazorTemplate_Generic_CanBeUsedFromRazorCode()
{
// Arrange
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
@for (var i = 0; i < 3; i++)
{
@template(""Hello, World!"");
}
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1));
}
[Fact]
public void RazorTemplate_NonGeneric_CanBeUsedFromMethod()
{
// Arrange
var component = CompileToComponent(@"
@(Repeat(@<div>@(""Hello, World!"".ToLower())</div>, 3))
@code {
RenderFragment Repeat(RenderFragment template, int count)
{
return (b) =>
{
for (var i = 0; i < count; i++)
{
b.AddContent(i, template);
}
};
}
}");
// Act
var frames = GetRenderTree(component);
// Assert
//
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
// from the same logical sequence as the surrounding code.
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2),
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2),
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2));
}
[Fact]
public void RazorTemplate_Generic_CanBeUsedFromMethod()
{
// Arrange
var component = CompileToComponent(@"
@(Repeat((context) => @<div>@context.ToLower()</div>, ""Hello, World!"", 3))
@code {
RenderFragment Repeat<T>(RenderFragment<T> template, T value, int count)
{
return (b) =>
{
for (var i = 0; i < count; i++)
{
b.AddContent(i, template, value);
}
};
}
}");
// Act
var frames = GetRenderTree(component);
// Assert
//
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
// from the same logical sequence as the surrounding code.
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2),
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2),
frame => AssertFrame.Element(frame, "div", 2, 1),
frame => AssertFrame.Text(frame, "hello, world!", 2));
}
}
}

View File

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="$(ReferenceBlazorBuildFromSourceProps)" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<!-- Test Placeholder -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\razorclasslibrary\RazorClassLibrary.csproj" />
</ItemGroup>
</Project>

View File

@ -1,9 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<!-- Override prerelease label and use preview 4, even in the final build -->
<PreReleaseVersionLabel>$(BlazorClientPreReleaseVersionLabel)</PreReleaseVersionLabel>
<DotNetFinalVersionKind></DotNetFinalVersionKind>
</PropertyGroup>
</Project>

View File

@ -1,8 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
<PropertyGroup>
<ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion>
</PropertyGroup>
</Project>

View File

@ -1,121 +0,0 @@
// 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.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Components
{
/// <summary>
/// Extension methods for working with JSON APIs.
/// </summary>
public static class HttpClientJsonExtensions
{
/// <summary>
/// Sends a GET request to the specified URI, and parses the JSON response body
/// to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
{
var stringContent = await httpClient.GetStringAsync(requestUri);
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
}
/// <summary>
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task PostJsonAsync(this HttpClient httpClient, string requestUri, object content)
=> httpClient.SendJsonAsync(HttpMethod.Post, requestUri, content);
/// <summary>
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task<T> PostJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
=> httpClient.SendJsonAsync<T>(HttpMethod.Post, requestUri, content);
/// <summary>
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
public static Task PutJsonAsync(this HttpClient httpClient, string requestUri, object content)
=> httpClient.SendJsonAsync(HttpMethod.Put, requestUri, content);
/// <summary>
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task<T> PutJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
=> httpClient.SendJsonAsync<T>(HttpMethod.Put, requestUri, content);
/// <summary>
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content);
/// <summary>
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
{
var requestJson = JsonSerializer.Serialize(content, JsonSerializerOptionsProvider.Options);
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
// Make sure the call was successful before we
// attempt to process the response content
response.EnsureSuccessStatusCode();
if (typeof(T) == typeof(IgnoreResponse))
{
return default;
}
else
{
var stringContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
}
}
class IgnoreResponse { }
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@ -1,209 +0,0 @@
// 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;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Components.Test
{
public class HttpClientJsonExtensionsTest
{
private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
const string TestUri = "http://example.com/some/uri";
[Fact]
public async Task GetJson_Success()
{
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
{
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
return Task.FromResult(CreateJsonResponse(HttpStatusCode.OK, new Person
{
Name = "Abc",
Age = 123
}));
}));
// Act
var result = await httpClient.GetJsonAsync<Person>(TestUri);
// Assert
Assert.Equal("Abc", result.Name);
Assert.Equal(123, result.Age);
}
[Fact]
public async Task GetJson_Failure()
{
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
{
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
}));
// Act/Assert
var ex = await Assert.ThrowsAsync<HttpRequestException>(
() => httpClient.GetJsonAsync<Person>(TestUri));
Assert.Contains("404 (Not Found)", ex.Message);
}
[Theory]
[InlineData("Put")]
[InlineData("Post")]
[InlineData("Patch")]
[InlineData("Delete")]
[InlineData("MyArtificialMethod")]
public async Task SendJson_Success(string httpMethodString)
{
var httpMethod = new HttpMethod(httpMethodString);
var requestContent = new { MyProp = true, OtherProp = "Hello" };
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
{
Assert.Equal(httpMethod, req.Method);
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
return CreateJsonResponse(HttpStatusCode.OK, new Person
{
Name = "Abc",
Age = 123
});
}));
// Act
var result = await Send(httpClient, httpMethodString, requestContent);
// Assert
Assert.Equal("Abc", result.Name);
Assert.Equal(123, result.Age);
}
[Fact]
public async Task ReadAsJsonAsync_ReadsCamelCasedJson()
{
var input = "{\"name\": \"TestPerson\", \"age\": 23 }";
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(input)
});
}));
// Act
var result = await httpClient.GetJsonAsync<Person>(TestUri);
// Assert
Assert.Equal("TestPerson", result.Name);
Assert.Equal(23, result.Age);
}
[Fact]
public async Task ReadAsJsonAsync_ReadsPascalCasedJson()
{
var input = "{\"Name\": \"TestPerson\", \"Age\": 23 }";
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(input)
});
}));
// Act
var result = await httpClient.GetJsonAsync<Person>(TestUri);
// Assert
Assert.Equal("TestPerson", result.Name);
Assert.Equal(23, result.Age);
}
[Theory]
[InlineData("Put")]
[InlineData("Post")]
[InlineData("Patch")]
[InlineData("Delete")]
[InlineData("MyArtificialMethod")]
public async Task SendJson_Failure(string httpMethodString)
{
var httpMethod = new HttpMethod(httpMethodString);
var requestContent = new { MyProp = true, OtherProp = "Hello" };
// Arrange
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
{
Assert.Equal(httpMethod, req.Method);
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
return new HttpResponseMessage(HttpStatusCode.BadGateway);
}));
// Act/Assert
var ex = await Assert.ThrowsAsync<HttpRequestException>(
() => Send(httpClient, httpMethodString, requestContent));
Assert.Contains("502 (Bad Gateway)", ex.Message);
}
HttpResponseMessage CreateJsonResponse(HttpStatusCode statusCode, object content)
{
return new HttpResponseMessage(statusCode)
{
Content = new StringContent(JsonSerializer.Serialize(content, _jsonSerializerOptions))
};
}
Task<Person> Send(HttpClient httpClient, string httpMethodString, object requestContent)
{
// For methods with convenience overloads, show those overloads work
switch (httpMethodString)
{
case "post":
return httpClient.PostJsonAsync<Person>(TestUri, requestContent);
case "put":
return httpClient.PutJsonAsync<Person>(TestUri, requestContent);
default:
return httpClient.SendJsonAsync<Person>(new HttpMethod(httpMethodString), TestUri, requestContent);
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class TestHttpMessageHandler : HttpMessageHandler
{
private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>> _sendDelegate;
public TestHttpMessageHandler(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendDelegate)
{
_sendDelegate = sendDelegate;
}
protected override void Dispose(bool disposing)
=> base.Dispose(disposing);
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
=> _sendDelegate(request);
}
}
}

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
</ItemGroup>
</Project>

View File

@ -1,184 +0,0 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.Blazor.Server.AutoRebuild;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Builder
{
internal static class AutoRebuildExtensions
{
// Note that we don't need to watch typical static-file extensions (.css, .js, etc.)
// because anything in wwwroot is just served directly from disk on each reload.
// TODO: Make the set of extensions and exclusions configurable in csproj
private static string[] _includedSuffixes = new[] { ".cs", ".cshtml" };
private static string[] _excludedDirectories = new[] { "obj", "bin" };
// To ensure the FileSystemWatchers aren't collected, reference them
// in this static list. They never need to be removed because there's no
// way to remove middleware once it's registered.
private static List<object> _uncollectableWatchers = new List<object>();
public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath)
{
var isFirstFileWrite = true;
WatchFileSystem(config, () =>
{
if (isFirstFileWrite)
{
try
{
// Touch any .cs file to force the host project to rebuild
// (which in turn rebuilds the client, since it's referenced)
var fileToTouch = Directory.EnumerateFiles(
hostAppContentRootPath,
"*.cs",
SearchOption.AllDirectories).FirstOrDefault();
if (!string.IsNullOrEmpty(fileToTouch))
{
File.SetLastWriteTime(fileToTouch, DateTime.Now);
}
}
catch (Exception ex)
{
// If we don't have permission to write these files, autorebuild will not be enabled
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions));
logger?.LogWarning(ex,
"Cannot autorebuild because there was an error when writing to a file in '{0}'.",
hostAppContentRootPath);
}
isFirstFileWrite = false;
}
});
}
public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config)
{
// Currently this only supports VS for Windows. Later on we can add
// an IRebuildService implementation for VS for Mac, etc.
if (!VSForWindowsRebuildService.TryCreate(out var rebuildService))
{
return; // You're not on Windows, or you didn't launch this process from VS
}
// Assume we're up to date when the app starts.
var buildToken = new RebuildToken(new DateTime(1970, 1, 1)) { BuildTask = Task.CompletedTask, };
WatchFileSystem(config, () =>
{
// Don't start the recompilation immediately. We only start it when the next
// HTTP request arrives, because it's annoying if the IDE is constantly rebuilding
// when you're making changes to multiple files and aren't ready to reload
// in the browser yet.
//
// Replacing the token means that new requests that come in will trigger a rebuild,
// and will all 'join' that build until a new file change occurs.
buildToken = new RebuildToken(DateTime.Now);
});
app.Use(async (context, next) =>
{
try
{
var token = buildToken;
if (token.BuildTask == null)
{
// The build is out of date, but a new build is not yet started.
//
// We can count on VS to only allow one build at a time, this is a safe race
// because if we request a second concurrent build, it will 'join' the current one.
var task = rebuildService.PerformRebuildAsync(
config.SourceMSBuildPath,
token.LastChange);
token.BuildTask = task;
}
// In the general case it's safe to await this task, it will be a completed task
// if everything is up to date.
await token.BuildTask;
}
catch (Exception)
{
// If there's no listener on the other end of the pipe, or if anything
// else goes wrong, we just let the incoming request continue.
// There's nowhere useful to log this information so if people report
// problems we'll just have to get a repro and debug it.
// If it was an error on the VS side, it logs to the output window.
}
await next();
});
}
private static void WatchFileSystem(BlazorConfig config, Action onWrite)
{
var clientAppRootDir = Path.GetDirectoryName(config.SourceMSBuildPath);
var excludePathPrefixes = _excludedDirectories.Select(subdir
=> Path.Combine(clientAppRootDir, subdir) + Path.DirectorySeparatorChar);
var fsw = new FileSystemWatcher(clientAppRootDir);
fsw.Created += OnEvent;
fsw.Changed += OnEvent;
fsw.Deleted += OnEvent;
fsw.Renamed += OnEvent;
fsw.IncludeSubdirectories = true;
fsw.EnableRaisingEvents = true;
// Ensure the watcher is not GCed for as long as the app lives
lock (_uncollectableWatchers)
{
_uncollectableWatchers.Add(fsw);
}
void OnEvent(object sender, FileSystemEventArgs eventArgs)
{
if (!File.Exists(eventArgs.FullPath))
{
// It's probably a directory rather than a file
return;
}
if (!_includedSuffixes.Any(ext => eventArgs.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
{
// Not a candidate file type
return;
}
if (excludePathPrefixes.Any(prefix => eventArgs.FullPath.StartsWith(prefix, StringComparison.Ordinal)))
{
// In an excluded subdirectory
return;
}
onWrite();
}
}
// Represents a three-state value for the state of the build
//
// BuildTask == null means the build is out of date, but no build has started
// BuildTask.IsCompleted == false means the build has been started, but has not completed
// BuildTask.IsCompleted == true means the build has completed
private class RebuildToken
{
public RebuildToken(DateTime lastChange)
{
LastChange = lastChange;
}
public DateTime LastChange { get; }
public Task BuildTask;
}
}
}

View File

@ -1,17 +0,0 @@
// 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;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
{
/// <summary>
/// Represents a mechanism for rebuilding a .NET project. For example, it
/// could be a way of signalling to a VS process to perform a build.
/// </summary>
internal interface IRebuildService
{
Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince);
}
}

View File

@ -1,51 +0,0 @@
// 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;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
{
internal static class ProcessUtils
{
// Based on https://stackoverflow.com/a/3346055
public static Process GetParent(Process process)
{
var result = new ProcessBasicInformation();
var handle = process.Handle;
var status = NtQueryInformationProcess(handle, 0, ref result, Marshal.SizeOf(result), out var returnLength);
if (status != 0)
{
throw new Win32Exception(status);
}
try
{
var parentProcessId = result.InheritedFromUniqueProcessId.ToInt32();
return parentProcessId > 0 ? Process.GetProcessById(parentProcessId) : null;
}
catch (ArgumentException)
{
return null; // Process not found
}
}
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength);
[StructLayout(LayoutKind.Sequential)]
struct ProcessBasicInformation
{
// These members must match PROCESS_BASIC_INFORMATION
public IntPtr Reserved1;
public IntPtr PebBaseAddress;
public IntPtr Reserved2_0;
public IntPtr Reserved2_1;
public IntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
}
}
}

View File

@ -1,40 +0,0 @@
// 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;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
{
internal static class StreamProtocolExtensions
{
public static async Task WriteStringAsync(this Stream stream, string str)
{
var utf8Bytes = Encoding.UTF8.GetBytes(str);
await stream.WriteAsync(BitConverter.GetBytes(utf8Bytes.Length), 0, 4);
await stream.WriteAsync(utf8Bytes, 0, utf8Bytes.Length);
}
public static async Task WriteDateTimeAsync(this Stream stream, DateTime value)
{
var ticksBytes = BitConverter.GetBytes(value.Ticks);
await stream.WriteAsync(ticksBytes, 0, 8);
}
public static async Task<bool> ReadBoolAsync(this Stream stream)
{
var responseBuf = new byte[1];
await stream.ReadAsync(responseBuf, 0, 1);
return responseBuf[0] == 1;
}
public static async Task<int> ReadIntAsync(this Stream stream)
{
var responseBuf = new byte[4];
await stream.ReadAsync(responseBuf, 0, 4);
return BitConverter.ToInt32(responseBuf, 0);
}
}
}

View File

@ -1,106 +0,0 @@
// 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;
using System.Diagnostics;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
{
/// <summary>
/// Finds the VS process that launched this app process (if any), and uses
/// named pipes to communicate with its AutoRebuild listener (if any).
/// </summary>
internal class VSForWindowsRebuildService : IRebuildService
{
private const int _connectionTimeoutMilliseconds = 3000;
private readonly Process _vsProcess;
public static bool TryCreate(out VSForWindowsRebuildService result)
{
var vsProcess = FindAncestorVSProcess();
if (vsProcess != null)
{
result = new VSForWindowsRebuildService(vsProcess);
return true;
}
else
{
result = null;
return false;
}
}
public async Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince)
{
var pipeName = $"BlazorAutoRebuild\\{_vsProcess.Id}";
using (var pipeClient = new NamedPipeClientStream(pipeName))
{
await pipeClient.ConnectAsync(_connectionTimeoutMilliseconds);
// Protocol:
// 1. Receive protocol version number from the VS listener
// If we're incompatible with it, send back special string "abort" and end
// 2. Send the project path to the VS listener
// 3. Send the 'if not rebuilt since' timestamp to the VS listener
// 4. Wait for it to send back a bool representing the result
// Keep in sync with AutoRebuildService.cs in the BlazorExtension project
// In the future we may extend this to getting back build error details
var remoteProtocolVersion = await pipeClient.ReadIntAsync();
if (remoteProtocolVersion == 1)
{
await pipeClient.WriteStringAsync(projectFullPath);
await pipeClient.WriteDateTimeAsync(ifNotBuiltSince);
return await pipeClient.ReadBoolAsync();
}
else
{
await pipeClient.WriteStringAsync("abort");
return false;
}
}
}
private VSForWindowsRebuildService(Process vsProcess)
{
_vsProcess = vsProcess ?? throw new ArgumentNullException(nameof(vsProcess));
}
private static Process FindAncestorVSProcess()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return null;
}
var candidateProcess = Process.GetCurrentProcess();
try
{
while (candidateProcess != null && !candidateProcess.HasExited)
{
// It's unlikely that anyone's going to have a non-VS process in the process
// hierarchy called 'devenv', but if that turns out to be a scenario, we could
// (for example) write the VS PID to the obj directory during build, and then
// only consider processes with that ID. We still want to be sure there really
// is such a process in our ancestor chain, otherwise if you did "dotnet run"
// in a command prompt, we'd be confused and think it was launched from VS.
if (candidateProcess.ProcessName.Equals("devenv", StringComparison.OrdinalIgnoreCase))
{
return candidateProcess;
}
candidateProcess = ProcessUtils.GetParent(candidateProcess);
}
}
catch (Exception)
{
// There's probably some permissions issue that prevents us from seeing
// further up the ancestor list, so we have to stop looking here.
}
return null;
}
}
}

View File

@ -1,78 +0,0 @@
// 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;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Blazor.Server
{
internal class BlazorConfig
{
public string SourceMSBuildPath { get; }
public string SourceOutputAssemblyPath { get; }
public string WebRootPath { get; }
public string DistPath
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
public bool EnableAutoRebuilding { get; }
public bool EnableDebugging { get; }
public static BlazorConfig Read(string assemblyPath)
=> new BlazorConfig(assemblyPath);
private BlazorConfig(string assemblyPath)
{
// TODO: Instead of assuming the lines are in a specific order, either JSON-encode
// the whole thing, or at least give the lines key prefixes (e.g., "reload:<someuri>")
// so we're not dependent on order and all lines being present.
var configFilePath = Path.ChangeExtension(assemblyPath, ".blazor.config");
var configLines = File.ReadLines(configFilePath).ToList();
SourceMSBuildPath = configLines[0];
if (SourceMSBuildPath == ".")
{
SourceMSBuildPath = assemblyPath;
}
var sourceMsBuildDir = Path.GetDirectoryName(SourceMSBuildPath);
SourceOutputAssemblyPath = Path.Combine(sourceMsBuildDir, configLines[1]);
var webRootPath = Path.Combine(sourceMsBuildDir, "wwwroot");
if (Directory.Exists(webRootPath))
{
WebRootPath = webRootPath;
}
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
}
public string FindIndexHtmlFile()
{
// Before publishing, the client project may have a wwwroot directory.
// If so, and if it contains index.html, use that.
if (!string.IsNullOrEmpty(WebRootPath))
{
var wwwrootIndexHtmlPath = Path.Combine(WebRootPath, "index.html");
if (File.Exists(wwwrootIndexHtmlPath))
{
return wwwrootIndexHtmlPath;
}
}
// After publishing, the client project won't have a wwwroot directory.
// The contents from that dir will have been copied to "dist" during publish.
// So if "dist/index.html" now exists, use that.
var distIndexHtmlPath = Path.Combine(DistPath, "index.html");
if (File.Exists(distIndexHtmlPath))
{
return distIndexHtmlPath;
}
// Since there's no index.html, we'll use the default DefaultPageStaticFileOptions,
// hence we'll look for index.html in the host server app's wwwroot.
return null;
}
}
}

View File

@ -1,98 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Net.Mime;
using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
/// </summary>
public static class BlazorHostingApplicationBuilderExtensions
{
/// <summary>
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
/// specified by <typeparamref name="TClientApp"/>.
/// </summary>
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
UseClientSideBlazorFiles(app, typeof(TClientApp).Assembly.Location);
return app;
}
/// <summary>
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
/// specified by <paramref name="clientAssemblyFilePath"/>.
/// </summary>
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseClientSideBlazorFiles(this IApplicationBuilder app, string clientAssemblyFilePath)
{
if (clientAssemblyFilePath == null)
{
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
}
var fileProviders = new List<IFileProvider>();
// TODO: Make the .blazor.config file contents sane
// Currently the items in it are bizarre and don't relate to their purpose,
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
var config = BlazorConfig.Read(clientAssemblyFilePath);
// First, match the request against files in the client app dist directory
fileProviders.Add(new PhysicalFileProvider(config.DistPath));
// * Before publishing, we serve the wwwroot files directly from source
// (and don't require them to be copied into dist).
// In this case, WebRootPath will be nonempty if that directory exists.
// * After publishing, the wwwroot files are already copied to 'dist' and
// will be served by the above middleware, so we do nothing here.
// In this case, WebRootPath will be empty (the publish process sets this).
if (!string.IsNullOrEmpty(config.WebRootPath))
{
fileProviders.Add(new PhysicalFileProvider(config.WebRootPath));
}
// We can't modify an IFileContentTypeProvider, so we have to decorate.
var contentTypeProvider = new FileExtensionContentTypeProvider();
AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet);
if (config.EnableDebugging)
{
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
}
var options = new StaticFileOptions()
{
ContentTypeProvider = contentTypeProvider,
FileProvider = new CompositeFileProvider(fileProviders),
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
};
app.UseStaticFiles(options);
return app;
static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
{
if (!provider.Mappings.ContainsKey(name))
{
provider.Mappings.Add(name, mimeType);
}
}
}
}
}

View File

@ -1,193 +0,0 @@
// 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;
using System.IO;
using Microsoft.AspNetCore.Blazor.Server;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
/// </summary>
public static class BlazorHostingEndpointRouteBuilderExtensions
{
/// <summary>
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
/// Blazor application specified by <typeparamref name="TClientApp"/>.
/// </summary>
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="filePath">
/// The relative path to the entry point of the client-side application. The path is relative to the
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
/// </param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// <para>
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
/// </para>
/// </remarks>
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string filePath)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
}
/// <summary>
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
/// <param name="filePath">
/// The relative path to the entry point of the client-side application. The path is relative to the
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
/// </param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// <para>
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
/// </para>
/// </remarks>
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string filePath)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (clientAssemblyFilePath == null)
{
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
}
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
return MapFallbackToClientSideBlazor(endpoints, clientAssemblyFilePath, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
}
/// <summary>
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
/// Blazor application specified by <typeparamref name="TClientApp"/>.
/// </summary>
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="pattern">The route pattern to match.</param>
/// <param name="filePath">
/// The relative path to the entry point of the client-side application. The path is relative to the
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
/// </param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// <para>
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
/// </para>
/// </remarks>
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string pattern, string filePath)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (pattern == null)
{
throw new ArgumentNullException(nameof(pattern));
}
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, pattern, filePath);
}
/// <summary>
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
/// </summary>
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="pattern">The route pattern to match.</param>
/// <param name="filePath">
/// The relative path to the entry point of the client-side application. The path is relative to the
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
/// </param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// <para>
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
/// </para>
/// </remarks>
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string pattern, string filePath)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (clientAssemblyFilePath == null)
{
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
}
if (pattern == null)
{
throw new ArgumentNullException(nameof(pattern));
}
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
var config = BlazorConfig.Read(clientAssemblyFilePath);
// We want to serve "index.html" from whichever directory contains it in this priority order:
// 1. Client app "dist" directory
// 2. Client app "wwwroot" directory
// 3. Server app "wwwroot" directory
var directory = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>().WebRootPath;
var indexHtml = config.FindIndexHtmlFile();
if (indexHtml != null)
{
directory = Path.GetDirectoryName(indexHtml);
}
var options = new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(directory),
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
};
return endpoints.MapFallbackToFile(pattern, filePath, options);
}
}
}

View File

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Description>Runtime server features for ASP.NET Core Blazor applications.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
<!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App -->
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json" />
<!-- Used by ws-proxy sources only. Remove this once we're able to consume ws-proxy as a NuGet package. -->
<Reference Include="Mono.Cecil" />
</ItemGroup>
</Project>

View File

@ -1,317 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using WsProxy;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Provides infrastructure for debugging Blazor applications.
/// </summary>
public static class BlazorMonoDebugProxyAppBuilderExtensions
{
private static JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
IgnoreNullValues = true
};
private static string DefaultDebuggerHost = "http://localhost:9222";
/// <summary>
/// Adds middleware for needed for debugging Blazor applications
/// inside Chromium dev tools.
/// </summary>
public static void UseBlazorDebugging(this IApplicationBuilder app)
{
app.UseWebSockets();
app.UseVisualStudioDebuggerConnectionRequestHandlers();
app.Use((context, next) =>
{
var requestPath = context.Request.Path;
if (!requestPath.StartsWithSegments("/_framework/debug"))
{
return next();
}
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
{
return DebugWebSocketProxyRequest(context);
}
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
{
return DebugHome(context);
}
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Task.CompletedTask;
});
}
private static string GetDebuggerHost()
{
var envVar = Environment.GetEnvironmentVariable("ASPNETCORE_WEBASSEMBLYDEBUGHOST");
if (string.IsNullOrEmpty(envVar))
{
return DefaultDebuggerHost;
}
else
{
return envVar;
}
}
private static int GetDebuggerPort()
{
var host = GetDebuggerHost();
return new Uri(host).Port;
}
private static void UseVisualStudioDebuggerConnectionRequestHandlers(this IApplicationBuilder app)
{
// Unfortunately VS doesn't send any deliberately distinguishing information so we know it's
// not a regular browser or API client. The closest we can do is look for the *absence* of a
// User-Agent header. In the future, we should try to get VS to send a special header to indicate
// this is a debugger metadata request.
app.Use(async (context, next) =>
{
var request = context.Request;
var requestPath = request.Path;
if (requestPath.StartsWithSegments("/json")
&& !request.Headers.ContainsKey("User-Agent"))
{
if (requestPath.Equals("/json", StringComparison.OrdinalIgnoreCase) || requestPath.Equals("/json/list", StringComparison.OrdinalIgnoreCase))
{
var availableTabs = await GetOpenedBrowserTabs();
// Filter the list to only include tabs displaying the requested app,
// but only during the "choose application to debug" phase. We can't apply
// the same filter during the "connecting" phase (/json/list), nor do we need to.
if (requestPath.Equals("/json"))
{
availableTabs = availableTabs.Where(tab => tab.Url.StartsWith($"{request.Scheme}://{request.Host}{request.PathBase}/"));
}
var proxiedTabInfos = availableTabs.Select(tab =>
{
var underlyingV8Endpoint = tab.WebSocketDebuggerUrl;
var proxiedV8Endpoint = $"ws://{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
return new
{
description = "",
devtoolsFrontendUrl = "",
id = tab.Id,
title = tab.Title,
type = tab.Type,
url = tab.Url,
webSocketDebuggerUrl = proxiedV8Endpoint
};
});
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(proxiedTabInfos));
}
else if (requestPath.Equals("/json/version", StringComparison.OrdinalIgnoreCase))
{
var browserVersionJson = await GetBrowserVersionInfoAsync();
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(browserVersionJson);
}
}
else
{
await next();
}
});
}
private static async Task DebugWebSocketProxyRequest(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return;
}
var browserUri = new Uri(context.Request.Query["browser"]);
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
await new MonoProxy().Run(browserUri, ideSocket);
}
private static async Task DebugHome(HttpContext context)
{
context.Response.ContentType = "text/html";
var request = context.Request;
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
var targetTabUrl = request.Query["url"];
if (string.IsNullOrEmpty(targetTabUrl))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("No value specified for 'url'");
return;
}
// TODO: Allow overriding port (but not hostname, as we're connecting to the
// local browser, not to the webserver serving the app)
var debuggerHost = GetDebuggerHost();
var debuggerTabsListUrl = $"{debuggerHost}/json";
IEnumerable<BrowserTab> availableTabs;
try
{
availableTabs = await GetOpenedBrowserTabs();
}
catch (Exception ex)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
Ensure your browser is running with debugging enabled.
</p>
<h2>Resolution</h2>
<p>
<h4>If you are using Google Chrome for your development, follow these instructions:</h4>
{GetLaunchChromeInstructions(appRootUrl)}
</p>
<p>
<h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4>
{GetLaunchEdgeInstructions(appRootUrl)}
</p>
<strong>This should launch a new browser window with debugging enabled..</p>
<h2>Underlying exception:</h2>
<pre>{ex}</pre>
");
return;
}
var matchingTabs = availableTabs
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
.ToList();
if (matchingTabs.Count == 0)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
The response from <code>{debuggerTabsListUrl}</code> does not include
any entry for <code>{targetTabUrl}</code>.
</p>");
return;
}
else if (matchingTabs.Count > 1)
{
// TODO: Automatically disambiguate by adding a GUID to the page title
// when you press the debugger hotkey, include it in the querystring passed
// here, then remove it once the debugger connects.
await context.Response.WriteAsync($@"
<h1>Multiple matching tabs are open</h1>
<p>
There is more than one browser tab at <code>{targetTabUrl}</code>.
Close the ones you do not wish to debug, then refresh this page.
</p>");
return;
}
// Now we know uniquely which tab to debug, construct the URL to the debug
// page and redirect there
var tabToDebug = matchingTabs.Single();
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
var wsParamName = request.IsHttps ? "wss" : "ws";
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{wsParamName}={proxyEndpoint}";
context.Response.Redirect(devToolsUrlWithProxy);
}
private static string GetLaunchChromeInstructions(string appRootUrl)
{
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug");
var debuggerPort = GetDebuggerPort();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>Execute the following:</p>
<p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static string GetLaunchEdgeInstructions(string appRootUrl)
{
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
var debugggerPort = GetDebuggerPort();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>msedge --remote-debugging-port={debugggerPort} --user-data-dir=""{profilePath}"" --no-first-run {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debugggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static async Task<string> GetBrowserVersionInfoAsync()
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var debuggerHost = GetDebuggerHost();
return await httpClient.GetStringAsync($"{debuggerHost}/json/version");
}
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs()
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var debuggerHost = GetDebuggerHost();
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
return JsonSerializer.Deserialize<BrowserTab[]>(jsonResponse, JsonOptions);
}
class BrowserTab
{
public string Id { get; set; }
public string Type { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string DevtoolsFrontendUrl { get; set; }
public string WebSocketDebuggerUrl { get; set; }
}
}
}

View File

@ -1,651 +0,0 @@
using System;
using System.IO;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using Mono.Cecil.Pdb;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
namespace WsProxy {
internal class BreakPointRequest {
public string Assembly { get; private set; }
public string File { get; private set; }
public int Line { get; private set; }
public int Column { get; private set; }
public override string ToString () {
return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
}
public static BreakPointRequest Parse (JObject args, DebugStore store)
{
if (args == null)
return null;
var url = args? ["url"]?.Value<string> ();
if (url == null) {
var urlRegex = args?["urlRegex"].Value<string>();
var sourceFile = store.GetFileByUrlRegex (urlRegex);
url = sourceFile?.DotNetUrl;
}
if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) {
var sourceFile = store.GetFileByUrl (url);
url = sourceFile?.DotNetUrl;
}
if (url == null)
return null;
var parts = ParseDocumentUrl (url);
if (parts.Assembly == null)
return null;
var line = args? ["lineNumber"]?.Value<int> ();
var column = args? ["columnNumber"]?.Value<int> ();
if (line == null || column == null)
return null;
return new BreakPointRequest () {
Assembly = parts.Assembly,
File = parts.DocumentPath,
Line = line.Value,
Column = column.Value
};
}
static (string Assembly, string DocumentPath) ParseDocumentUrl (string url)
{
if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") {
return (
docUri.Host,
docUri.PathAndQuery.Substring (1)
);
} else {
return (null, null);
}
}
}
internal class VarInfo {
public VarInfo (VariableDebugInformation v)
{
this.Name = v.Name;
this.Index = v.Index;
}
public VarInfo (ParameterDefinition p)
{
this.Name = p.Name;
this.Index = (p.Index + 1) * -1;
}
public string Name { get; private set; }
public int Index { get; private set; }
public override string ToString ()
{
return $"(var-info [{Index}] '{Name}')";
}
}
internal class CliLocation {
private MethodInfo method;
private int offset;
public CliLocation (MethodInfo method, int offset)
{
this.method = method;
this.offset = offset;
}
public MethodInfo Method { get => method; }
public int Offset { get => offset; }
}
internal class SourceLocation {
SourceId id;
int line;
int column;
CliLocation cliLoc;
public SourceLocation (SourceId id, int line, int column)
{
this.id = id;
this.line = line;
this.column = column;
}
public SourceLocation (MethodInfo mi, SequencePoint sp)
{
this.id = mi.SourceId;
this.line = sp.StartLine - 1;
this.column = sp.StartColumn - 1;
this.cliLoc = new CliLocation (mi, sp.Offset);
}
public SourceId Id { get => id; }
public int Line { get => line; }
public int Column { get => column; }
public CliLocation CliLocation => this.cliLoc;
public override string ToString ()
{
return $"{id}:{Line}:{Column}";
}
public static SourceLocation Parse (JObject obj)
{
if (obj == null)
return null;
var id = SourceId.TryParse (obj ["scriptId"]?.Value<string> ());
var line = obj ["lineNumber"]?.Value<int> ();
var column = obj ["columnNumber"]?.Value<int> ();
if (id == null || line == null || column == null)
return null;
return new SourceLocation (id, line.Value, column.Value);
}
internal JObject ToJObject ()
{
return JObject.FromObject (new {
scriptId = id.ToString (),
lineNumber = line,
columnNumber = column
});
}
}
internal class SourceId {
readonly int assembly, document;
public int Assembly => assembly;
public int Document => document;
internal SourceId (int assembly, int document)
{
this.assembly = assembly;
this.document = document;
}
public SourceId (string id)
{
id = id.Substring ("dotnet://".Length);
var sp = id.Split ('_');
this.assembly = int.Parse (sp [0]);
this.document = int.Parse (sp [1]);
}
public static SourceId TryParse (string id)
{
if (!id.StartsWith ("dotnet://", StringComparison.InvariantCulture))
return null;
return new SourceId (id);
}
public override string ToString ()
{
return $"dotnet://{assembly}_{document}";
}
public override bool Equals (object obj)
{
if (obj == null)
return false;
SourceId that = obj as SourceId;
return that.assembly == this.assembly && that.document == this.document;
}
public override int GetHashCode ()
{
return this.assembly.GetHashCode () ^ this.document.GetHashCode ();
}
public static bool operator == (SourceId a, SourceId b)
{
if ((object)a == null)
return (object)b == null;
return a.Equals (b);
}
public static bool operator != (SourceId a, SourceId b)
{
return !a.Equals (b);
}
}
internal class MethodInfo {
AssemblyInfo assembly;
internal MethodDefinition methodDef;
SourceFile source;
public SourceId SourceId => source.SourceId;
public string Name => methodDef.Name;
public SourceLocation StartLocation { get; private set; }
public SourceLocation EndLocation { get; private set; }
public AssemblyInfo Assembly => assembly;
public int Token => (int)methodDef.MetadataToken.RID;
public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
{
this.assembly = assembly;
this.methodDef = methodDef;
this.source = source;
var sps = methodDef.DebugInformation.SequencePoints;
if (sps != null && sps.Count > 0) {
StartLocation = new SourceLocation (this, sps [0]);
EndLocation = new SourceLocation (this, sps [sps.Count - 1]);
}
}
public SourceLocation GetLocationByIl (int pos)
{
SequencePoint prev = null;
foreach (var sp in methodDef.DebugInformation.SequencePoints) {
if (sp.Offset > pos)
break;
prev = sp;
}
if (prev != null)
return new SourceLocation (this, prev);
return null;
}
public VarInfo [] GetLiveVarsAt (int offset)
{
var res = new List<VarInfo> ();
res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p)));
res.AddRange (methodDef.DebugInformation.GetScopes ()
.Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
.SelectMany (s => s.Variables)
.Where (v => !v.IsDebuggerHidden)
.Select (v => new VarInfo (v)));
return res.ToArray ();
}
}
internal class AssemblyInfo {
static int next_id;
ModuleDefinition image;
readonly int id;
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> ();
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
readonly List<SourceFile> sources = new List<SourceFile>();
public AssemblyInfo (byte[] assembly, byte[] pdb)
{
lock (typeof (AssemblyInfo)) {
this.id = ++next_id;
}
try {
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
if (pdb != null) {
rp.ReadSymbols = true;
rp.SymbolReaderProvider = new PortablePdbReaderProvider ();
rp.SymbolStream = new MemoryStream (pdb);
}
rp.ReadingMode = ReadingMode.Immediate;
rp.InMemory = true;
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
} catch (BadImageFormatException ex) {
Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}");
}
if (this.image == null) {
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
if (pdb != null) {
rp.ReadSymbols = true;
rp.SymbolReaderProvider = new NativePdbReaderProvider ();
rp.SymbolStream = new MemoryStream (pdb);
}
rp.ReadingMode = ReadingMode.Immediate;
rp.InMemory = true;
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
}
Populate ();
}
public AssemblyInfo ()
{
}
void Populate ()
{
ProcessSourceLink();
var d2s = new Dictionary<Document, SourceFile> ();
Func<Document, SourceFile> get_src = (doc) => {
if (doc == null)
return null;
if (d2s.ContainsKey (doc))
return d2s [doc];
var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url));
sources.Add (src);
d2s [doc] = src;
return src;
};
foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) {
Document first_doc = null;
foreach (var sp in m.DebugInformation.SequencePoints) {
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) {
first_doc = sp.Document;
}
// else if (first_doc != sp.Document) {
// //FIXME this is needed for (c)ctors in corlib
// throw new Exception ($"Cant handle multi-doc methods in {m}");
//}
}
if (first_doc == null) {
// all generated files
first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document;
}
if (first_doc != null) {
var src = get_src (first_doc);
var mi = new MethodInfo (this, m, src);
int mt = (int)m.MetadataToken.RID;
this.methods [mt] = mi;
if (src != null)
src.AddMethod (mi);
}
}
}
private void ProcessSourceLink ()
{
var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink);
if (sourceLinkDebugInfo != null) {
var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
if (sourceLinkContent != null) {
var jObject = JObject.Parse (sourceLinkContent) ["documents"];
sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ());
}
}
}
private Uri GetSourceLinkUrl (string document)
{
if (sourceLinkMappings.TryGetValue (document, out string url)) {
return new Uri (url);
}
foreach (var sourceLinkDocument in sourceLinkMappings) {
string key = sourceLinkDocument.Key;
if (Path.GetFileName (key) != "*") {
continue;
}
var keyTrim = key.TrimEnd ('*');
if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) {
var docUrlPart = document.Replace (keyTrim, "");
return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart);
}
}
return null;
}
private string GetRelativePath (string relativeTo, string path)
{
var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute);
var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) {
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
}
return rel;
}
public IEnumerable<SourceFile> Sources {
get { return this.sources; }
}
public int Id => id;
public string Name => image.Name;
public SourceFile GetDocById (int document)
{
return sources.FirstOrDefault (s => s.SourceId.Document == document);
}
public MethodInfo GetMethodByToken (int token)
{
methods.TryGetValue (token, out var value);
return value;
}
}
internal class SourceFile {
HashSet<MethodInfo> methods;
AssemblyInfo assembly;
int id;
Document doc;
internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
{
this.methods = new HashSet<MethodInfo> ();
this.SourceLinkUri = sourceLinkUri;
this.assembly = assembly;
this.id = id;
this.doc = doc;
this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", "");
this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) {
this.Url = this.SourceUri.ToString ();
} else {
this.Url = DotNetUrl;
}
}
internal void AddMethod (MethodInfo mi)
{
this.methods.Add (mi);
}
public string DebuggerFileName { get; }
public string Url { get; }
public string AssemblyName => assembly.Name;
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
public string DocHashCode => "abcdee" + id;
public SourceId SourceId => new SourceId (assembly.Id, this.id);
public Uri SourceLinkUri { get; }
public Uri SourceUri { get; }
public IEnumerable<MethodInfo> Methods => this.methods;
}
internal class DebugStore {
List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
public DebugStore (string [] loaded_files)
{
bool MatchPdb (string asm, string pdb)
{
return Path.ChangeExtension (asm, "pdb") == pdb;
}
var asm_files = new List<string> ();
var pdb_files = new List<string> ();
foreach (var f in loaded_files) {
var file_name = f;
if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase))
pdb_files.Add (file_name);
else
asm_files.Add (file_name);
}
//FIXME make this parallel
foreach (var p in asm_files) {
try {
var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n));
HttpClient h = new HttpClient ();
var assembly_bytes = h.GetByteArrayAsync (p).Result;
byte [] pdb_bytes = null;
if (pdb != null)
pdb_bytes = h.GetByteArrayAsync (pdb).Result;
this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes));
} catch (Exception e) {
Console.WriteLine ($"Failed to read {p} ({e.Message})");
}
}
}
public IEnumerable<SourceFile> AllSources ()
{
foreach (var a in assemblies) {
foreach (var s in a.Sources)
yield return s;
}
}
public SourceFile GetFileById (SourceId id)
{
return AllSources ().FirstOrDefault (f => f.SourceId.Equals (id));
}
public AssemblyInfo GetAssemblyByName (string name)
{
return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
}
/*
V8 uses zero based indexing for both line and column.
PPDBs uses one based indexing for both line and column.
*/
static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
{
var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
if (start.Line > spStart.Line)
return false;
if (start.Column > spStart.Column && start.Line == sp.StartLine)
return false;
if (end.Line < spEnd.Line)
return false;
if (end.Column < spEnd.Column && end.Line == spEnd.Line)
return false;
return true;
}
public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end)
{
//XXX FIXME no idea what todo with locations on different files
if (start.Id != end.Id)
return null;
var src_id = start.Id;
var doc = GetFileById (src_id);
var res = new List<SourceLocation> ();
if (doc == null) {
//FIXME we need to write up logging here
Console.WriteLine ($"Could not find document {src_id}");
return res;
}
foreach (var m in doc.Methods) {
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
if (Match (sp, start, end))
res.Add (new SourceLocation (m, sp));
}
}
return res;
}
/*
V8 uses zero based indexing for both line and column.
PPDBs uses one based indexing for both line and column.
*/
static bool Match (SequencePoint sp, int line, int column)
{
var bp = (line: line + 1, column: column + 1);
if (sp.StartLine > bp.line || sp.EndLine < bp.line)
return false;
//Chrome sends a zero column even if getPossibleBreakpoints say something else
if (column == 0)
return true;
if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
return false;
if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
return false;
return true;
}
public SourceLocation FindBestBreakpoint (BreakPointRequest req)
{
var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase));
var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase));
if (src == null)
return null;
foreach (var m in src.Methods) {
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
//FIXME handle multi doc methods
if (Match (sp, req.Line, req.Column))
return new SourceLocation (m, sp);
}
}
return null;
}
public string ToUrl (SourceLocation location)
=> location != null ? GetFileById (location.Id).Url : "";
public SourceFile GetFileByUrlRegex (string urlRegex)
{
var regex = new Regex (urlRegex);
return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString()));
}
public SourceFile GetFileByUrl (string url)
=> AllSources ().FirstOrDefault (file => file.Url.ToString() == url);
}
}

View File

@ -1,792 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net;
namespace WsProxy {
internal class MonoCommands {
public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()";
public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready";
public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})";
public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])";
public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})";
public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
}
internal enum MonoErrorCodes {
BpNotFound = 100000,
}
internal class MonoConstants {
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
}
class Frame {
public Frame (MethodInfo method, SourceLocation location, int id)
{
this.Method = method;
this.Location = location;
this.Id = id;
}
public MethodInfo Method { get; private set; }
public SourceLocation Location { get; private set; }
public int Id { get; private set; }
}
class Breakpoint {
public SourceLocation Location { get; private set; }
public int LocalId { get; private set; }
public int RemoteId { get; set; }
public BreakPointState State { get; set; }
public Breakpoint (SourceLocation loc, int localId, BreakPointState state)
{
this.Location = loc;
this.LocalId = localId;
this.State = state;
}
}
enum BreakPointState {
Active,
Disabled,
Pending
}
enum StepKind {
Into,
Out,
Over
}
internal class MonoProxy : WsProxy {
DebugStore store;
List<Breakpoint> breakpoints = new List<Breakpoint> ();
List<Frame> current_callstack;
bool runtime_ready;
int local_breakpoint_id;
int ctx_id;
JObject aux_ctx_data;
public MonoProxy () { }
protected override async Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
{
switch (method) {
case "Runtime.executionContextCreated": {
var ctx = args? ["context"];
var aux_data = ctx? ["auxData"] as JObject;
if (aux_data != null) {
var is_default = aux_data ["isDefault"]?.Value<bool> ();
if (is_default == true) {
var ctx_id = ctx ["id"].Value<int> ();
await OnDefaultContext (ctx_id, aux_data, token);
}
}
break;
}
case "Debugger.paused": {
//TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
await OnBreakPointHit (args, token);
return true;
}
if (top_func == MonoConstants.RUNTIME_IS_READY) {
await OnRuntimeReady (token);
return true;
}
break;
}
case "Debugger.scriptParsed":{
if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) {
// Console.WriteLine ("ignoring wasm event");
return true;
}
break;
}
}
return false;
}
protected override async Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
{
switch (method) {
case "Debugger.getScriptSource": {
var script_id = args? ["scriptId"]?.Value<string> ();
if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
await OnGetScriptSource (id, script_id, token);
return true;
}
break;
}
case "Runtime.compileScript": {
var exp = args? ["expression"]?.Value<string> ();
if (exp.StartsWith ("//dotnet:", StringComparison.InvariantCultureIgnoreCase)) {
OnCompileDotnetScript (id, token);
return true;
}
break;
}
case "Debugger.getPossibleBreakpoints": {
var start = SourceLocation.Parse (args? ["start"] as JObject);
//FIXME support variant where restrictToFunction=true and end is omitted
var end = SourceLocation.Parse (args? ["end"] as JObject);
if (start != null && end != null)
return GetPossibleBreakpoints (id, start, end, token);
break;
}
case "Debugger.setBreakpointByUrl": {
Info ($"BP req {args}");
var bp_req = BreakPointRequest.Parse (args, store);
if (bp_req != null) {
await SetBreakPoint (id, bp_req, token);
return true;
}
break;
}
case "Debugger.removeBreakpoint": {
return await RemoveBreakpoint (id, args, token);
}
case "Debugger.resume": {
await OnResume (token);
break;
}
case "Debugger.stepInto": {
if (this.current_callstack != null) {
await Step (id, StepKind.Into, token);
return true;
}
break;
}
case "Debugger.stepOut": {
if (this.current_callstack != null) {
await Step (id, StepKind.Out, token);
return true;
}
break;
}
case "Debugger.stepOver": {
if (this.current_callstack != null) {
await Step (id, StepKind.Over, token);
return true;
}
break;
}
case "Runtime.getProperties": {
var objId = args? ["objectId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:scope:", StringComparison.InvariantCulture)) {
await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token);
return true;
}
if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture))
{
if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture))
await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES);
if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture))
await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES);
return true;
}
break;
}
}
return false;
}
async Task OnRuntimeReady (CancellationToken token)
{
Info ("RUNTIME READY, PARTY TIME");
await RuntimeReady (token);
await SendCommand ("Debugger.resume", new JObject (), token);
SendEvent ("Mono.runtimeReady", new JObject (), token);
}
async Task OnBreakPointHit (JObject args, CancellationToken token)
{
//FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
var o = JObject.FromObject (new {
expression = MonoCommands.GET_CALL_STACK,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true
});
var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
var res = await SendCommand ("Runtime.evaluate", o, token);
if (res.IsErr) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
//step one, figure out where did we hit
var res_value = res.Value? ["result"]? ["value"];
if (res_value == null || res_value is JValue) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
Debug ($"call stack (err is {res.Error} value is:\n{res.Value}");
var bp_id = res_value? ["breakpoint_id"]?.Value<int> ();
Debug ($"We just hit bp {bp_id}");
if (!bp_id.HasValue) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value);
var src = bp == null ? null : store.GetFileById (bp.Location.Id);
var callFrames = new List<JObject> ();
foreach (var frame in orig_callframes) {
var function_name = frame ["functionName"]?.Value<string> ();
var url = frame ["url"]?.Value<string> ();
if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
var frames = new List<Frame> ();
int frame_id = 0;
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
foreach (var mono_frame in the_mono_frames) {
var il_pos = mono_frame ["il_pos"].Value<int> ();
var method_token = mono_frame ["method_token"].Value<int> ();
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
var asm = store.GetAssemblyByName (assembly_name);
if (asm == null) {
Info ($"Unable to find assembly: {assembly_name}");
continue;
}
var method = asm.GetMethodByToken (method_token);
if (method == null) {
Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
continue;
}
var location = method?.GetLocationByIl (il_pos);
// When hitting a breakpoint on the "IncrementCount" method in the standard
// Blazor project template, one of the stack frames is inside mscorlib.dll
// and we get location==null for it. It will trigger a NullReferenceException
// if we don't skip over that stack frame.
if (location == null) {
continue;
}
Info ($"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
Info ($"\tmethod {method.Name} location: {location}");
frames.Add (new Frame (method, location, frame_id));
callFrames.Add (JObject.FromObject (new {
functionName = method.Name,
callFrameId = $"dotnet:scope:{frame_id}",
functionLocation = method.StartLocation.ToJObject (),
location = location.ToJObject (),
url = store.ToUrl (location),
scopeChain = new [] {
new {
type = "local",
@object = new {
@type = "object",
className = "Object",
description = "Object",
objectId = $"dotnet:scope:{frame_id}",
},
name = method.Name,
startLocation = method.StartLocation.ToJObject (),
endLocation = method.EndLocation.ToJObject (),
}}
}));
++frame_id;
this.current_callstack = frames;
}
} else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
|| url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
callFrames.Add (frame);
}
}
var bp_list = new string [bp == null ? 0 : 1];
if (bp != null)
bp_list [0] = $"dotnet:{bp.LocalId}";
o = JObject.FromObject (new {
callFrames = callFrames,
reason = "other", //other means breakpoint
hitBreakpoints = bp_list,
});
SendEvent ("Debugger.paused", o, token);
}
async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken token)
{
Debug ("Default context created, clearing state and sending events");
//reset all bps
foreach (var b in this.breakpoints){
b.State = BreakPointState.Pending;
}
this.runtime_ready = false;
var o = JObject.FromObject (new {
expression = MonoCommands.IS_RUNTIME_READY_VAR,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true
});
this.ctx_id = ctx_id;
this.aux_ctx_data = aux_data;
Debug ("checking if the runtime is ready");
var res = await SendCommand ("Runtime.evaluate", o, token);
var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
//Debug ($"\t{is_ready}");
if (is_ready.HasValue && is_ready.Value == true) {
Debug ("RUNTIME LOOK READY. GO TIME!");
await OnRuntimeReady (token);
}
}
async Task OnResume (CancellationToken token)
{
//discard frames
this.current_callstack = null;
await Task.CompletedTask;
}
async Task Step (int msg_id, StepKind kind, CancellationToken token)
{
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
SendResponse (msg_id, Result.Ok (new JObject ()), token);
this.current_callstack = null;
await SendCommand ("Debugger.resume", new JObject (), token);
}
async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command)
{
var o = JObject.FromObject(new
{
expression = string.Format(command, object_id),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand("Runtime.evaluate", o, token);
//if we fail we just buble that to the IDE (and let it panic over it)
if (res.IsErr)
{
SendResponse(msg_id, res, token);
return;
}
try {
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
var var_list = new List<JObject>();
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
for (int i = 0; i < values.Length; i+=2)
{
string fieldName = (string)values[i]["name"];
if (fieldName.Contains("k__BackingField")){
fieldName = fieldName.Replace("k__BackingField", "");
fieldName = fieldName.Replace("<", "");
fieldName = fieldName.Replace(">", "");
}
var value = values [i + 1]? ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
var_list.Add(JObject.FromObject(new {
name = fieldName,
value
}));
}
o = JObject.FromObject(new
{
result = var_list
});
} catch (Exception) {
Debug ($"failed to parse {res.Value}");
}
SendResponse(msg_id, Result.Ok(o), token);
}
async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token)
{
var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
var var_ids = string.Join (",", vars.Select (v => v.Index));
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
//if we fail we just buble that to the IDE (and let it panic over it)
if (res.IsErr) {
SendResponse (msg_id, res, token);
return;
}
try {
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
var var_list = new List<JObject> ();
int i = 0;
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
while (values != null && i < vars.Length && i < values.Length) {
var value = values [i] ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
var_list.Add (JObject.FromObject (new {
name = vars [i].Name,
value
}));
i++;
}
//Async methods are special in the way that local variables can be lifted to generated class fields
//value of "this" comes here either
while (i < values.Length) {
String name = values [i] ["name"].ToString ();
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
var value = values [i + 1] ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
var_list.Add (JObject.FromObject (new {
name,
value
}));
i = i + 2;
}
o = JObject.FromObject (new {
result = var_list
});
SendResponse (msg_id, Result.Ok (o), token);
}
catch (Exception) {
SendResponse (msg_id, res, token);
}
}
async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)
{
var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
var method_token = bp.Location.CliLocation.Method.Token;
var il_offset = bp.Location.CliLocation.Offset;
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
if (ret_code.HasValue) {
bp.RemoteId = ret_code.Value;
bp.State = BreakPointState.Active;
//Debug ($"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
}
return res;
}
async Task RuntimeReady (CancellationToken token)
{
var o = JObject.FromObject (new {
expression = MonoCommands.GET_LOADED_FILES,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var loaded_pdbs = await SendCommand ("Runtime.evaluate", o, token);
var the_value = loaded_pdbs.Value? ["result"]? ["value"];
var the_pdbs = the_value?.ToObject<string[]> ();
this.store = new DebugStore (the_pdbs);
foreach (var s in store.AllSources ()) {
var ok = JObject.FromObject (new {
scriptId = s.SourceId.ToString (),
url = s.Url,
executionContextId = this.ctx_id,
hash = s.DocHashCode,
executionContextAuxData = this.aux_ctx_data,
dotNetUrl = s.DotNetUrl
});
//Debug ($"\tsending {s.Url}");
SendEvent ("Debugger.scriptParsed", ok, token);
}
o = JObject.FromObject (new {
expression = MonoCommands.CLEAR_ALL_BREAKPOINTS,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var clear_result = await SendCommand ("Runtime.evaluate", o, token);
if (clear_result.IsErr) {
Debug ($"Failed to clear breakpoints due to {clear_result}");
}
runtime_ready = true;
foreach (var bp in breakpoints) {
if (bp.State != BreakPointState.Pending)
continue;
var res = await EnableBreakPoint (bp, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
//if we fail we just buble that to the IDE (and let it panic over it)
if (!ret_code.HasValue) {
//FIXME figure out how to inform the IDE of that.
Info ($"FAILED TO ENABLE BP {bp.LocalId}");
bp.State = BreakPointState.Disabled;
}
}
}
async Task<bool> RemoveBreakpoint(int msg_id, JObject args, CancellationToken token) {
var bpid = args? ["breakpointId"]?.Value<string> ();
if (bpid?.StartsWith ("dotnet:") != true)
return false;
var the_id = int.Parse (bpid.Substring ("dotnet:".Length));
var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id);
if (bp == null) {
Info ($"Could not find dotnet bp with id {the_id}");
return false;
}
breakpoints.Remove (bp);
//FIXME verify result (and log?)
var res = await RemoveBreakPoint (bp, token);
return true;
}
async Task<Result> RemoveBreakPoint (Breakpoint bp, CancellationToken token)
{
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
if (ret_code.HasValue) {
bp.RemoteId = -1;
bp.State = BreakPointState.Disabled;
}
return res;
}
async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken token)
{
var bp_loc = store.FindBestBreakpoint (req);
Info ($"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'");
if (bp_loc == null) {
Info ($"Could not resolve breakpoint request: {req}");
SendResponse (msg_id, Result.Err(JObject.FromObject (new {
code = (int)MonoErrorCodes.BpNotFound,
message = $"C# Breakpoint at {req} not found."
})), token);
return;
}
Breakpoint bp = null;
if (!runtime_ready) {
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending);
} else {
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled);
var res = await EnableBreakPoint (bp, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
//if we fail we just buble that to the IDE (and let it panic over it)
if (!ret_code.HasValue) {
SendResponse (msg_id, res, token);
return;
}
}
var locations = new List<JObject> ();
locations.Add (JObject.FromObject (new {
scriptId = bp_loc.Id.ToString (),
lineNumber = bp_loc.Line,
columnNumber = bp_loc.Column
}));
breakpoints.Add (bp);
var ok = JObject.FromObject (new {
breakpointId = $"dotnet:{bp.LocalId}",
locations = locations,
});
SendResponse (msg_id, Result.Ok (ok), token);
}
bool GetPossibleBreakpoints (int msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
{
var bps = store.FindPossibleBreakpoints (start, end);
if (bps == null)
return false;
var loc = new List<JObject> ();
foreach (var b in bps) {
loc.Add (b.ToJObject ());
}
var o = JObject.FromObject (new {
locations = loc
});
SendResponse (msg_id, Result.Ok (o), token);
return true;
}
void OnCompileDotnetScript (int msg_id, CancellationToken token)
{
var o = JObject.FromObject (new { });
SendResponse (msg_id, Result.Ok (o), token);
}
async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
{
var id = new SourceId (script_id);
var src_file = store.GetFileById (id);
var res = new StringWriter ();
//res.WriteLine ($"//{id}");
try {
var uri = new Uri (src_file.Url);
if (uri.IsFile && File.Exists(uri.LocalPath)) {
using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
await res.WriteAsync (await f.ReadToEndAsync ());
}
var o = JObject.FromObject (new {
scriptSource = res.ToString ()
});
SendResponse (msg_id, Result.Ok (o), token);
} else if(src_file.SourceLinkUri != null) {
var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
await res.WriteAsync (doc);
var o = JObject.FromObject (new {
scriptSource = res.ToString ()
});
SendResponse (msg_id, Result.Ok (o), token);
} else {
var o = JObject.FromObject (new {
scriptSource = $"// Unable to find document {src_file.SourceUri}"
});
SendResponse (msg_id, Result.Ok (o), token);
}
} catch (Exception e) {
var o = JObject.FromObject (new {
scriptSource = $"// Unable to read document ({e.Message})\n" +
$"Local path: {src_file?.SourceUri}\n" +
$"SourceLink path: {src_file?.SourceLinkUri}\n"
});
SendResponse (msg_id, Result.Ok (o), token);
}
}
}
}

View File

@ -1,3 +0,0 @@
# We only track the .template.config.src items in source control
# The .template.config files are generated on build
src/content/**/.template.config/

View File

@ -1,64 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<NuspecFile>Microsoft.AspNetCore.Blazor.Templates.nuspec</NuspecFile>
<IsShippingPackage>false</IsShippingPackage>
<EnableDefaultItems>False</EnableDefaultItems>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<IncludeBuildOutput>False</IncludeBuildOutput>
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
<DebugType>none</DebugType>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<NoWarn>$(NoWarn);2008</NoWarn>
<Description>Templates for ASP.NET Core Blazor projects.</Description>
<PackageTags>aspnet;templates;blazor;spa</PackageTags>
<IsProjectReferenceProvider>false</IsProjectReferenceProvider>
</PropertyGroup>
<ItemGroup>
<UpToDateCheckInput Include="content\**\.template.config.src\**\*.*" />
</ItemGroup>
<Target Name="PrepareFileLists" AfterTargets="PrepareForBuild">
<ItemGroup>
<_TemplateConfigMainFile Include="content\**\.template.config.src\template.json" />
<_TemplateConfigDir Include="@(_TemplateConfigMainFile->'$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigMainFile.FullPath)'))')" />
<_TemplateConfigFileToCopy Include="%(_TemplateConfigDir.Identity)\**\*.*">
<DestDir>$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigDir.Identity)'))\.template.config\</DestDir>
</_TemplateConfigFileToCopy>
</ItemGroup>
</Target>
<Target
Name="TransformTemplateConfigs"
BeforeTargets="CoreBuild"
DependsOnTargets="SetTemplateJsonSymbolReplacements"
Inputs="@(_TemplateConfigFileToCopy)"
Outputs="@(_TemplateConfigFileToCopy->'%(DestDir)%(FileName)%(Extension)')">
<!--
For each template, copy its '.template.config.src' directory to '.template.config',
removing any earlier output at that location
-->
<RemoveDir Directories="%(_TemplateConfigFileToCopy.DestDir)" />
<Copy SourceFiles="%(_TemplateConfigFileToCopy.Identity)" DestinationFolder="%(_TemplateConfigFileToCopy.DestDir)" />
<!--
Modify the .json files in the .template.config dirs to stamp MSBuild properties into them
-->
<ItemGroup>
<GeneratedContent Include="@(_TemplateConfigFileToCopy->WithMetadataValue('Extension','.json'))">
<OutputPath>%(DestDir)%(RecursiveDir)%(Filename)%(Extension)</OutputPath>
<Properties>$(GeneratedContentProperties)</Properties>
</GeneratedContent>
</ItemGroup>
<GenerateFileFromTemplate
TemplateFile="%(GeneratedContent.Identity)"
Properties="%(GeneratedContent.Properties)"
OutputPath="%(GeneratedContent.OutputPath)">
<Output TaskParameter="OutputPath" ItemName="FileWrites" />
<Output TaskParameter="OutputPath" ItemName="Content" />
</GenerateFileFromTemplate>
</Target>
</Project>

View File

@ -1,34 +0,0 @@
// 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.
namespace System.ComponentModel.DataAnnotations
{
/// <summary>
/// A <see cref="ValidationAttribute"/> that compares two properties
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ComparePropertyAttribute : CompareAttribute
{
/// <summary>
/// Initializes a new instance of <see cref="BlazorCompareAttribute"/>.
/// </summary>
/// <param name="otherProperty">The property to compare with the current property.</param>
public ComparePropertyAttribute(string otherProperty)
: base(otherProperty)
{
}
/// <inheritdoc />
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var validationResult = base.IsValid(value, validationContext);
if (validationResult == ValidationResult.Success)
{
return validationResult;
}
return new ValidationResult(validationResult.ErrorMessage, new[] { validationContext.MemberName });
}
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Provides experimental support for validation using DataAnnotations.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Forms" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests" />
</ItemGroup>
</Project>

View File

@ -1,125 +0,0 @@
// 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.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Microsoft.AspNetCore.Components.Forms
{
public class ObjectGraphDataAnnotationsValidator : ComponentBase
{
private static readonly object ValidationContextValidatorKey = new object();
private static readonly object ValidatedObjectsKey = new object();
private ValidationMessageStore _validationMessageStore;
[CascadingParameter]
internal EditContext EditContext { get; set; }
protected override void OnInitialized()
{
_validationMessageStore = new ValidationMessageStore(EditContext);
// Perform object-level validation (starting from the root model) on request
EditContext.OnValidationRequested += (sender, eventArgs) =>
{
_validationMessageStore.Clear();
ValidateObject(EditContext.Model, new HashSet<object>());
EditContext.NotifyValidationStateChanged();
};
// Perform per-field validation on each field edit
EditContext.OnFieldChanged += (sender, eventArgs) =>
ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier);
}
internal void ValidateObject(object value, HashSet<object> visited)
{
if (value is null)
{
return;
}
if (!visited.Add(value))
{
// Already visited this object.
return;
}
if (value is IEnumerable<object> enumerable)
{
var index = 0;
foreach (var item in enumerable)
{
ValidateObject(item, visited);
index++;
}
return;
}
var validationResults = new List<ValidationResult>();
ValidateObject(value, visited, validationResults);
// Transfer results to the ValidationMessageStore
foreach (var validationResult in validationResults)
{
if (!validationResult.MemberNames.Any())
{
_validationMessageStore.Add(new FieldIdentifier(value, string.Empty), validationResult.ErrorMessage);
continue;
}
foreach (var memberName in validationResult.MemberNames)
{
var fieldIdentifier = new FieldIdentifier(value, memberName);
_validationMessageStore.Add(fieldIdentifier, validationResult.ErrorMessage);
}
}
}
private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults)
{
var validationContext = new ValidationContext(value);
validationContext.Items.Add(ValidationContextValidatorKey, this);
validationContext.Items.Add(ValidatedObjectsKey, visited);
Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true);
}
internal static bool TryValidateRecursive(object value, ValidationContext validationContext)
{
if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is ObjectGraphDataAnnotationsValidator validator)
{
var visited = (HashSet<object>)validationContext.Items[ValidatedObjectsKey];
validator.ValidateObject(value, visited);
return true;
}
return false;
}
private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
{
// DataAnnotations only validates public properties, so that's all we'll look for
var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName);
if (propertyInfo != null)
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
messages.Clear(fieldIdentifier);
messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
editContext.NotifyValidationStateChanged();
}
}
}
}

View File

@ -1,30 +0,0 @@
// 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 Microsoft.AspNetCore.Components.Forms;
namespace System.ComponentModel.DataAnnotations
{
/// <summary>
/// A <see cref="ValidationAttribute"/> that indicates that the property is a complex or collection type that further needs to be validated.
/// <para>
/// By default <see cref="Validator"/> does not recurse in to complex property types during validation.
/// When used in conjunction with <see cref="ObjectGraphDataAnnotationsValidator"/>, this property allows the validation system to validate
/// complex or collection type properties.
/// </para>
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ValidateComplexTypeAttribute : ValidationAttribute
{
/// <inheritdoc />
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!ObjectGraphDataAnnotationsValidator.TryValidateRecursive(value, validationContext))
{
throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(ObjectGraphDataAnnotationsValidator)}.");
}
return ValidationResult.Success;
}
}
}

View File

@ -1,540 +0,0 @@
// 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;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;
using Xunit;
namespace Microsoft.AspNetCore.Components
{
public class ObjectGraphDataAnnotationsValidatorTest
{
public class SimpleModel
{
[Required]
public string Name { get; set; }
[Range(1, 16)]
public int Age { get; set; }
}
[Fact]
public void ValidateObject_SimpleObject()
{
var model = new SimpleModel
{
Age = 23,
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Age);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_SimpleObject_AllValid()
{
var model = new SimpleModel { Name = "Test", Age = 5 };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Age);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
public class ModelWithComplexProperty
{
[Required]
public string Property1 { get; set; }
[ValidateComplexType]
public SimpleModel SimpleModel { get; set; }
}
[Fact]
public void ValidateObject_NullComplexProperty()
{
var model = new ModelWithComplexProperty();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_ModelWithComplexProperties()
{
var model = new ModelWithComplexProperty { SimpleModel = new SimpleModel() };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ModelWithComplexProperties_SomeValid()
{
var model = new ModelWithComplexProperty
{
Property1 = "Value",
SimpleModel = new SimpleModel { Name = "Some Value" },
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
public class TestValidatableObject : IValidatableObject
{
[Required]
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Custom validation error");
}
}
public class ModelWithValidatableComplexProperty
{
[Required]
public string Property1 { get; set; }
[ValidateComplexType]
public TestValidatableObject Property2 { get; set; } = new TestValidatableObject();
}
[Fact]
public void ValidateObject_ValidatableComplexProperty()
{
var model = new ModelWithValidatableComplexProperty();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Property2);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Property2.Name);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ValidatableComplexProperty_ValidatesIValidatableProperty()
{
var model = new ModelWithValidatableComplexProperty
{
Property2 = new TestValidatableObject { Name = "test" },
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(new FieldIdentifier(model.Property2, string.Empty));
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Property2.Name);
Assert.Empty(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ModelIsIValidatable_PropertyHasError()
{
var model = new TestValidatableObject();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_ModelIsIValidatable_ModelHasError()
{
var model = new TestValidatableObject { Name = "test" };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Name);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_CollectionModel()
{
var model = new List<SimpleModel>
{
new SimpleModel(),
new SimpleModel { Name = "test", },
};
var editContext = Validate(model);
var item = model[0];
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, "0"));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => item.Age);
Assert.Single(messages);
item = model[1];
messages = editContext.GetValidationMessages(new FieldIdentifier(model, "1"));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Age);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_CollectionValidatableModel()
{
var model = new List<TestValidatableObject>
{
new TestValidatableObject(),
new TestValidatableObject { Name = "test", },
};
var editContext = Validate(model);
var item = model[0];
var messages = editContext.GetValidationMessages(() => item.Name);
Assert.Single(messages);
item = model[1];
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Empty(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
private class Level1Validation
{
[ValidateComplexType]
public Level2Validation Level2 { get; set; }
}
public class Level2Validation
{
[ValidateComplexType]
public SimpleModel Level3 { get; set; }
}
[Fact]
public void ValidateObject_ManyLevels()
{
var model = new Level1Validation
{
Level2 = new Level2Validation
{
Level3 = new SimpleModel
{
Age = 47,
}
}
};
var editContext = Validate(model);
var level3 = model.Level2.Level3;
var messages = editContext.GetValidationMessages(() => level3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => level3.Age);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
private class Person
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Person Related { get; set; }
}
[Fact]
public void ValidateObject_RecursiveRelation()
{
var model = new Person { Related = new Person() };
model.Related.Related = model;
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Related.Name);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_RecursiveRelation_OverManySteps()
{
var person1 = new Person();
var person2 = new Person { Name = "Valid name" };
var person3 = new Person();
var person4 = new Person();
person1.Related = person2;
person2.Related = person3;
person3.Related = person4;
person4.Related = person1;
var editContext = Validate(person1);
var messages = editContext.GetValidationMessages(() => person1.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person2.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => person3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person4.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
private class Node
{
[Required]
public string Id { get; set; }
[ValidateComplexType]
public List<Node> Related { get; set; } = new List<Node>();
}
[Fact]
public void ValidateObject_RecursiveRelation_ViaCollection()
{
var node1 = new Node();
var node2 = new Node { Id = "Valid Id" };
var node3 = new Node();
node1.Related.Add(node2);
node2.Related.Add(node3);
node3.Related.Add(node1);
var editContext = Validate(node1);
var messages = editContext.GetValidationMessages(() => node1.Id);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => node2.Id);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => node3.Id);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_RecursiveRelation_InCollection()
{
var person1 = new Person();
var person2 = new Person { Name = "Valid name" };
var person3 = new Person();
var person4 = new Person();
person1.Related = person2;
person2.Related = person3;
person3.Related = person4;
person4.Related = person1;
var editContext = Validate(person1);
var messages = editContext.GetValidationMessages(() => person1.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person2.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => person3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person4.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateField_PropertyValid()
{
var model = new SimpleModel { Age = 1 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_PropertyInvalid()
{
var model = new SimpleModel { Age = 42 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_AfterSubmitValidation()
{
var model = new SimpleModel { Age = 42 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
model.Age = 4;
editContext.NotifyFieldChanged(fieldIdentifier);
messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_ModelWithComplexProperty()
{
var model = new ModelWithComplexProperty
{
SimpleModel = new SimpleModel { Age = 1 },
};
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Name);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_ModelWithComplexProperty_AfterSubmitValidation()
{
var model = new ModelWithComplexProperty
{
Property1 = "test",
SimpleModel = new SimpleModel { Age = 29, Name = "Test" },
};
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Age);
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
model.SimpleModel.Age = 9;
editContext.NotifyFieldChanged(fieldIdentifier);
messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
private static EditContext Validate(object model)
{
var editContext = new EditContext(model);
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
validator.OnInitialized();
editContext.Validate();
return editContext;
}
private static EditContext ValidateField(object model, in FieldIdentifier field)
{
var editContext = new EditContext(model);
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
validator.OnInitialized();
editContext.NotifyFieldChanged(field);
return editContext;
}
private class TestObjectGraphDataAnnotationsValidator : ObjectGraphDataAnnotationsValidator
{
public new void OnInitialized() => base.OnInitialized();
}
}
}

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Exe</OutputType>
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
</ItemGroup>
</Project>

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Sample Blazor app</title>
</head>
<body>
<app>Loading...</app>
<script src="customJsFileForTests.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
</ItemGroup>
</Project>

View File

@ -9,19 +9,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Analyzers.Tests", "Analyzers\test\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "{F000C49D-3857-42A4-918D-DA4C08691FE2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor", "Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly", "WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Tests", "Blazor\Blazor\test\Microsoft.AspNetCore.Blazor.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Tests", "WebAssembly\WebAssembly\test\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build", "Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build", "WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build.Tests", "Blazor\Build\test\Microsoft.AspNetCore.Blazor.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.Tests", "WebAssembly\Build\test\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DevServer", "WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server", "WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
EndProject
@ -206,10 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Connections.Client", "..\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj", "{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient", "Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj", "{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient.Tests", "Blazor\Http\test\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", "{E4C01A3F-D3C1-4639-A6A9-930E918843DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Tests", "Web\test\Microsoft.AspNetCore.Components.Web.Tests.csproj", "{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{08791FEE-761D-40EF-B701-1D31FD1E6E53}"
@ -236,23 +232,41 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop", "Mono.WebAssembly.Interop", "{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{346EC9B8-BF36-4A5E-A1A3-77879931713A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA95650-E56E-470A-82A3-BC6572E4D6CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server.Tests", "WebAssembly\Server\test\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "{3D0ED658-9DAC-4066-A587-795321FA1C98}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Authentication.WebAssembly.Msal", "WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj", "{4B4E4247-7BBF-444E-9737-407D34821D70}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression", "WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj", "{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DebugProxy", "WebAssembly\DebugProxy\src\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj", "{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevServer", "DevServer", "{C6B58D53-04E2-4D65-B445-B510A3CB7569}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.WebAssembly", "WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj", "{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication", "WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "{5CD61479-5181-4A5B-B90F-9F34316248B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", "WebAssembly\WebAssembly.Authentication\test\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", "{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{81250121-9B43-40B1-BF11-CE4458F2676C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.HttpHandler", "WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -1344,30 +1358,6 @@ Global
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x64.Build.0 = Release|Any CPU
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.ActiveCfg = Release|Any CPU
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.Build.0 = Release|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -1476,42 +1466,6 @@ Global
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -1548,6 +1502,102 @@ Global
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1555,12 +1605,12 @@ Global
GlobalSection(NestedProjects) = preSolution
{ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
{F000C49D-3857-42A4-918D-DA4C08691FE2} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
{C1E2C117-BE47-4E29-94B3-753262D97A5C} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
@ -1645,8 +1695,6 @@ Global
{DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E4C01A3F-D3C1-4639-A6A9-930E918843DD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373}
{956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
{B13CDE69-ED22-4664-AAD7-686ED8CD5E88} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
@ -1656,14 +1704,23 @@ Global
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
{346EC9B8-BF36-4A5E-A1A3-77879931713A} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{1FA95650-E56E-470A-82A3-BC6572E4D6CD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{3D0ED658-9DAC-4066-A587-795321FA1C98} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{4B4E4247-7BBF-444E-9737-407D34821D70} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
{C6B58D53-04E2-4D65-B445-B510A3CB7569} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{5CD61479-5181-4A5B-B90F-9F34316248B3} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{81250121-9B43-40B1-BF11-CE4458F2676C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

View File

@ -6,22 +6,6 @@
"Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj",
"Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
"Authorization\\test\\Microsoft.AspNetCore.Components.Authorization.Tests.csproj",
"Blazor\\Blazor\\src\\Microsoft.AspNetCore.Blazor.csproj",
"Blazor\\Blazor\\test\\Microsoft.AspNetCore.Blazor.Tests.csproj",
"Blazor\\Build\\src\\Microsoft.AspNetCore.Blazor.Build.csproj",
"Blazor\\Build\\test\\Microsoft.AspNetCore.Blazor.Build.Tests.csproj",
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
"Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
"Blazor\\testassets\\MonoSanity\\MonoSanity.csproj",
"Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj",
"Components\\perf\\Microsoft.AspNetCore.Components.Performance.csproj",
"Components\\src\\Microsoft.AspNetCore.Components.csproj",
"Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj",
@ -32,12 +16,27 @@
"Samples\\BlazorServerApp\\BlazorServerApp.csproj",
"Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
"WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
"WebAssembly\\Build\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj",
"WebAssembly\\Build\\test\\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj",
"WebAssembly\\Compression\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj",
"WebAssembly\\DebugProxy\\src\\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj",
"WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj",
"WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
"WebAssembly\\Server\\src\\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj",
"WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj",
"WebAssembly\\WebAssembly.Authentication\\src\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj",
"WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
"WebAssembly\\WebAssemblyHttpHandler\\src\\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj",
"WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj",
"WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj",
"Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
"test\\testassets\\TestServer\\Components.TestServer.csproj"
]

View File

@ -10,11 +10,11 @@
</PropertyGroup>
<PropertyGroup>
<PackageTags>aspnetcore;components</PackageTags>
<EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget>
</PropertyGroup>
<!-- This property points to the latest released Microsoft.AspNetCore.App version it needs to be updated to
target the latest patch before a preview release. -->
<LatestAspNetCoreReferenceVersion>3.1.0</LatestAspNetCoreReferenceVersion>
<PropertyGroup>
<PackageTags>aspnetcore;components</PackageTags>
<ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot>

View File

@ -3,26 +3,6 @@
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
</PropertyGroup>
<!-- We need to do this because our build config interferes with the FrameworkReference definition.
This is a way to add the framework defition to the projects that need it (like Blazor Server and
Blazor Dev Server) -->
<Target Name="_AddAspNetCoreFrameworkReference" BeforeTargets="ProcessFrameworkReferences" Condition="'$(UseLatestAspNetCoreReference)' == 'true' ">
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" Version="$(LatestAspNetCoreReferenceVersion)" />
<KnownFrameworkReference Include="Microsoft.AspNetCore.App">
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeFrameworkName>Microsoft.AspNetCore.App</RuntimeFrameworkName>
<DefaultRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</DefaultRuntimeFrameworkVersion>
<LatestRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</LatestRuntimeFrameworkVersion>
<TargetingPackName>Microsoft.AspNetCore.App.Ref</TargetingPackName>
<TargetingPackVersion>$(LatestAspNetCoreReferenceVersion)</TargetingPackVersion>
<RuntimePackNamePatterns>Microsoft.AspNetCore.App.Runtime.**RID**</RuntimePackNamePatterns>
<RuntimePackRuntimeIdentifiers>linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86</RuntimePackRuntimeIdentifiers>
<IsTrimmable>true</IsTrimmable>
</KnownFrameworkReference>
</ItemGroup>
</Target>
<ItemGroup>
<!-- Add a project dependency without reference output assemblies to enforce build order -->
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
@ -35,7 +15,29 @@
Private="false" />
</ItemGroup>
<Import Project="Blazor\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
<Import Project="WebAssembly\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
<ItemGroup Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'">
<ProjectReference
Include="$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj"
CopyLocal="false" />
</ItemGroup>
<Target Name="_FixupReferenceToWebAssemblyHttpHandler"
Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'"
AfterTargets="_ResolveBlazorInputs"
BeforeTargets="_ResolveBlazorOutputs">
<!--
ProjectReference doesn't really play well with IncludeAssets behavior you get when referencing
the package with IncludeAssets="compile".
-->
<ItemGroup>
<_HttpHandlerAssembly Include="@(_BlazorUserRuntimeAssembly)"
Condition="%(_BlazorUserRuntimeAssembly.ProjectReferenceOriginalItemSpec) == '$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj'" />
<_BlazorUserRuntimeAssembly Remove="@(_HttpHandlerAssembly)" />
</ItemGroup>
</Target>
</Project>

View File

@ -12,7 +12,6 @@
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" />
</ItemGroup>
</Project>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,19 +17,19 @@
"@aspnet/signalr": "link:../../SignalR/clients/ts/signalr",
"@aspnet/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack",
"@dotnet/jsinterop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz",
"@types/emscripten": "0.0.31",
"@types/jest": "^24.0.6",
"@types/emscripten": "^1.39.3",
"@types/jest": "^24.9.1",
"@types/jsdom": "11.0.6",
"@typescript-eslint/eslint-plugin": "^1.5.0",
"@typescript-eslint/parser": "^1.5.0",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"eslint": "^5.16.0",
"jest": "^24.8.0",
"rimraf": "^2.6.2",
"ts-jest": "^24.0.0",
"jest": "^24.9.0",
"rimraf": "^2.7.1",
"ts-jest": "^24.3.0",
"ts-loader": "^4.4.1",
"typescript": "^3.5.3",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6"
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
"resolutions": {
"**/set-value": "^2.0.1"

View File

@ -0,0 +1,47 @@
const uint64HighPartShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
export function readInt32LE(buffer: Uint8Array, position: number): any {
return (buffer[position])
| (buffer[position + 1] << 8)
| (buffer[position + 2] << 16)
| (buffer[position + 3] << 24);
}
export function readUint32LE(buffer: Uint8Array, position: number): any {
return (buffer[position])
+ (buffer[position + 1] << 8)
+ (buffer[position + 2] << 16)
+ ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
}
export function readUint64LE(buffer: Uint8Array, position: number): any {
// This cannot be done using bit-shift operators in JavaScript, because
// those all implicitly convert to int32
const highPart = readUint32LE(buffer, position + 4);
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
}
export function readLEB128(buffer: Uint8Array, position: number) {
let result = 0;
let shift = 0;
for (let index = 0; index < 4; index++) {
const byte = buffer[position + index];
result |= (byte & 127) << shift;
if (byte < 128) {
break;
}
shift += 7;
}
return result;
}
export function numLEB128Bytes(value: number) {
return value < 128 ? 1
: value < 16384 ? 2
: value < 2097152 ? 3 : 4;
}

View File

@ -9,14 +9,14 @@ import { ConsoleLogger } from './Platform/Logging/Loggers';
import { LogLevel, Logger } from './Platform/Logging/Logger';
import { discoverComponents, CircuitDescriptor } from './Platform/Circuits/CircuitManager';
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
import { resolveOptions, BlazorOptions } from './Platform/Circuits/BlazorOptions';
import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
let renderingFailed = false;
let started = false;
async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> {
async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
if (started) {
throw new Error('Blazor has already started.');
}
@ -72,7 +72,7 @@ async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> {
logger.log(LogLevel.Information, 'Blazor server-side application started.');
}
async function initializeConnection(options: BlazorOptions, logger: Logger, circuit: CircuitDescriptor): Promise<signalR.HubConnection> {
async function initializeConnection(options: CircuitStartOptions, logger: Logger, circuit: CircuitDescriptor): Promise<signalR.HubConnection> {
const hubProtocol = new MessagePackHubProtocol();
(hubProtocol as unknown as { name: string }).name = 'blazorpack';

View File

@ -4,20 +4,24 @@ import * as Environment from './Environment';
import { monoPlatform } from './Platform/Mono/MonoPlatform';
import { renderBatch } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { Pointer } from './Platform/Platform';
import { shouldAutoStart } from './BootCommon';
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader';
import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
import { BootConfigResult } from './Platform/BootConfig';
import { Pointer } from './Platform/Platform';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
let started = false;
async function boot(options?: any): Promise<void> {
async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
if (started) {
throw new Error('Blazor has already started.');
}
started = true;
setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Blazor', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
const platform = Environment.setPlatform(monoPlatform);
@ -29,51 +33,39 @@ async function boot(options?: any): Promise<void> {
// Configure navigation via JS Interop
window['Blazor']._internal.navigationManager.listenForNavigationEvents(async (uri: string, intercepted: boolean): Promise<void> => {
await DotNet.invokeMethodAsync(
'Microsoft.AspNetCore.Blazor',
'Microsoft.AspNetCore.Components.WebAssembly',
'NotifyLocationChanged',
uri,
intercepted
);
});
// Fetch the boot JSON file
const bootConfig = await fetchBootConfigAsync();
// Fetch the resources and prepare the Mono runtime
const bootConfigResult = await BootConfigResult.initAsync();
if (!bootConfig.linkerEnabled) {
console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414');
}
// Determine the URLs of the assemblies we want to load, then begin fetching them all
const loadAssemblyUrls = bootConfig.assemblies
.map(filename => `_framework/_bin/${filename}`);
const [resourceLoader] = await Promise.all([
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, options || {}),
WebAssemblyConfigLoader.initAsync(bootConfigResult)]);
try {
await platform.start(loadAssemblyUrls);
await platform.start(resourceLoader);
} catch (ex) {
throw new Error(`Failed to start platform. Reason: ${ex}`);
}
// Start up the application
platform.callEntryPoint(bootConfig.entryAssembly);
}
async function fetchBootConfigAsync() {
// Later we might make the location of this configurable (e.g., as an attribute on the <script>
// element that's importing this file), but currently there isn't a use case for that.
const bootConfigResponse = await fetch('_framework/blazor.boot.json', { method: 'Get', credentials: 'include' });
return bootConfigResponse.json() as Promise<BootJsonData>;
}
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
interface BootJsonData {
entryAssembly: string;
assemblies: string[];
linkerEnabled: boolean;
platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly);
}
window['Blazor'].start = boot;
if (shouldAutoStart()) {
boot().catch(error => {
Module.printErr(error); // Logs it, and causes the error UI to appear
if (typeof Module !== 'undefined' && Module.printErr) {
// Logs it, and causes the error UI to appear
Module.printErr(error);
} else {
// The error must have happened so early we didn't yet set up the error UI, so just log to console
console.error(error);
}
});
}

View File

@ -0,0 +1,38 @@
export class BootConfigResult {
private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) {
}
static async initAsync(): Promise<BootConfigResult> {
const bootConfigResponse = await fetch('_framework/blazor.boot.json', {
method: 'GET',
credentials: 'include',
cache: 'no-cache'
});
// While we can expect an ASP.NET Core hosted application to include the environment, other
// hosts may not. Assume 'Production' in the absenc of any specified value.
const applicationEnvironment = bootConfigResponse.headers.get('Blazor-Environment') || 'Production';
const bootConfig: BootJsonData = await bootConfigResponse.json();
return new BootConfigResult(bootConfig, applicationEnvironment);
};
}
// Keep in sync with bootJsonData in Microsoft.AspNetCore.Components.WebAssembly.Build
export interface BootJsonData {
readonly entryAssembly: string;
readonly resources: ResourceGroups;
readonly debugBuild: boolean;
readonly linkerEnabled: boolean;
readonly cacheBootResources: boolean;
readonly config: string[];
}
export interface ResourceGroups {
readonly assembly: ResourceList;
readonly pdb?: ResourceList;
readonly runtime: ResourceList;
readonly satelliteResources?: { [cultureName: string] : ResourceList };
}
export type ResourceList = { [name: string]: string };

View File

@ -1,13 +1,13 @@
import { LogLevel } from '../Logging/Logger';
export interface BlazorOptions {
export interface CircuitStartOptions {
configureSignalR: (builder: signalR.HubConnectionBuilder) => void;
logLevel: LogLevel;
reconnectionOptions: ReconnectionOptions;
reconnectionHandler?: ReconnectionHandler;
}
export function resolveOptions(userOptions?: Partial<BlazorOptions>): BlazorOptions {
export function resolveOptions(userOptions?: Partial<CircuitStartOptions>): CircuitStartOptions {
const result = { ...defaultOptions, ...userOptions };
// The spread operator can't be used for a deep merge, so do the same for subproperties
@ -29,7 +29,7 @@ export interface ReconnectionHandler {
onConnectionUp(): void;
}
const defaultOptions: BlazorOptions = {
const defaultOptions: CircuitStartOptions = {
configureSignalR: (_) => { },
logLevel: LogLevel.Warning,
reconnectionOptions: {

View File

@ -1,4 +1,4 @@
import { ReconnectionHandler, ReconnectionOptions } from './BlazorOptions';
import { ReconnectionHandler, ReconnectionOptions } from './CircuitStartOptions';
import { ReconnectDisplay } from './ReconnectDisplay';
import { DefaultReconnectDisplay } from './DefaultReconnectDisplay';
import { UserSpecifiedDisplay } from './UserSpecifiedDisplay';

View File

@ -1,4 +1,4 @@
import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
import { WebAssemblyResourceLoader } from '../WebAssemblyResourceLoader';
const currentBrowserIsChrome = (window as any).chrome
&& navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome
@ -9,9 +9,8 @@ export function hasDebuggingEnabled() {
return hasReferencedPdbs && currentBrowserIsChrome;
}
export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
hasReferencedPdbs = loadAssemblyUrls
.some(url => /\.pdb$/.test(getFileNameFromUrl(url)));
export function attachDebuggerHotkey(resourceLoader: WebAssemblyResourceLoader) {
hasReferencedPdbs = !!resourceLoader.bootConfig.resources.pdb;
// Use the combination shift+alt+D because it isn't used by the major browsers
// for anything else by default
@ -26,7 +25,7 @@ export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
if (!hasReferencedPdbs) {
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
} else if (!currentBrowserIsChrome) {
console.error('Currently, only Edge(Chromium) or Chrome is supported for debugging.');
console.error('Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging.');
} else {
launchDebugger();
}

View File

@ -1,17 +1,38 @@
import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getFileNameFromUrl } from '../Url';
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
import { showErrorNotification } from '../../BootErrors';
import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader';
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
import { loadTimezoneData } from './TimezoneDataFile';
import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions';
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
let mono_string_get_utf8: (managedString: System_String) => Pointer;
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
const appBinDirName = 'appBinDir';
const uint64HighOrderShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
// Memory access helpers
// The implementations are exactly equivalent to what the global getValue(addr, type) function does,
// except without having to parse the 'type' parameter, and with less risk of mistakes at the call site
function getValueI16(ptr: number) { return Module.HEAP16[ptr >> 1]; }
function getValueI32(ptr: number) { return Module.HEAP32[ptr >> 2]; }
function getValueFloat(ptr: number) { return Module.HEAPF32[ptr >> 2]; }
function getValueU64(ptr: number) {
// There is no Module.HEAPU64, and Module.getValue(..., 'i64') doesn't work because the implementation
// treats 'i64' as being the same as 'i32'. Also we must take care to read both halves as unsigned.
const heapU32Index = ptr >> 2;
const highPart = Module.HEAPU32[heapU32Index + 1];
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index];
}
export const monoPlatform: Platform = {
start: function start(loadAssemblyUrls: string[]) {
start: function start(resourceLoader: WebAssemblyResourceLoader) {
return new Promise<void>((resolve, reject) => {
attachDebuggerHotkey(loadAssemblyUrls);
attachDebuggerHotkey(resourceLoader);
// dotnet.js assumes the existence of this
window['Browser'] = {
@ -22,8 +43,8 @@ export const monoPlatform: Platform = {
// For compatibility with macOS Catalina, we have to assign a temporary value to window.Module
// before we start loading the WebAssembly files
addGlobalModuleScriptTagsToDocument(() => {
window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject);
addScriptTagsToDocument();
window['Module'] = createEmscriptenModuleInstance(resourceLoader, resolve, reject);
addScriptTagsToDocument(resourceLoader);
});
});
},
@ -35,30 +56,19 @@ export const monoPlatform: Platform = {
// In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the
// outer promise reflects the startup process, and the inner one reflects the possibly-async
// .NET entrypoint method.
const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint');
const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Hosting.EntrypointInvoker', 'InvokeEntrypoint');
// Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved.
invokeEntrypoint(assemblyName, null);
},
toJavaScriptString: function toJavaScriptString(managedString: System_String) {
// Comments from original Mono sample:
// FIXME this is wastefull, we could remove the temp malloc by going the UTF16 route
// FIXME this is unsafe, cuz raw objects could be GC'd.
const utf8 = mono_string_get_utf8(managedString);
const res = Module.UTF8ToString(utf8);
Module._free(utf8 as any);
return res;
},
toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
const dataPtr = getArrayDataPointer(array);
const length = Module.getValue(dataPtr, 'i32');
const length = getValueI32(dataPtr);
return new Uint8Array(Module.HEAPU8.buffer, dataPtr + 4, length);
},
getArrayLength: function getArrayLength(array: System_Array<any>): number {
return Module.getValue(getArrayDataPointer(array), 'i32');
return getValueI32(getArrayDataPointer(array));
},
getArrayEntryPtr: function getArrayEntryPtr<TPtr extends Pointer>(array: System_Array<TPtr>, index: number, itemSize: number): TPtr {
@ -73,37 +83,42 @@ export const monoPlatform: Platform = {
},
readInt16Field: function readHeapInt16(baseAddress: Pointer, fieldOffset?: number): number {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i16');
return getValueI16((baseAddress as any as number) + (fieldOffset || 0));
},
readInt32Field: function readHeapInt32(baseAddress: Pointer, fieldOffset?: number): number {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32');
return getValueI32((baseAddress as any as number) + (fieldOffset || 0));
},
readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number {
// Module.getValue(..., 'i64') doesn't work because the implementation treats 'i64' as
// being the same as 'i32'. Also we must take care to read both halves as unsigned.
const address = (baseAddress as any as number) + (fieldOffset || 0);
const heapU32Index = address >> 2;
const highPart = Module.HEAPU32[heapU32Index + 1];
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index];
return getValueU64((baseAddress as any as number) + (fieldOffset || 0));
},
readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'float');
return getValueFloat((baseAddress as any as number) + (fieldOffset || 0));
},
readObjectField: function readHeapObject<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T {
return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32') as any as T;
return getValueI32((baseAddress as any as number) + (fieldOffset || 0)) as any as T;
},
readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number): string | null {
const fieldValue = Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32');
return fieldValue === 0 ? null : monoPlatform.toJavaScriptString(fieldValue as any as System_String);
readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null {
const fieldValue = getValueI32((baseAddress as any as number) + (fieldOffset || 0));
if (fieldValue === 0) {
return null;
}
if (readBoolValueAsString) {
// Some fields are stored as a union of bool | string | null values, but need to read as a string.
// If the stored value is a bool, the behavior we want is empty string ('') for true, or null for false.
const unboxedValue = BINDING.unbox_mono_obj(fieldValue as any as System_Object);
if (typeof (unboxedValue) === 'boolean') {
return unboxedValue ? '' : null;
}
return unboxedValue;
}
return BINDING.conv_string(fieldValue as any as System_String);
},
readStructField: function readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T {
@ -111,15 +126,43 @@ export const monoPlatform: Platform = {
},
};
function addScriptTagsToDocument() {
function addScriptTagsToDocument(resourceLoader: WebAssemblyResourceLoader) {
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
if (!browserSupportsNativeWebAssembly) {
throw new Error('This browser does not support WebAssembly.');
}
// The dotnet.*.js file has a version or hash in its name as a form of cache-busting. This is needed
// because it's the only part of the loading process that can't use cache:'no-cache' (because it's
// not a 'fetch') and isn't controllable by the developer (so they can't put in their own cache-busting
// querystring). So, to find out the exact URL we have to search the boot manifest.
const dotnetJsResourceName = Object
.keys(resourceLoader.bootConfig.resources.runtime)
.filter(n => n.startsWith('dotnet.') && n.endsWith('.js'))[0];
const dotnetJsContentHash = resourceLoader.bootConfig.resources.runtime[dotnetJsResourceName];
const scriptElem = document.createElement('script');
scriptElem.src = '_framework/wasm/dotnet.js';
scriptElem.src = `_framework/wasm/${dotnetJsResourceName}`;
scriptElem.defer = true;
// For consistency with WebAssemblyResourceLoader, we only enforce SRI if caching is allowed
if (resourceLoader.bootConfig.cacheBootResources) {
scriptElem.integrity = dotnetJsContentHash;
scriptElem.crossOrigin = 'anonymous';
}
// Allow overriding the URI from which the dotnet.*.js file is loaded
if (resourceLoader.startOptions.loadBootResource) {
const resourceType: WebAssemblyBootResourceType = 'dotnetjs';
const customSrc = resourceLoader.startOptions.loadBootResource(
resourceType, dotnetJsResourceName, scriptElem.src, dotnetJsContentHash);
if (typeof(customSrc) === 'string') {
scriptElem.src = customSrc;
} else if (customSrc) {
// Since we must load this via a <script> tag, it's only valid to supply a URI (and not a Request, say)
throw new Error(`For a ${resourceType} resource, custom loaders must supply a URI string.`);
}
}
document.body.appendChild(scriptElem);
}
@ -140,79 +183,151 @@ function addGlobalModuleScriptTagsToDocument(callback: () => void) {
document.body.appendChild(scriptElem);
}
function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () => void, onError: (reason?: any) => void) {
const module = {} as typeof Module;
const wasmBinaryFile = '_framework/wasm/dotnet.wasm';
function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader, onReady: () => void, onError: (reason?: any) => void) {
const resources = resourceLoader.bootConfig.resources;
const module = (window['Module'] || { }) as typeof Module;
const suppressMessages = ['DEBUGGING ENABLED'];
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(line));
module.printErr = line => {
console.error(`WASM: ${line}`);
// If anything writes to stderr, treat it as a critical exception. The underlying runtime writes
// to stderr if a truly critical problem occurs outside .NET code. Note that .NET unhandled
// exceptions also reach this, but via a different code path - see dotNetCriticalError below.
console.error(line);
showErrorNotification();
};
module.preRun = [];
module.postRun = [];
module.preloadPlugins = [];
module.preRun = module.preRun || [];
module.postRun = module.postRun || [];
(module as any).preloadPlugins = [];
module.locateFile = fileName => {
switch (fileName) {
case 'dotnet.wasm': return wasmBinaryFile;
default: return fileName;
}
// Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel.
const dotnetWasmResourceName = 'dotnet.wasm';
const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/_bin/${filename}`, 'assembly');
const pdbsBeingLoaded = resourceLoader.loadResources(resources.pdb || {}, filename => `_framework/_bin/${filename}`, 'pdb');
const wasmBeingLoaded = resourceLoader.loadResource(
/* name */ dotnetWasmResourceName,
/* url */ `_framework/wasm/${dotnetWasmResourceName}`,
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName],
/* type */ 'dotnetwasm');
const dotnetTimeZoneResourceName = 'dotnet.timezones.dat';
let timeZoneResource: LoadingResource | undefined;
if (resourceLoader.bootConfig.resources.runtime.hasOwnProperty(dotnetTimeZoneResourceName)) {
timeZoneResource = resourceLoader.loadResource(
dotnetTimeZoneResourceName,
`_framework/wasm/${dotnetTimeZoneResourceName}`,
resourceLoader.bootConfig.resources.runtime[dotnetTimeZoneResourceName],
'timezonedata');
}
// Override the mechanism for fetching the main wasm file so we can connect it to our cache
module.instantiateWasm = (imports, successCallback): Emscripten.WebAssemblyExports => {
(async () => {
let compiledInstance: WebAssembly.Instance;
try {
const dotnetWasmResource = await wasmBeingLoaded;
compiledInstance = await compileWasmModule(dotnetWasmResource, imports);
} catch (ex) {
module.printErr(ex);
throw ex;
}
successCallback(compiledInstance);
})();
return []; // No exports
};
module.preRun.push(() => {
// By now, emscripten should be initialised enough that we can capture these methods for later use
const mono_wasm_add_assembly = Module.cwrap('mono_wasm_add_assembly', null, [
'string',
'number',
'number',
]);
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
mono_wasm_add_assembly = cwrap('mono_wasm_add_assembly', null, ['string', 'number', 'number']);
mono_string_get_utf8 = cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
MONO.loaded_files = [];
loadAssemblyUrls.forEach(url => {
const filename = getFileNameFromUrl(url);
const runDependencyId = `blazor:${filename}`;
addRunDependency(runDependencyId);
asyncLoad(url).then(
data => {
const heapAddress = Module._malloc(data.length);
const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress, data.length);
heapMemory.set(data);
mono_wasm_add_assembly(filename, heapAddress, data.length);
MONO.loaded_files.push(toAbsoluteUrl(url));
removeRunDependency(runDependencyId);
},
errorInfo => {
// If it's a 404 on a .pdb, we don't want to block the app from starting up.
// We'll just skip that file and continue (though the 404 is logged in the console).
// This happens if you build a Debug build but then run in Production environment.
const isPdb404 = errorInfo instanceof XMLHttpRequest
&& errorInfo.status === 404
&& filename.match(/\.pdb$/);
if (!isPdb404) {
onError(errorInfo);
if (timeZoneResource) {
loadTimezone(timeZoneResource);
}
// Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded
// Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless
// of the extensions in the URLs. This allows loading assemblies with arbitrary filenames.
assembliesBeingLoaded.forEach(r => addResourceAsAssembly(r, changeExtension(r.name, '.dll')));
pdbsBeingLoaded.forEach(r => addResourceAsAssembly(r, r.name));
window['Blazor']._internal.dotNetCriticalError = (message: System_String) => {
module.printErr(BINDING.conv_string(message) || '(null)');
};
// Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application
// startup sequence to load satellite assemblies for the application's culture.
window['Blazor']._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray: System_Array<System_String>) : System_Object => {
const culturesToLoad = BINDING.mono_array_to_js_array<System_String, string>(culturesToLoadDotNetArray);
const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources;
if (satelliteResources) {
const resourcePromises = Promise.all(culturesToLoad
.filter(culture => satelliteResources.hasOwnProperty(culture))
.map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/_bin/${fileName}`, 'assembly'))
.reduce((previous, next) => previous.concat(next), new Array<LoadingResource>())
.map(async resource => (await resource.response).arrayBuffer()));
return BINDING.js_to_mono_obj(
resourcePromises.then(resourcesToLoad => {
if (resourcesToLoad.length) {
window['Blazor']._internal.readSatelliteAssemblies = () => {
const array = BINDING.mono_obj_array_new(resourcesToLoad.length);
for (var i = 0; i < resourcesToLoad.length; i++) {
BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i])));
}
return array;
};
}
removeRunDependency(runDependencyId);
}
);
});
return resourcesToLoad.length;
}));
}
return BINDING.js_to_mono_obj(Promise.resolve(0));
}
});
module.postRun.push(() => {
if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) {
resourceLoader.logToConsole();
}
resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background
MONO.mono_wasm_setenv("MONO_URI_DOTNETRELATIVEORABSOLUTE", "true");
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
load_runtime(appBinDirName, hasDebuggingEnabled() ? 1 : 0);
MONO.mono_wasm_runtime_is_ready = true;
const load_runtime = cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
// -1 enables debugging with logging disabled. 0 disables debugging entirely.
load_runtime(appBinDirName, hasDebuggingEnabled() ? -1 : 0);
MONO.mono_wasm_runtime_ready ();
attachInteropInvoker();
onReady();
});
return module;
async function addResourceAsAssembly(dependency: LoadingResource, loadAsName: string) {
const runDependencyId = `blazor:${dependency.name}`;
addRunDependency(runDependencyId);
try {
// Wait for the data to be loaded and verified
const dataBuffer = await dependency.response.then(r => r.arrayBuffer());
// Load it into the Mono runtime
const data = new Uint8Array(dataBuffer);
const heapAddress = Module._malloc(data.length);
const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress, data.length);
heapMemory.set(data);
mono_wasm_add_assembly(loadAsName, heapAddress, data.length);
MONO.loaded_files.push(toAbsoluteUrl(dependency.url));
} catch (errorInfo) {
onError(errorInfo);
return;
}
removeRunDependency(runDependencyId);
}
}
const anchorTagForAbsoluteUrlConversions = document.createElement('a');
@ -221,38 +336,20 @@ function toAbsoluteUrl(possiblyRelativeUrl: string) {
return anchorTagForAbsoluteUrlConversions.href;
}
function asyncLoad(url: string) {
return new Promise<Uint8Array>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, /* async: */ true);
xhr.responseType = 'arraybuffer';
xhr.onload = function xhr_onload() {
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
const asm = new Uint8Array(xhr.response);
resolve(asm);
} else {
reject(xhr);
}
};
xhr.onerror = reject;
xhr.send(undefined);
});
}
function getArrayDataPointer<T>(array: System_Array<T>): number {
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
}
function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any {
function bindStaticMethod(assembly: string, typeName: string, method: string) {
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
const fqn = `[${assembly}] ${typeName}:${method}`;
return Module.mono_bind_static_method(fqn);
return BINDING.bind_static_method(fqn);
}
function attachInteropInvoker(): void {
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet');
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS');
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'InvokeDotNet');
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'BeginInvokeDotNet');
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'EndInvokeJS');
DotNet.attachDispatcher({
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
@ -287,3 +384,45 @@ function attachInteropInvoker(): void {
},
});
}
async function loadTimezone(timeZoneResource: LoadingResource) : Promise<void> {
const runDependencyId = `blazor:timezonedata`;
addRunDependency(runDependencyId);
const request = await timeZoneResource.response;
const arrayBuffer = await request.arrayBuffer();
loadTimezoneData(arrayBuffer)
removeRunDependency(runDependencyId);
}
async function compileWasmModule(wasmResource: LoadingResource, imports: any): Promise<WebAssembly.Instance> {
// This is the same logic as used in emscripten's generated js. We can't use emscripten's js because
// it doesn't provide any method for supplying a custom response provider, and we want to integrate
// with our resource loader cache.
if (typeof WebAssembly['instantiateStreaming'] === 'function') {
try {
const streamingResult = await WebAssembly['instantiateStreaming'](wasmResource.response, imports);
return streamingResult.instance;
}
catch (ex) {
console.info('Streaming compilation failed. Falling back to ArrayBuffer instantiation. ', ex);
}
}
// If that's not available or fails (e.g., due to incorrect content-type header),
// fall back to ArrayBuffer instantiation
const arrayBuffer = await wasmResource.response.then(r => r.arrayBuffer());
const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, imports);
return arrayBufferResult.instance;
}
function changeExtension(filename: string, newExtensionWithLeadingDot: string) {
const lastDotIndex = filename.lastIndexOf('.');
if (lastDotIndex < 0) {
throw new Error(`No extension to replace in '${filename}'`);
}
return filename.substr(0, lastDotIndex) + newExtensionWithLeadingDot;
}

View File

@ -1,34 +0,0 @@
declare namespace Module {
function UTF8ToString(utf8: Mono.Utf8Ptr): string;
var preloadPlugins: any[];
function stackSave(): Mono.StackSaveHandle;
function stackAlloc(length: number): number;
function stackRestore(handle: Mono.StackSaveHandle): void;
// These should probably be in @types/emscripten
function FS_createPath(parent, path, canRead, canWrite);
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
function mono_bind_static_method(fqn: string): BoundStaticMethod;
}
// Emscripten declares these globals
declare const addRunDependency: any;
declare const removeRunDependency: any;
declare namespace Mono {
interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any }
interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any }
}
// Mono uses this global to hang various debugging-related items on
declare namespace MONO {
var loaded_files: string[];
var mono_wasm_runtime_is_ready: boolean;
function mono_wasm_setenv (name: string, value: string): void;
}
// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are
// artifically limiting it to a subset of types that we actually use.
declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null);

View File

@ -0,0 +1,28 @@
import { Pointer, System_String, System_Array, System_Object } from '../Platform';
// Mono uses this global to hang various debugging-related items on
declare interface MONO {
loaded_files: string[];
mono_wasm_runtime_ready (): void;
mono_wasm_setenv (name: string, value: string): void;
}
// Mono uses this global to hold low-level interop APIs
declare interface BINDING {
mono_obj_array_new(length: number): System_Array<System_Object>;
mono_obj_array_set(array: System_Array<System_Object>, index: Number, value: System_Object): void;
js_string_to_mono_string(jsString: string): System_String;
js_typed_array_to_array(array: Uint8Array): System_Object;
js_to_mono_obj(jsObject: any) : System_Object;
mono_array_to_js_array<TInput, TOutput>(array: System_Array<TInput>) : Array<TOutput>;
conv_string(dotnetString: System_String | null): string | null;
bind_static_method(fqn: string, signature?: string): Function;
unbox_mono_obj(object: System_Object): any;
}
declare global {
var MONO: MONO;
var BINDING: BINDING;
}

View File

@ -0,0 +1,43 @@
import { readInt32LE } from "../../BinaryDecoder";
import { decodeUtf8 } from "../../Utf8Decoder";
export function loadTimezoneData(arrayBuffer: ArrayBuffer) {
let remainingData = new Uint8Array(arrayBuffer);
// The timezone file is generated by https://github.com/dotnet/blazor/tree/master/src/TimeZoneData.
// The file format of the TZ file look like so
//
// [4 - byte length of manifest]
// [json manifest]
// [data bytes]
//
// The json manifest is an array that looks like so:
//
// [...["America/Fort_Nelson",2249],["America/Glace_Bay",2206]..]
//
// where the first token in each array is the relative path of the file on disk, and the second is the
// length of the file. The starting offset of a file can be calculated using the lengths of all files
// that appear prior to it.
const manifestSize = readInt32LE(remainingData, 0);
remainingData = remainingData.slice(4);
const manifestContent = decodeUtf8(remainingData.slice(0, manifestSize));
const manifest = JSON.parse(manifestContent) as ManifestEntry[];
remainingData = remainingData.slice(manifestSize);
// Create the folder structure
// /zoneinfo
// /zoneinfo/Africa
// /zoneinfo/Asia
// ..
Module['FS_createPath']('/', 'zoneinfo', true, true);
new Set(manifest.map(m => m[0].split('/')![0])).forEach(folder =>
Module['FS_createPath']('/zoneinfo', folder, true, true));
for (const [name, length] of manifest) {
const bytes = remainingData.slice(0, length);
Module['FS_createDataFile'](`/zoneinfo/${name}`, null, bytes, true, true, true);
remainingData = remainingData.slice(length);
}
}
type ManifestEntry = [string, number];

View File

@ -1,9 +1,10 @@
import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader';
export interface Platform {
start(loadAssemblyUrls: string[]): Promise<void>;
start(resourceLoader: WebAssemblyResourceLoader): Promise<void>;
callEntryPoint(assemblyName: string): void;
toJavaScriptString(dotNetString: System_String): string;
toUint8Array(array: System_Array<any>): Uint8Array;
getArrayLength(array: System_Array<any>): number;
@ -15,7 +16,7 @@ export interface Platform {
readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T;
readStringField(baseAddress: Pointer, fieldOffset?: number): string | null;
readStringField(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null;
readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T;
}

View File

@ -1,11 +0,0 @@
export function getFileNameFromUrl(url: string) {
// This could also be called "get last path segment from URL", but the primary
// use case is to extract things that look like filenames
const lastSegment = url.substring(url.lastIndexOf('/') + 1);
const queryStringStartPos = lastSegment.indexOf('?');
return queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
}
export function getAssemblyNameFromUrl(url: string) {
return getFileNameFromUrl(url).replace(/\.dll$/, '');
}

View File

@ -0,0 +1,28 @@
import { BootConfigResult } from './BootConfig';
import { System_String, System_Object } from './Platform';
export class WebAssemblyConfigLoader {
static async initAsync(bootConfigResult: BootConfigResult): Promise<void> {
window['Blazor']._internal.getApplicationEnvironment = () => BINDING.js_string_to_mono_string(bootConfigResult.applicationEnvironment);
const configFiles = await Promise.all((bootConfigResult.bootConfig.config || [])
.filter(name => name === 'appsettings.json' || name === `appsettings.${bootConfigResult.applicationEnvironment}.json`)
.map(async name => ({ name, content: await getConfigBytes(name) })));
window['Blazor']._internal.getConfig = (dotNetFileName: System_String) : System_Object | undefined => {
const fileName = BINDING.conv_string(dotNetFileName);
const resolvedFile = configFiles.find(f => f.name === fileName);
return resolvedFile ? BINDING.js_typed_array_to_array(resolvedFile.content) : undefined;
};
async function getConfigBytes(file: string): Promise<Uint8Array> {
const response = await fetch(file, {
method: 'GET',
credentials: 'include',
cache: 'no-cache'
});
return new Uint8Array(await response.arrayBuffer());
}
}
}

View File

@ -0,0 +1,198 @@
import { toAbsoluteUri } from '../Services/NavigationManager';
import { BootJsonData, ResourceList } from './BootConfig';
import { WebAssemblyStartOptions, WebAssemblyBootResourceType } from './WebAssemblyStartOptions';
const networkFetchCacheMode = 'no-cache';
export class WebAssemblyResourceLoader {
private usedCacheKeys: { [key: string]: boolean } = {};
private networkLoads: { [name: string]: LoadLogEntry } = {};
private cacheLoads: { [name: string]: LoadLogEntry } = {};
static async initAsync(bootConfig: BootJsonData, startOptions: Partial<WebAssemblyStartOptions>): Promise<WebAssemblyResourceLoader> {
const cache = await getCacheToUseIfEnabled(bootConfig);
return new WebAssemblyResourceLoader(bootConfig, cache, startOptions);
}
constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null, readonly startOptions: Partial<WebAssemblyStartOptions>) {
}
loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] {
return Object.keys(resources)
.map(name => this.loadResource(name, url(name), resources[name], resourceType));
}
loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource {
const response = this.cacheIfUsed
? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash, resourceType)
: this.loadResourceWithoutCaching(name, url, contentHash, resourceType);
return { name, url, response };
}
logToConsole() {
const cacheLoadsEntries = Object.values(this.cacheLoads);
const networkLoadsEntries = Object.values(this.networkLoads);
const cacheResponseBytes = countTotalBytes(cacheLoadsEntries);
const networkResponseBytes = countTotalBytes(networkLoadsEntries);
const totalResponseBytes = cacheResponseBytes + networkResponseBytes;
if (totalResponseBytes === 0) {
// We have no perf stats to display, likely because caching is not in use.
return;
}
const linkerDisabledWarning = this.bootConfig.linkerEnabled ? '%c' : '\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller.';
console.groupCollapsed(`%cblazor%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, 'background: purple; color: white; padding: 1px 3px; border-radius: 3px;', 'font-weight: bold;', 'font-weight: normal;');
if (cacheLoadsEntries.length) {
console.groupCollapsed(`Loaded ${toDataSizeString(cacheResponseBytes)} resources from cache`);
console.table(this.cacheLoads);
console.groupEnd();
}
if (networkLoadsEntries.length) {
console.groupCollapsed(`Loaded ${toDataSizeString(networkResponseBytes)} resources from network`);
console.table(this.networkLoads);
console.groupEnd();
}
console.groupEnd();
}
async purgeUnusedCacheEntriesAsync() {
// We want to keep the cache small because, even though the browser will evict entries if it
// gets too big, we don't want to be considered problematic by the end user viewing storage stats
const cache = this.cacheIfUsed;
if (cache) {
const cachedRequests = await cache.keys();
const deletionPromises = cachedRequests.map(async cachedRequest => {
if (!(cachedRequest.url in this.usedCacheKeys)) {
await cache.delete(cachedRequest);
}
});
await Promise.all(deletionPromises);
}
}
private async loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType) {
// Since we are going to cache the response, we require there to be a content hash for integrity
// checking. We don't want to cache bad responses. There should always be a hash, because the build
// process generates this data.
if (!contentHash || contentHash.length === 0) {
throw new Error('Content hash is required');
}
const cacheKey = toAbsoluteUri(`${url}.${contentHash}`);
this.usedCacheKeys[cacheKey] = true;
const cachedResponse = await cache.match(cacheKey);
if (cachedResponse) {
// It's in the cache.
const responseBytes = parseInt(cachedResponse.headers.get('content-length') || '0');
this.cacheLoads[name] = { responseBytes };
return cachedResponse;
} else {
// It's not in the cache. Fetch from network.
const networkResponse = await this.loadResourceWithoutCaching(name, url, contentHash, resourceType);
this.addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background
return networkResponse;
}
}
private loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise<Response> {
// Allow developers to override how the resource is loaded
if (this.startOptions.loadBootResource) {
const customLoadResult = this.startOptions.loadBootResource(resourceType, name, url, contentHash);
if (customLoadResult instanceof Promise) {
// They are supplying an entire custom response, so just use that
return customLoadResult;
} else if (typeof customLoadResult === 'string') {
// They are supplying a custom URL, so use that with the default fetch behavior
url = customLoadResult;
}
}
// Note that if cacheBootResources was explicitly disabled, we also bypass hash checking
// This is to give developers an easy opt-out from the entire caching/validation flow if
// there's anything they don't like about it.
return fetch(url, {
cache: networkFetchCacheMode,
integrity: this.bootConfig.cacheBootResources ? contentHash : undefined
});
}
private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) {
// We have to clone in order to put this in the cache *and* not prevent other code from
// reading the original response stream.
const responseData = await response.clone().arrayBuffer();
// Now is an ideal moment to capture the performance stats for the request, since it
// only just completed and is most likely to still be in the buffer. However this is
// only done on a 'best effort' basis. Even if we do receive an entry, some of its
// properties may be blanked out if it was a CORS request.
const performanceEntry = getPerformanceEntry(response.url);
const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined;
this.networkLoads[name] = { responseBytes };
// Add to cache as a custom response object so we can track extra data such as responseBytes
// We can't rely on the server sending content-length (ASP.NET Core doesn't by default)
await cache.put(cacheKey, new Response(responseData, {
headers: {
'content-type': response.headers.get('content-type') || '',
'content-length': (responseBytes || response.headers.get('content-length') || '').toString()
}
}));
}
}
async function getCacheToUseIfEnabled(bootConfig: BootJsonData): Promise<Cache | null> {
// caches will be undefined if we're running on an insecure origin (secure means https or localhost)
if (!bootConfig.cacheBootResources || typeof caches === 'undefined') {
return null;
}
// Define a separate cache for each base href, so we're isolated from any other
// Blazor application running on the same origin. We need this so that we're free
// to purge from the cache anything we're not using and don't let it keep growing,
// since we don't want to be worst offenders for space usage.
const relativeBaseHref = document.baseURI.substring(document.location.origin.length);
const cacheName = `blazor-resources-${relativeBaseHref}`;
try {
// There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when
// caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance.
// However, if the browser was launched with a --user-data-dir param that's "too long" in some sense,
// then even through the promise resolves as success, the value given is `undefined`.
// See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541
// If we see this happening, return "null" to mean "proceed without caching".
return (await caches.open(cacheName)) || null;
} catch {
// There's no known scenario where we should get an exception here, but considering the
// Chromium bug above, let's tolerate it and treat as "proceed without caching".
return null;
}
}
function countTotalBytes(loads: LoadLogEntry[]) {
return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0);
}
function toDataSizeString(byteCount: number) {
return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`;
}
function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined {
if (typeof performance !== 'undefined') {
return performance.getEntriesByName(url)[0] as PerformanceResourceTiming;
}
}
interface LoadLogEntry {
responseBytes: number | undefined;
}
export interface LoadingResource {
name: string;
url: string;
response: Promise<Response>;
}

Some files were not shown because too many files have changed in this diff Show More