Merge branch 'blazor-wasm' into prkrishn/merge-blazor-wasm
This commit is contained in:
commit
8efeefb3d1
|
|
@ -7,9 +7,9 @@ trigger:
|
||||||
batch: true
|
batch: true
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- blazor-wasm
|
|
||||||
- master
|
- master
|
||||||
- release/*
|
- release/*
|
||||||
|
- internal/release/3.*
|
||||||
|
|
||||||
# Run PR validation on all branches
|
# Run PR validation on all branches
|
||||||
pr:
|
pr:
|
||||||
|
|
@ -80,18 +80,11 @@ variables:
|
||||||
value: test
|
value: test
|
||||||
- name: _PublishArgs
|
- name: _PublishArgs
|
||||||
value: ''
|
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:
|
stages:
|
||||||
- stage: build
|
- stage: build
|
||||||
displayName: Build
|
displayName: Build
|
||||||
jobs:
|
jobs:
|
||||||
<<<<<<< HEAD
|
|
||||||
# Code check
|
# Code check
|
||||||
- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
|
- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
|
||||||
- template: jobs/default-build.yml
|
- template: jobs/default-build.yml
|
||||||
|
|
@ -116,8 +109,6 @@ stages:
|
||||||
publishOnError: true
|
publishOnError: true
|
||||||
includeForks: true
|
includeForks: true
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
|
|
||||||
# Build Windows (x64/x86)
|
# Build Windows (x64/x86)
|
||||||
- template: jobs/default-build.yml
|
- template: jobs/default-build.yml
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -151,13 +142,12 @@ stages:
|
||||||
-arch x64
|
-arch x64
|
||||||
-pack
|
-pack
|
||||||
-all
|
-all
|
||||||
-NoBuildNative
|
-buildNative
|
||||||
/bl:artifacts/log/build.x64.binlog
|
/bl:artifacts/log/build.x64.binlog
|
||||||
$(_BuildArgs)
|
$(_BuildArgs)
|
||||||
$(_InternalRuntimeDownloadArgs)
|
$(_InternalRuntimeDownloadArgs)
|
||||||
displayName: Build x64
|
displayName: Build x64
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
# Build the x86 shared framework
|
# Build the x86 shared framework
|
||||||
# TODO: make it possible to build for one Windows architecture at a time
|
# 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
|
# This is going to actually build x86 native assets. See https://github.com/aspnet/AspNetCore/issues/7196
|
||||||
|
|
@ -183,8 +173,6 @@ stages:
|
||||||
$(_InternalRuntimeDownloadArgs)
|
$(_InternalRuntimeDownloadArgs)
|
||||||
displayName: Build SiteExtension
|
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,
|
# 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.
|
# 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.
|
# 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
|
/p:PublishInstallerBaseVersion=true
|
||||||
displayName: Build Installers
|
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:
|
artifacts:
|
||||||
- name: Windows_Logs
|
- name: Windows_Logs
|
||||||
path: artifacts/log/
|
path: artifacts/log/
|
||||||
|
|
@ -220,7 +218,6 @@ stages:
|
||||||
- name: Windows_Packages
|
- name: Windows_Packages
|
||||||
path: artifacts/packages/
|
path: artifacts/packages/
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
# Build Windows ARM
|
# Build Windows ARM
|
||||||
- template: jobs/default-build.yml
|
- template: jobs/default-build.yml
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -515,8 +512,6 @@ stages:
|
||||||
parameters:
|
parameters:
|
||||||
inputName: Linux_musl_arm64
|
inputName: Linux_musl_arm64
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
|
|
||||||
# Test jobs
|
# Test jobs
|
||||||
- template: jobs/default-build.yml
|
- template: jobs/default-build.yml
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -661,7 +656,6 @@ stages:
|
||||||
publishOnError: true
|
publishOnError: true
|
||||||
includeForks: true
|
includeForks: true
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
# Source build
|
# Source build
|
||||||
- job: Source_Build
|
- job: Source_Build
|
||||||
displayName: 'Test: Linux Source Build'
|
displayName: 'Test: Linux Source Build'
|
||||||
|
|
@ -720,17 +714,23 @@ stages:
|
||||||
artifactType: Container
|
artifactType: Container
|
||||||
parallel: true
|
parallel: true
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
|
|
||||||
# Publish to the BAR
|
# Publish to the BAR
|
||||||
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
|
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
|
||||||
- template: /eng/common/templates/job/publish-build-assets.yml
|
- template: /eng/common/templates/job/publish-build-assets.yml
|
||||||
parameters:
|
parameters:
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Windows_build
|
- 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.
|
# In addition to the dependencies above, ensure the build was successful overall.
|
||||||
- Linux_Test
|
- Linux_Test
|
||||||
- MacOS_Test
|
- MacOS_Test
|
||||||
|
- Source_Build
|
||||||
- Windows_Templates_Test
|
- Windows_Templates_Test
|
||||||
- Windows_Test
|
- Windows_Test
|
||||||
pool:
|
pool:
|
||||||
|
|
|
||||||
22
NuGet.config
22
NuGet.config
|
|
@ -3,32 +3,20 @@
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
|
<!--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-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.-->
|
<!--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-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="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" 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="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="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="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="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
|
||||||
<add key="aspnet-extensions" value="https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json" />
|
<add key="dotnet3.1" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json" />
|
||||||
<add key="aspnet-entityframeworkcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/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="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="nuget.org" value="https://api.nuget.org/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" />
|
<add key="blazor-wasm" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json" />
|
||||||
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
|
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@
|
||||||
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
||||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||||
</PropertyGroup>
|
</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-->
|
<!-- Package: Microsoft.AspNetCore.ApiAuthorization.IdentityServer-->
|
||||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' ">
|
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' ">
|
||||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||||
|
|
@ -240,6 +247,48 @@
|
||||||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" />
|
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" />
|
||||||
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" />
|
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" />
|
||||||
</ItemGroup>
|
</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-->
|
<!-- Package: Microsoft.AspNetCore.ConcurrencyLimiter-->
|
||||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' ">
|
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' ">
|
||||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -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.x64" Version="3.0.3" />
|
||||||
<Package Id="AspNetCoreRuntime.3.0.x86" 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="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.ApiAuthorization.IdentityServer" Version="3.1.4" />
|
||||||
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" 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" />
|
<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.Authorization" Version="3.1.4" />
|
||||||
<Package Id="Microsoft.AspNetCore.Components.Forms" 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.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.ConcurrencyLimiter" Version="3.1.4" />
|
||||||
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
|
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
|
||||||
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" />
|
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" />
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@
|
||||||
$(RepoRoot)src\Installers\**\*.*proj;
|
$(RepoRoot)src\Installers\**\*.*proj;
|
||||||
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
|
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
|
||||||
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
|
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
|
||||||
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
|
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
|
||||||
$(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj;
|
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
|
||||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
||||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
|
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
|
||||||
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
|
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
|
||||||
|
|
@ -50,11 +50,6 @@
|
||||||
" />
|
" />
|
||||||
</ItemGroup>
|
</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>
|
<Choose>
|
||||||
<!-- Project selection can be overridden on the command line by passing in -projects -->
|
<!-- Project selection can be overridden on the command line by passing in -projects -->
|
||||||
<When Condition="'$(ProjectToBuild)' != ''">
|
<When Condition="'$(ProjectToBuild)' != ''">
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ and are generated based on the last package release.
|
||||||
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
|
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
|
||||||
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
|
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
|
||||||
<LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
|
<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.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
|
||||||
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
||||||
<LatestPackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
|
<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.1" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion)" />
|
||||||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
|
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
|
||||||
<LatestPackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
<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.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
|
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
|
||||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />
|
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -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.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.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.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="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" 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.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" />
|
<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" />
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,13 @@
|
||||||
-->
|
-->
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
<ProductDependencies>
|
<ProductDependencies>
|
||||||
<Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.1.0-preview4.19605.1" Pinned="true">
|
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0">
|
||||||
<Uri>https://github.com/aspnet/Blazor</Uri>
|
<Uri>https://github.com/dotnet/blazor</Uri>
|
||||||
<Sha>7868699de745fd30a654c798a99dc541b77b95c0</Sha>
|
<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>
|
||||||
<Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.4">
|
<Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.4">
|
||||||
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore-tooling</Uri>
|
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore-tooling</Uri>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@
|
||||||
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
|
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
|
||||||
<AspNetCorePatchVersion>5</AspNetCorePatchVersion>
|
<AspNetCorePatchVersion>5</AspNetCorePatchVersion>
|
||||||
<PreReleasePreviewNumber>0</PreReleasePreviewNumber>
|
<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
|
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>
|
<IncludePreReleaseLabelInPackageVersion Condition=" '$(DotNetFinalVersionKind)' == 'release' ">false</IncludePreReleaseLabelInPackageVersion>
|
||||||
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
|
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
|
||||||
<PreReleaseBrandingLabel>Servicing</PreReleaseBrandingLabel>
|
<PreReleaseBrandingLabel>Servicing</PreReleaseBrandingLabel>
|
||||||
<!-- Blazor Client packages will not RTM with 3.1 -->
|
|
||||||
<BlazorClientPreReleasePreviewNumber>4</BlazorClientPreReleasePreviewNumber>
|
|
||||||
<BlazorClientPreReleaseVersionLabel>preview$(BlazorClientPreReleasePreviewNumber)</BlazorClientPreReleaseVersionLabel>
|
|
||||||
<AspNetCoreMajorMinorVersion>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)</AspNetCoreMajorMinorVersion>
|
<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. -->
|
<!-- 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>
|
<MicrosoftInternalExtensionsRefsPackageOverrideVersion>3.1.0</MicrosoftInternalExtensionsRefsPackageOverrideVersion>
|
||||||
|
|
@ -30,6 +32,7 @@
|
||||||
<!-- Servicing builds have different characteristics for the way dependencies, framework references, and versions are handled. -->
|
<!-- Servicing builds have different characteristics for the way dependencies, framework references, and versions are handled. -->
|
||||||
<IsServicingBuild Condition=" '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
|
<IsServicingBuild Condition=" '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
|
||||||
<VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix>
|
<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 is used by projects, like .deb and .rpm, which use slightly different version formats. -->
|
||||||
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
|
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
|
||||||
<!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. -->
|
<!-- 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>
|
<SystemServiceProcessServiceControllerPackageVersion>4.7.0</SystemServiceProcessServiceControllerPackageVersion>
|
||||||
<SystemTextEncodingsWebPackageVersion>4.7.1</SystemTextEncodingsWebPackageVersion>
|
<SystemTextEncodingsWebPackageVersion>4.7.1</SystemTextEncodingsWebPackageVersion>
|
||||||
<SystemTextJsonPackageVersion>4.7.2</SystemTextJsonPackageVersion>
|
<SystemTextJsonPackageVersion>4.7.2</SystemTextJsonPackageVersion>
|
||||||
|
<SystemNetHttpJsonPackageVersion>3.2.0</SystemNetHttpJsonPackageVersion>
|
||||||
<SystemThreadingChannelsPackageVersion>4.7.1</SystemThreadingChannelsPackageVersion>
|
<SystemThreadingChannelsPackageVersion>4.7.1</SystemThreadingChannelsPackageVersion>
|
||||||
<SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion>
|
<SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion>
|
||||||
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
||||||
<MicrosoftNETCorePlatformsPackageVersion>3.1.1</MicrosoftNETCorePlatformsPackageVersion>
|
<MicrosoftNETCorePlatformsPackageVersion>3.1.1</MicrosoftNETCorePlatformsPackageVersion>
|
||||||
<!-- Packages from aspnet/Blazor -->
|
<!-- Packages from aspnet/Blazor -->
|
||||||
<MicrosoftAspNetCoreBlazorMonoPackageVersion>3.1.0-preview4.19605.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
|
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
|
||||||
<!-- Packages from aspnet/Extensions -->
|
<!-- Packages from aspnet/Extensions -->
|
||||||
<InternalAspNetCoreAnalyzersPackageVersion>3.1.4-servicing.20221.11</InternalAspNetCoreAnalyzersPackageVersion>
|
<InternalAspNetCoreAnalyzersPackageVersion>3.1.4-servicing.20221.11</InternalAspNetCoreAnalyzersPackageVersion>
|
||||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.4-servicing.20221.11</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.4-servicing.20221.11</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
|
||||||
if ($msbuildCmd -ne $null) {
|
if ($msbuildCmd -ne $null) {
|
||||||
# Workaround for https://github.com/dotnet/roslyn/issues/35793
|
# 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+
|
# 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) {
|
if ($msbuildVersion -ge $vsMinVersion) {
|
||||||
return $global:_MSBuildExe = $msbuildCmd.Path
|
return $global:_MSBuildExe = $msbuildCmd.Path
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,10 @@ try {
|
||||||
& $PSScriptRoot\GenerateReferenceAssemblies.ps1 -ci:$ci
|
& $PSScriptRoot\GenerateReferenceAssemblies.ps1 -ci:$ci
|
||||||
}
|
}
|
||||||
|
|
||||||
# Temporarily disable package baseline generation while we stage for publishing
|
Write-Host "Re-generating package baselines"
|
||||||
# Write-Host "Re-generating package baselines"
|
Invoke-Block {
|
||||||
# Invoke-Block {
|
& dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/"
|
||||||
# & dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/"
|
}
|
||||||
# }
|
|
||||||
|
|
||||||
Write-Host "Run git diff to check for pending changes"
|
Write-Host "Run git diff to check for pending changes"
|
||||||
|
|
||||||
|
|
|
||||||
12
global.json
12
global.json
|
|
@ -1,16 +1,9 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
<<<<<<< HEAD
|
|
||||||
"version": "3.1.103"
|
"version": "3.1.103"
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet": "3.1.103",
|
"dotnet": "3.1.103",
|
||||||
=======
|
|
||||||
"version": "3.1.100"
|
|
||||||
},
|
|
||||||
"tools": {
|
|
||||||
"dotnet": "3.1.100",
|
|
||||||
>>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61
|
|
||||||
"runtimes": {
|
"runtimes": {
|
||||||
"dotnet/x64": [
|
"dotnet/x64": [
|
||||||
"$(MicrosoftNETCoreAppInternalPackageVersion)"
|
"$(MicrosoftNETCoreAppInternalPackageVersion)"
|
||||||
|
|
@ -32,12 +25,7 @@
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Yarn.MSBuild": "1.15.2",
|
"Yarn.MSBuild": "1.15.2",
|
||||||
<<<<<<< HEAD
|
|
||||||
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20213.4",
|
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20213.4",
|
||||||
"Microsoft.DotNet.Helix.Sdk": "2.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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- Override version labels -->
|
|
||||||
<VersionPrefix>3.2.0</VersionPrefix>
|
|
||||||
<PreReleaseVersionLabel>preview1</PreReleaseVersionLabel>
|
|
||||||
<DotNetFinalVersionKind />
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
|
|
||||||
</Project>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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(@"<span>Hi</span>");
|
|
||||||
|
|
||||||
// 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><span>Hi</span></div>");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var frames = GetRenderTree(component);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
frames,
|
|
||||||
frame => AssertFrame.Markup(frame, "<div><span>Hi</span></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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -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 { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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/
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -9,19 +9,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
|
||||||
EndProject
|
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}"
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -206,10 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
|
||||||
EndProject
|
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}"
|
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
|
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}"
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{08791FEE-761D-40EF-B701-1D31FD1E6E53}"
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
|
||||||
EndProject
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
|
||||||
EndProject
|
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
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.ActiveCfg = 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
|
{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.ActiveCfg = Debug|Any CPU
|
||||||
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.Build.0 = 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
|
{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|x64.Build.0 = Release|Any CPU
|
||||||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = 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
|
{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.ActiveCfg = Debug|Any CPU
|
||||||
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = 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
|
{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|x64.Build.0 = Release|Any CPU
|
||||||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -1555,12 +1605,12 @@ Global
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
|
{ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
|
||||||
{F000C49D-3857-42A4-918D-DA4C08691FE2} = {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}
|
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
|
||||||
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
|
||||||
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
|
||||||
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
|
||||||
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
|
||||||
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
|
||||||
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||||
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
|
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
|
||||||
{C1E2C117-BE47-4E29-94B3-753262D97A5C} = {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}
|
{DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
|
||||||
{0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {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}
|
{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}
|
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373}
|
||||||
{956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
|
{956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
|
||||||
{B13CDE69-ED22-4664-AAD7-686ED8CD5E88} = {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}
|
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
|
||||||
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
|
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
|
||||||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {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}
|
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
|
||||||
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
|
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
|
||||||
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
|
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
|
||||||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {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
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}
|
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,6 @@
|
||||||
"Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj",
|
"Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj",
|
||||||
"Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
|
"Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
|
||||||
"Authorization\\test\\Microsoft.AspNetCore.Components.Authorization.Tests.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\\perf\\Microsoft.AspNetCore.Components.Performance.csproj",
|
||||||
"Components\\src\\Microsoft.AspNetCore.Components.csproj",
|
"Components\\src\\Microsoft.AspNetCore.Components.csproj",
|
||||||
"Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj",
|
"Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj",
|
||||||
|
|
@ -32,12 +16,27 @@
|
||||||
"Samples\\BlazorServerApp\\BlazorServerApp.csproj",
|
"Samples\\BlazorServerApp\\BlazorServerApp.csproj",
|
||||||
"Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
|
"Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
|
||||||
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.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\\src\\Microsoft.AspNetCore.Components.Web.csproj",
|
||||||
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
|
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
|
||||||
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
|
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
|
||||||
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
|
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
|
||||||
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
|
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
|
||||||
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
|
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
|
||||||
|
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
||||||
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
||||||
"test\\testassets\\TestServer\\Components.TestServer.csproj"
|
"test\\testassets\\TestServer\\Components.TestServer.csproj"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<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
|
<PropertyGroup>
|
||||||
target the latest patch before a preview release. -->
|
<PackageTags>aspnetcore;components</PackageTags>
|
||||||
<LatestAspNetCoreReferenceVersion>3.1.0</LatestAspNetCoreReferenceVersion>
|
|
||||||
|
|
||||||
<ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot>
|
<ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,6 @@
|
||||||
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
|
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</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>
|
<ItemGroup>
|
||||||
<!-- Add a project dependency without reference output assemblies to enforce build order -->
|
<!-- 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 -->
|
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
|
||||||
|
|
@ -35,7 +15,29 @@
|
||||||
Private="false" />
|
Private="false" />
|
||||||
</ItemGroup>
|
</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" />
|
<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>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
|
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
||||||
<Reference Include="Microsoft.Extensions.Hosting" />
|
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -17,19 +17,19 @@
|
||||||
"@aspnet/signalr": "link:../../SignalR/clients/ts/signalr",
|
"@aspnet/signalr": "link:../../SignalR/clients/ts/signalr",
|
||||||
"@aspnet/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack",
|
"@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",
|
"@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/emscripten": "^1.39.3",
|
||||||
"@types/jest": "^24.0.6",
|
"@types/jest": "^24.9.1",
|
||||||
"@types/jsdom": "11.0.6",
|
"@types/jsdom": "11.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^1.5.0",
|
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||||
"@typescript-eslint/parser": "^1.5.0",
|
"@typescript-eslint/parser": "^1.13.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.9.0",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.7.1",
|
||||||
"ts-jest": "^24.0.0",
|
"ts-jest": "^24.3.0",
|
||||||
"ts-loader": "^4.4.1",
|
"ts-loader": "^4.4.1",
|
||||||
"typescript": "^3.5.3",
|
"typescript": "^3.8.3",
|
||||||
"webpack": "^4.36.1",
|
"webpack": "^4.42.1",
|
||||||
"webpack-cli": "^3.3.6"
|
"webpack-cli": "^3.3.11"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"**/set-value": "^2.0.1"
|
"**/set-value": "^2.0.1"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -9,14 +9,14 @@ import { ConsoleLogger } from './Platform/Logging/Loggers';
|
||||||
import { LogLevel, Logger } from './Platform/Logging/Logger';
|
import { LogLevel, Logger } from './Platform/Logging/Logger';
|
||||||
import { discoverComponents, CircuitDescriptor } from './Platform/Circuits/CircuitManager';
|
import { discoverComponents, CircuitDescriptor } from './Platform/Circuits/CircuitManager';
|
||||||
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
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 { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
|
||||||
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
|
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
|
||||||
|
|
||||||
let renderingFailed = false;
|
let renderingFailed = false;
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> {
|
async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
|
||||||
if (started) {
|
if (started) {
|
||||||
throw new Error('Blazor has already 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.');
|
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();
|
const hubProtocol = new MessagePackHubProtocol();
|
||||||
(hubProtocol as unknown as { name: string }).name = 'blazorpack';
|
(hubProtocol as unknown as { name: string }).name = 'blazorpack';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,24 @@ import * as Environment from './Environment';
|
||||||
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
||||||
import { renderBatch } from './Rendering/Renderer';
|
import { renderBatch } from './Rendering/Renderer';
|
||||||
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
||||||
import { Pointer } from './Platform/Platform';
|
|
||||||
import { shouldAutoStart } from './BootCommon';
|
import { shouldAutoStart } from './BootCommon';
|
||||||
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
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;
|
let started = false;
|
||||||
|
|
||||||
async function boot(options?: any): Promise<void> {
|
async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
||||||
|
|
||||||
if (started) {
|
if (started) {
|
||||||
throw new Error('Blazor has already started.');
|
throw new Error('Blazor has already started.');
|
||||||
}
|
}
|
||||||
started = true;
|
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
|
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
|
||||||
const platform = Environment.setPlatform(monoPlatform);
|
const platform = Environment.setPlatform(monoPlatform);
|
||||||
|
|
@ -29,51 +33,39 @@ async function boot(options?: any): Promise<void> {
|
||||||
// Configure navigation via JS Interop
|
// Configure navigation via JS Interop
|
||||||
window['Blazor']._internal.navigationManager.listenForNavigationEvents(async (uri: string, intercepted: boolean): Promise<void> => {
|
window['Blazor']._internal.navigationManager.listenForNavigationEvents(async (uri: string, intercepted: boolean): Promise<void> => {
|
||||||
await DotNet.invokeMethodAsync(
|
await DotNet.invokeMethodAsync(
|
||||||
'Microsoft.AspNetCore.Blazor',
|
'Microsoft.AspNetCore.Components.WebAssembly',
|
||||||
'NotifyLocationChanged',
|
'NotifyLocationChanged',
|
||||||
uri,
|
uri,
|
||||||
intercepted
|
intercepted
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the boot JSON file
|
// Fetch the resources and prepare the Mono runtime
|
||||||
const bootConfig = await fetchBootConfigAsync();
|
const bootConfigResult = await BootConfigResult.initAsync();
|
||||||
|
|
||||||
if (!bootConfig.linkerEnabled) {
|
const [resourceLoader] = await Promise.all([
|
||||||
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');
|
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, options || {}),
|
||||||
}
|
WebAssemblyConfigLoader.initAsync(bootConfigResult)]);
|
||||||
|
|
||||||
// Determine the URLs of the assemblies we want to load, then begin fetching them all
|
|
||||||
const loadAssemblyUrls = bootConfig.assemblies
|
|
||||||
.map(filename => `_framework/_bin/${filename}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await platform.start(loadAssemblyUrls);
|
await platform.start(resourceLoader);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw new Error(`Failed to start platform. Reason: ${ex}`);
|
throw new Error(`Failed to start platform. Reason: ${ex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start up the application
|
// Start up the application
|
||||||
platform.callEntryPoint(bootConfig.entryAssembly);
|
platform.callEntryPoint(resourceLoader.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window['Blazor'].start = boot;
|
window['Blazor'].start = boot;
|
||||||
if (shouldAutoStart()) {
|
if (shouldAutoStart()) {
|
||||||
boot().catch(error => {
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { LogLevel } from '../Logging/Logger';
|
import { LogLevel } from '../Logging/Logger';
|
||||||
|
|
||||||
export interface BlazorOptions {
|
export interface CircuitStartOptions {
|
||||||
configureSignalR: (builder: signalR.HubConnectionBuilder) => void;
|
configureSignalR: (builder: signalR.HubConnectionBuilder) => void;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
reconnectionOptions: ReconnectionOptions;
|
reconnectionOptions: ReconnectionOptions;
|
||||||
reconnectionHandler?: ReconnectionHandler;
|
reconnectionHandler?: ReconnectionHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveOptions(userOptions?: Partial<BlazorOptions>): BlazorOptions {
|
export function resolveOptions(userOptions?: Partial<CircuitStartOptions>): CircuitStartOptions {
|
||||||
const result = { ...defaultOptions, ...userOptions };
|
const result = { ...defaultOptions, ...userOptions };
|
||||||
|
|
||||||
// The spread operator can't be used for a deep merge, so do the same for subproperties
|
// 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;
|
onConnectionUp(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: BlazorOptions = {
|
const defaultOptions: CircuitStartOptions = {
|
||||||
configureSignalR: (_) => { },
|
configureSignalR: (_) => { },
|
||||||
logLevel: LogLevel.Warning,
|
logLevel: LogLevel.Warning,
|
||||||
reconnectionOptions: {
|
reconnectionOptions: {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReconnectionHandler, ReconnectionOptions } from './BlazorOptions';
|
import { ReconnectionHandler, ReconnectionOptions } from './CircuitStartOptions';
|
||||||
import { ReconnectDisplay } from './ReconnectDisplay';
|
import { ReconnectDisplay } from './ReconnectDisplay';
|
||||||
import { DefaultReconnectDisplay } from './DefaultReconnectDisplay';
|
import { DefaultReconnectDisplay } from './DefaultReconnectDisplay';
|
||||||
import { UserSpecifiedDisplay } from './UserSpecifiedDisplay';
|
import { UserSpecifiedDisplay } from './UserSpecifiedDisplay';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
|
import { WebAssemblyResourceLoader } from '../WebAssemblyResourceLoader';
|
||||||
|
|
||||||
const currentBrowserIsChrome = (window as any).chrome
|
const currentBrowserIsChrome = (window as any).chrome
|
||||||
&& navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome
|
&& navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome
|
||||||
|
|
@ -9,9 +9,8 @@ export function hasDebuggingEnabled() {
|
||||||
return hasReferencedPdbs && currentBrowserIsChrome;
|
return hasReferencedPdbs && currentBrowserIsChrome;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
|
export function attachDebuggerHotkey(resourceLoader: WebAssemblyResourceLoader) {
|
||||||
hasReferencedPdbs = loadAssemblyUrls
|
hasReferencedPdbs = !!resourceLoader.bootConfig.resources.pdb;
|
||||||
.some(url => /\.pdb$/.test(getFileNameFromUrl(url)));
|
|
||||||
|
|
||||||
// Use the combination shift+alt+D because it isn't used by the major browsers
|
// Use the combination shift+alt+D because it isn't used by the major browsers
|
||||||
// for anything else by default
|
// for anything else by default
|
||||||
|
|
@ -26,7 +25,7 @@ export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
|
||||||
if (!hasReferencedPdbs) {
|
if (!hasReferencedPdbs) {
|
||||||
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
|
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
|
||||||
} else if (!currentBrowserIsChrome) {
|
} 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 {
|
} else {
|
||||||
launchDebugger();
|
launchDebugger();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||||
import { showErrorNotification } from '../../BootErrors';
|
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 appBinDirName = 'appBinDir';
|
||||||
const uint64HighOrderShift = Math.pow(2, 32);
|
const uint64HighOrderShift = Math.pow(2, 32);
|
||||||
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
|
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 = {
|
export const monoPlatform: Platform = {
|
||||||
start: function start(loadAssemblyUrls: string[]) {
|
start: function start(resourceLoader: WebAssemblyResourceLoader) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
attachDebuggerHotkey(loadAssemblyUrls);
|
attachDebuggerHotkey(resourceLoader);
|
||||||
|
|
||||||
// dotnet.js assumes the existence of this
|
// dotnet.js assumes the existence of this
|
||||||
window['Browser'] = {
|
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
|
// For compatibility with macOS Catalina, we have to assign a temporary value to window.Module
|
||||||
// before we start loading the WebAssembly files
|
// before we start loading the WebAssembly files
|
||||||
addGlobalModuleScriptTagsToDocument(() => {
|
addGlobalModuleScriptTagsToDocument(() => {
|
||||||
window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject);
|
window['Module'] = createEmscriptenModuleInstance(resourceLoader, resolve, reject);
|
||||||
addScriptTagsToDocument();
|
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
|
// 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
|
// outer promise reflects the startup process, and the inner one reflects the possibly-async
|
||||||
// .NET entrypoint method.
|
// .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.
|
// Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved.
|
||||||
invokeEntrypoint(assemblyName, null);
|
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 {
|
toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
|
||||||
const dataPtr = getArrayDataPointer(array);
|
const dataPtr = getArrayDataPointer(array);
|
||||||
const length = Module.getValue(dataPtr, 'i32');
|
const length = getValueI32(dataPtr);
|
||||||
return new Uint8Array(Module.HEAPU8.buffer, dataPtr + 4, length);
|
return new Uint8Array(Module.HEAPU8.buffer, dataPtr + 4, length);
|
||||||
},
|
},
|
||||||
|
|
||||||
getArrayLength: function getArrayLength(array: System_Array<any>): number {
|
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 {
|
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 {
|
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 {
|
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 {
|
readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number {
|
||||||
// Module.getValue(..., 'i64') doesn't work because the implementation treats 'i64' as
|
return getValueU64((baseAddress as any as number) + (fieldOffset || 0));
|
||||||
// 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];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number {
|
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 {
|
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 {
|
readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null {
|
||||||
const fieldValue = Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32');
|
const fieldValue = getValueI32((baseAddress as any as number) + (fieldOffset || 0));
|
||||||
return fieldValue === 0 ? null : monoPlatform.toJavaScriptString(fieldValue as any as System_String);
|
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 {
|
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;
|
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
|
||||||
if (!browserSupportsNativeWebAssembly) {
|
if (!browserSupportsNativeWebAssembly) {
|
||||||
throw new Error('This browser does not support WebAssembly.');
|
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');
|
const scriptElem = document.createElement('script');
|
||||||
scriptElem.src = '_framework/wasm/dotnet.js';
|
scriptElem.src = `_framework/wasm/${dotnetJsResourceName}`;
|
||||||
scriptElem.defer = true;
|
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);
|
document.body.appendChild(scriptElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,79 +183,151 @@ function addGlobalModuleScriptTagsToDocument(callback: () => void) {
|
||||||
document.body.appendChild(scriptElem);
|
document.body.appendChild(scriptElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () => void, onError: (reason?: any) => void) {
|
function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader, onReady: () => void, onError: (reason?: any) => void) {
|
||||||
const module = {} as typeof Module;
|
const resources = resourceLoader.bootConfig.resources;
|
||||||
const wasmBinaryFile = '_framework/wasm/dotnet.wasm';
|
const module = (window['Module'] || { }) as typeof Module;
|
||||||
const suppressMessages = ['DEBUGGING ENABLED'];
|
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 => {
|
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();
|
showErrorNotification();
|
||||||
};
|
};
|
||||||
module.preRun = [];
|
module.preRun = module.preRun || [];
|
||||||
module.postRun = [];
|
module.postRun = module.postRun || [];
|
||||||
module.preloadPlugins = [];
|
(module as any).preloadPlugins = [];
|
||||||
|
|
||||||
module.locateFile = fileName => {
|
// Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel.
|
||||||
switch (fileName) {
|
const dotnetWasmResourceName = 'dotnet.wasm';
|
||||||
case 'dotnet.wasm': return wasmBinaryFile;
|
const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/_bin/${filename}`, 'assembly');
|
||||||
default: return fileName;
|
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(() => {
|
module.preRun.push(() => {
|
||||||
// By now, emscripten should be initialised enough that we can capture these methods for later use
|
// 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, [
|
mono_wasm_add_assembly = cwrap('mono_wasm_add_assembly', null, ['string', 'number', 'number']);
|
||||||
'string',
|
mono_string_get_utf8 = cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
|
||||||
'number',
|
|
||||||
'number',
|
|
||||||
]);
|
|
||||||
|
|
||||||
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
|
|
||||||
|
|
||||||
MONO.loaded_files = [];
|
MONO.loaded_files = [];
|
||||||
|
|
||||||
loadAssemblyUrls.forEach(url => {
|
if (timeZoneResource) {
|
||||||
const filename = getFileNameFromUrl(url);
|
loadTimezone(timeZoneResource);
|
||||||
const runDependencyId = `blazor:${filename}`;
|
}
|
||||||
addRunDependency(runDependencyId);
|
|
||||||
asyncLoad(url).then(
|
// Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded
|
||||||
data => {
|
// Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless
|
||||||
const heapAddress = Module._malloc(data.length);
|
// of the extensions in the URLs. This allows loading assemblies with arbitrary filenames.
|
||||||
const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress, data.length);
|
assembliesBeingLoaded.forEach(r => addResourceAsAssembly(r, changeExtension(r.name, '.dll')));
|
||||||
heapMemory.set(data);
|
pdbsBeingLoaded.forEach(r => addResourceAsAssembly(r, r.name));
|
||||||
mono_wasm_add_assembly(filename, heapAddress, data.length);
|
|
||||||
MONO.loaded_files.push(toAbsoluteUrl(url));
|
window['Blazor']._internal.dotNetCriticalError = (message: System_String) => {
|
||||||
removeRunDependency(runDependencyId);
|
module.printErr(BINDING.conv_string(message) || '(null)');
|
||||||
},
|
};
|
||||||
errorInfo => {
|
|
||||||
// If it's a 404 on a .pdb, we don't want to block the app from starting up.
|
// Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application
|
||||||
// We'll just skip that file and continue (though the 404 is logged in the console).
|
// startup sequence to load satellite assemblies for the application's culture.
|
||||||
// This happens if you build a Debug build but then run in Production environment.
|
window['Blazor']._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray: System_Array<System_String>) : System_Object => {
|
||||||
const isPdb404 = errorInfo instanceof XMLHttpRequest
|
const culturesToLoad = BINDING.mono_array_to_js_array<System_String, string>(culturesToLoadDotNetArray);
|
||||||
&& errorInfo.status === 404
|
const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources;
|
||||||
&& filename.match(/\.pdb$/);
|
|
||||||
if (!isPdb404) {
|
if (satelliteResources) {
|
||||||
onError(errorInfo);
|
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(() => {
|
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");
|
MONO.mono_wasm_setenv("MONO_URI_DOTNETRELATIVEORABSOLUTE", "true");
|
||||||
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
|
const load_runtime = cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
|
||||||
load_runtime(appBinDirName, hasDebuggingEnabled() ? 1 : 0);
|
// -1 enables debugging with logging disabled. 0 disables debugging entirely.
|
||||||
MONO.mono_wasm_runtime_is_ready = true;
|
load_runtime(appBinDirName, hasDebuggingEnabled() ? -1 : 0);
|
||||||
|
MONO.mono_wasm_runtime_ready ();
|
||||||
attachInteropInvoker();
|
attachInteropInvoker();
|
||||||
onReady();
|
onReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
return module;
|
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');
|
const anchorTagForAbsoluteUrlConversions = document.createElement('a');
|
||||||
|
|
@ -221,38 +336,20 @@ function toAbsoluteUrl(possiblyRelativeUrl: string) {
|
||||||
return anchorTagForAbsoluteUrlConversions.href;
|
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 {
|
function getArrayDataPointer<T>(array: System_Array<T>): number {
|
||||||
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
|
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"
|
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
|
||||||
const fqn = `[${assembly}] ${typeName}:${method}`;
|
const fqn = `[${assembly}] ${typeName}:${method}`;
|
||||||
return Module.mono_bind_static_method(fqn);
|
return BINDING.bind_static_method(fqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachInteropInvoker(): void {
|
function attachInteropInvoker(): void {
|
||||||
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet');
|
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'InvokeDotNet');
|
||||||
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
||||||
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS');
|
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'EndInvokeJS');
|
||||||
|
|
||||||
DotNet.attachDispatcher({
|
DotNet.attachDispatcher({
|
||||||
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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];
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader';
|
||||||
|
|
||||||
export interface Platform {
|
export interface Platform {
|
||||||
start(loadAssemblyUrls: string[]): Promise<void>;
|
start(resourceLoader: WebAssemblyResourceLoader): Promise<void>;
|
||||||
|
|
||||||
callEntryPoint(assemblyName: string): void;
|
callEntryPoint(assemblyName: string): void;
|
||||||
|
|
||||||
toJavaScriptString(dotNetString: System_String): string;
|
|
||||||
toUint8Array(array: System_Array<any>): Uint8Array;
|
toUint8Array(array: System_Array<any>): Uint8Array;
|
||||||
|
|
||||||
getArrayLength(array: System_Array<any>): number;
|
getArrayLength(array: System_Array<any>): number;
|
||||||
|
|
@ -15,7 +16,7 @@ export interface Platform {
|
||||||
readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
|
readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
|
||||||
readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
|
readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
|
||||||
readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T;
|
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;
|
readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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$/, '');
|
|
||||||
}
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
Loading…
Reference in New Issue