Merge pull request #21921 from dotnet/prkrishn/merge-blazor-wasm
Merge blazor-wasm -> 3.1
This commit is contained in:
commit
432dc979b3
|
|
@ -664,6 +664,8 @@ stages:
|
||||||
vmImage: 'ubuntu-16.04'
|
vmImage: 'ubuntu-16.04'
|
||||||
variables:
|
variables:
|
||||||
DotNetCoreSdkDir: $(Agent.ToolsDirectory)/dotnet
|
DotNetCoreSdkDir: $(Agent.ToolsDirectory)/dotnet
|
||||||
|
# This isn't needed in the path because build does not need to _use_ global tools.
|
||||||
|
DOTNET_CLI_HOME: $(System.DefaultWorkingDirectory)
|
||||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: true
|
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: true
|
||||||
steps:
|
steps:
|
||||||
- script: |
|
- script: |
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ jobs:
|
||||||
- _BuildConfig: ${{ parameters.configuration }}
|
- _BuildConfig: ${{ parameters.configuration }}
|
||||||
- BuildConfiguration: ${{ parameters.configuration }}
|
- BuildConfiguration: ${{ parameters.configuration }}
|
||||||
- BuildDirectory: ${{ parameters.buildDirectory }}
|
- BuildDirectory: ${{ parameters.buildDirectory }}
|
||||||
|
- DOTNET_CLI_HOME: $(System.DefaultWorkingDirectory)
|
||||||
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
- TeamName: AspNetCore
|
- TeamName: AspNetCore
|
||||||
- ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
- ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
||||||
|
|
@ -155,6 +156,12 @@ jobs:
|
||||||
- ${{ if and(eq(parameters.installTar, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
- ${{ if and(eq(parameters.installTar, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
||||||
- powershell: ./eng/scripts/InstallTar.ps1
|
- powershell: ./eng/scripts/InstallTar.ps1
|
||||||
displayName: Find or install Tar
|
displayName: Find or install Tar
|
||||||
|
- ${{ if eq(parameters.agentOs, 'Windows') }}:
|
||||||
|
- powershell: Write-Host "##vso[task.prependpath]$(DOTNET_CLI_HOME)\.dotnet\tools"
|
||||||
|
displayName: Add dotnet tools to path
|
||||||
|
- ${{ if ne(parameters.agentOs, 'Windows') }}:
|
||||||
|
- script: echo "##vso[task.prependpath]$(DOTNET_CLI_HOME)/.dotnet/tools"
|
||||||
|
displayName: Add dotnet tools to path
|
||||||
|
|
||||||
- ${{ parameters.beforeBuild }}
|
- ${{ parameters.beforeBuild }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-serve": {
|
||||||
|
"version": "1.7.125",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-serve"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,13 @@
|
||||||
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
||||||
<BaselinePackageVersion>3.1.5</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.5</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.5</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.5</BaselinePackageVersion>
|
||||||
|
|
@ -201,6 +208,49 @@
|
||||||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.5, )" />
|
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.5, )" />
|
||||||
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.5, )" />
|
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.5, )" />
|
||||||
</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.WebAssembly.HttpHandler" 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.5</BaselinePackageVersion>
|
<BaselinePackageVersion>3.1.5</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.5" />
|
<Package Id="dotnet-sql-cache" Version="3.1.5" />
|
||||||
|
<Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" />
|
||||||
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.5" />
|
||||||
|
|
@ -30,6 +31,12 @@ Update this list when preparing for a new patch.
|
||||||
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.5" />
|
||||||
|
<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.5" />
|
<Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.5" />
|
||||||
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.5" />
|
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.5" />
|
||||||
|
|
|
||||||
|
|
@ -34,7 +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\Templates\src\content\**\*.*proj;
|
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
|
||||||
|
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
|
||||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.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;
|
||||||
|
|
|
||||||
|
|
@ -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.5">
|
<Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.5">
|
||||||
<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>6</AspNetCorePatchVersion>
|
<AspNetCorePatchVersion>6</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.5-servicing.20270.9</InternalAspNetCoreAnalyzersPackageVersion>
|
<InternalAspNetCoreAnalyzersPackageVersion>3.1.5-servicing.20270.9</InternalAspNetCoreAnalyzersPackageVersion>
|
||||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.5-servicing.20270.9</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.5-servicing.20270.9</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ try {
|
||||||
Get-ChildItem "$repoRoot/*.sln" -Recurse `
|
Get-ChildItem "$repoRoot/*.sln" -Recurse `
|
||||||
| ? {
|
| ? {
|
||||||
# These .sln files are used by the templating engine.
|
# These .sln files are used by the templating engine.
|
||||||
($_.Name -ne "BlazorServerWeb_CSharp.sln")
|
(($_.Name -ne "BlazorServerWeb_CSharp.sln") -and ($_.Name -ne 'ComponentsWebAssembly-CSharp.sln'))
|
||||||
} `
|
} `
|
||||||
| % {
|
| % {
|
||||||
Write-Host " Checking $(Split-Path -Leaf $_)"
|
Write-Host " Checking $(Split-Path -Leaf $_)"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +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.Components.Builder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides extension methods for <see cref="IComponentsApplicationBuilder"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static class ComponentsApplicationBuilderExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Associates the component type with the application,
|
|
||||||
/// causing it to be displayed in the specified DOM element.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="app">The <see cref="IComponentsApplicationBuilder"/>.</param>
|
|
||||||
/// <typeparam name="TComponent">The type of the component.</typeparam>
|
|
||||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
|
||||||
public static void AddComponent<TComponent>(this IComponentsApplicationBuilder app, string domElementSelector)
|
|
||||||
where TComponent : IComponent
|
|
||||||
{
|
|
||||||
app.AddComponent(typeof(TComponent), domElementSelector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Builder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A builder for adding components to an application.
|
|
||||||
/// </summary>
|
|
||||||
public interface IComponentsApplicationBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the application services.
|
|
||||||
/// </summary>
|
|
||||||
IServiceProvider Services { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Associates the <see cref="IComponent"/> with the application,
|
|
||||||
/// causing it to be displayed in the specified DOM element.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="componentType">The type of the component.</param>
|
|
||||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
|
||||||
void AddComponent(Type componentType, string domElementSelector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +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.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used to create an instance of Blazor host builder for a Browser application.
|
|
||||||
/// </summary>
|
|
||||||
public static class BlazorWebAssemblyHost
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of <see cref="IWebAssemblyHostBuilder"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
|
||||||
public static IWebAssemblyHostBuilder CreateDefaultBuilder()
|
|
||||||
{
|
|
||||||
return new WebAssemblyHostBuilder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +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.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.ExceptionServices;
|
|
||||||
using Microsoft.AspNetCore.Components.Builder;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
// Keeping this simple for now to focus on predictable and reasonable behaviors.
|
|
||||||
// Startup in WebHost supports lots of things we don't yet support, and some we
|
|
||||||
// may never support.
|
|
||||||
//
|
|
||||||
// Possible additions:
|
|
||||||
// - environments
|
|
||||||
// - case-insensitivity (makes sense with environments)
|
|
||||||
//
|
|
||||||
// Likely never:
|
|
||||||
// - statics
|
|
||||||
// - DI into constructor
|
|
||||||
internal class ConventionBasedStartup : IBlazorStartup
|
|
||||||
{
|
|
||||||
public ConventionBasedStartup(object instance)
|
|
||||||
{
|
|
||||||
Instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Instance { get; }
|
|
||||||
|
|
||||||
public void Configure(IComponentsApplicationBuilder app, IServiceProvider services)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var method = GetConfigureMethod();
|
|
||||||
Debug.Assert(method != null);
|
|
||||||
|
|
||||||
var parameters = method.GetParameters();
|
|
||||||
var arguments = new object[parameters.Length];
|
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
|
||||||
{
|
|
||||||
var parameter = parameters[i];
|
|
||||||
arguments[i] = parameter.ParameterType == typeof(IComponentsApplicationBuilder)
|
|
||||||
? app
|
|
||||||
: services.GetRequiredService(parameter.ParameterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
method.Invoke(Instance, arguments);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is TargetInvocationException)
|
|
||||||
{
|
|
||||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal MethodInfo GetConfigureMethod()
|
|
||||||
{
|
|
||||||
var methods = Instance.GetType()
|
|
||||||
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
|
|
||||||
.Where(m => string.Equals(m.Name, "Configure", StringComparison.Ordinal))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (methods.Length == 1)
|
|
||||||
{
|
|
||||||
return methods[0];
|
|
||||||
}
|
|
||||||
else if (methods.Length == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The startup class must define a 'Configure' method.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Overloading the 'Configure' method is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var method = GetConfigureServicesMethod();
|
|
||||||
if (method != null)
|
|
||||||
{
|
|
||||||
method.Invoke(Instance, new object[] { services });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is TargetInvocationException)
|
|
||||||
{
|
|
||||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal MethodInfo GetConfigureServicesMethod()
|
|
||||||
{
|
|
||||||
return Instance.GetType()
|
|
||||||
.GetMethod(
|
|
||||||
"ConfigureServices",
|
|
||||||
BindingFlags.Public | BindingFlags.Instance,
|
|
||||||
null,
|
|
||||||
new Type[] { typeof(IServiceCollection), },
|
|
||||||
Array.Empty<ParameterModifier>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +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.Builder;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
internal interface IBlazorStartup
|
|
||||||
{
|
|
||||||
void ConfigureServices(IServiceCollection services);
|
|
||||||
|
|
||||||
void Configure(IComponentsApplicationBuilder app, IServiceProvider services);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A program abstraction.
|
|
||||||
/// </summary>
|
|
||||||
public interface IWebAssemblyHost : IDisposable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The programs configured services.
|
|
||||||
/// </summary>
|
|
||||||
IServiceProvider Services { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start the program.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">Used to abort program start.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task StartAsync(CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to gracefully stop the program.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task StopAsync(CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +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 Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstraction for configuring a Blazor browser-based application.
|
|
||||||
/// </summary>
|
|
||||||
public interface IWebAssemblyHostBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A central location for sharing state between components during the host building process.
|
|
||||||
/// </summary>
|
|
||||||
IDictionary<object, object> Properties { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the factory used to create the service provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the factory used to create the service provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configureDelegate">The delegate for configuring the <see cref="IServiceCollection"/> that will be used
|
|
||||||
/// to construct the <see cref="IServiceProvider"/>.</param>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
IWebAssemblyHostBuilder ConfigureServices(Action<WebAssemblyHostBuilderContext, IServiceCollection> configureDelegate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run the given actions to initialize the host. This can only be called once.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An initialized <see cref="IWebAssemblyHost"/></returns>
|
|
||||||
IWebAssemblyHost Build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs
|
|
||||||
|
|
||||||
internal interface IWebAssemblyServiceFactoryAdapter
|
|
||||||
{
|
|
||||||
object CreateBuilder(IServiceCollection services);
|
|
||||||
|
|
||||||
IServiceProvider CreateServiceProvider(object containerBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +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.Blazor.Rendering;
|
|
||||||
using Microsoft.AspNetCore.Components.Builder;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
internal class WebAssemblyBlazorApplicationBuilder : IComponentsApplicationBuilder
|
|
||||||
{
|
|
||||||
public WebAssemblyBlazorApplicationBuilder(IServiceProvider services)
|
|
||||||
{
|
|
||||||
Entries = new List<(Type componentType, string domElementSelector)>();
|
|
||||||
Services = services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<(Type componentType, string domElementSelector)> Entries { get; }
|
|
||||||
|
|
||||||
public IServiceProvider Services { get; }
|
|
||||||
|
|
||||||
public void AddComponent(Type componentType, string domElementSelector)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domElementSelector == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(domElementSelector));
|
|
||||||
}
|
|
||||||
|
|
||||||
Entries.Add((componentType, domElementSelector));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<WebAssemblyRenderer> CreateRendererAsync()
|
|
||||||
{
|
|
||||||
var loggerFactory = (ILoggerFactory)Services.GetService(typeof(ILoggerFactory));
|
|
||||||
var renderer = new WebAssemblyRenderer(Services, loggerFactory);
|
|
||||||
for (var i = 0; i < Entries.Count; i++)
|
|
||||||
{
|
|
||||||
var (componentType, domElementSelector) = Entries[i];
|
|
||||||
await renderer.AddComponentAsync(componentType, domElementSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +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;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Blazor.Rendering;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
internal class WebAssemblyHost : IWebAssemblyHost
|
|
||||||
{
|
|
||||||
private readonly IJSRuntime _runtime;
|
|
||||||
|
|
||||||
private IServiceScope _scope;
|
|
||||||
private WebAssemblyRenderer _renderer;
|
|
||||||
|
|
||||||
public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime)
|
|
||||||
{
|
|
||||||
Services = services ?? throw new ArgumentNullException(nameof(services));
|
|
||||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceProvider Services { get; }
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return StartAsyncAwaited();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartAsyncAwaited()
|
|
||||||
{
|
|
||||||
var scopeFactory = Services.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
_scope = scopeFactory.CreateScope();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var startup = _scope.ServiceProvider.GetService<IBlazorStartup>();
|
|
||||||
if (startup == null)
|
|
||||||
{
|
|
||||||
var message =
|
|
||||||
$"Could not find a registered Blazor Startup class. " +
|
|
||||||
$"Using {nameof(IWebAssemblyHost)} requires a call to {nameof(IWebAssemblyHostBuilder)}.UseBlazorStartup.";
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that we differ from the WebHost startup path here by using a 'scope' for the app builder
|
|
||||||
// as well as the Configure method.
|
|
||||||
var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider);
|
|
||||||
startup.Configure(builder, _scope.ServiceProvider);
|
|
||||||
|
|
||||||
_renderer = await builder.CreateRendererAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_scope.Dispose();
|
|
||||||
_scope = null;
|
|
||||||
|
|
||||||
if (_renderer != null)
|
|
||||||
{
|
|
||||||
_renderer.Dispose();
|
|
||||||
_renderer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_scope != null)
|
|
||||||
{
|
|
||||||
_scope.Dispose();
|
|
||||||
_scope = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_renderer != null)
|
|
||||||
{
|
|
||||||
_renderer.Dispose();
|
|
||||||
_renderer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
(Services as IDisposable)?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +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 Microsoft.AspNetCore.Blazor.Services;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// This code was taken virtually as-is from the Microsoft.Extensions.Hosting project in aspnet/Hosting and then
|
|
||||||
// lots of things were removed.
|
|
||||||
//
|
|
||||||
internal class WebAssemblyHostBuilder : IWebAssemblyHostBuilder
|
|
||||||
{
|
|
||||||
private List<Action<WebAssemblyHostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<WebAssemblyHostBuilderContext, IServiceCollection>>();
|
|
||||||
private bool _hostBuilt;
|
|
||||||
private WebAssemblyHostBuilderContext _BrowserHostBuilderContext;
|
|
||||||
private IWebAssemblyServiceFactoryAdapter _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
|
|
||||||
private IServiceProvider _appServices;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A central location for sharing state between components during the host building process.
|
|
||||||
/// </summary>
|
|
||||||
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configureDelegate"></param>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
public IWebAssemblyHostBuilder ConfigureServices(Action<WebAssemblyHostBuilderContext, IServiceCollection> configureDelegate)
|
|
||||||
{
|
|
||||||
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the factory used to create the service provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
|
|
||||||
{
|
|
||||||
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the factory used to create the service provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
|
|
||||||
{
|
|
||||||
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(() => _BrowserHostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run the given actions to initialize the host. This can only be called once.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An initialized <see cref="IWebAssemblyHost"/></returns>
|
|
||||||
public IWebAssemblyHost Build()
|
|
||||||
{
|
|
||||||
if (_hostBuilt)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Build can only be called once.");
|
|
||||||
}
|
|
||||||
_hostBuilt = true;
|
|
||||||
|
|
||||||
CreateBrowserHostBuilderContext();
|
|
||||||
CreateServiceProvider();
|
|
||||||
|
|
||||||
return _appServices.GetRequiredService<IWebAssemblyHost>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateBrowserHostBuilderContext()
|
|
||||||
{
|
|
||||||
_BrowserHostBuilderContext = new WebAssemblyHostBuilderContext(Properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateServiceProvider()
|
|
||||||
{
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddSingleton(_BrowserHostBuilderContext);
|
|
||||||
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
|
||||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
|
||||||
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
|
||||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
|
||||||
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Needed for authorization
|
|
||||||
// However, since authorization isn't on by default, we could consider removing these and
|
|
||||||
// having a separate services.AddBlazorAuthorization() call that brings in the required services.
|
|
||||||
services.AddOptions();
|
|
||||||
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>)));
|
|
||||||
|
|
||||||
foreach (var configureServicesAction in _configureServicesActions)
|
|
||||||
{
|
|
||||||
configureServicesAction(_BrowserHostBuilderContext, services);
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = _serviceProviderFactory.CreateBuilder(services);
|
|
||||||
_appServices = _serviceProviderFactory.CreateServiceProvider(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Context containing the common services on the <see cref="IWebAssemblyHost" />. Some properties may be null until set by the <see cref="IWebAssemblyHost" />.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class WebAssemblyHostBuilderContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="WebAssemblyHostBuilderContext" />.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="properties">The property collection.</param>
|
|
||||||
public WebAssemblyHostBuilderContext(IDictionary<object, object> properties)
|
|
||||||
{
|
|
||||||
Properties = properties ?? throw new System.ArgumentNullException(nameof(properties));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A central location for sharing state between components during the host building process.
|
|
||||||
/// </summary>
|
|
||||||
public IDictionary<object, object> Properties { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +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.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides Blazor-specific support for <see cref="IWebAssemblyHost"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static class WebAssemblyHostBuilderExtensions
|
|
||||||
{
|
|
||||||
private const string BlazorStartupKey = "Blazor.Startup";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hostBuilder">The <see cref="IWebAssemblyHostBuilder" /> to configure.</param>
|
|
||||||
/// <param name="configureDelegate"></param>
|
|
||||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
|
||||||
public static IWebAssemblyHostBuilder ConfigureServices(this IWebAssemblyHostBuilder hostBuilder, Action<IServiceCollection> configureDelegate)
|
|
||||||
{
|
|
||||||
return hostBuilder.ConfigureServices((context, collection) => configureDelegate(collection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the <see cref="IWebAssemblyHostBuilder"/> to use the provided startup class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">The <see cref="IWebAssemblyHostBuilder"/>.</param>
|
|
||||||
/// <param name="startupType">A type that configures a Blazor application.</param>
|
|
||||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
|
||||||
public static IWebAssemblyHostBuilder UseBlazorStartup(this IWebAssemblyHostBuilder builder, Type startupType)
|
|
||||||
{
|
|
||||||
if (builder == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(builder));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder.Properties.ContainsKey(BlazorStartupKey))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("A startup class has already been registered.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// It would complicate the implementation to allow multiple startup classes, and we don't
|
|
||||||
// really have a need for it.
|
|
||||||
builder.Properties.Add(BlazorStartupKey, bool.TrueString);
|
|
||||||
|
|
||||||
var startup = new ConventionBasedStartup(Activator.CreateInstance(startupType));
|
|
||||||
|
|
||||||
builder.ConfigureServices(startup.ConfigureServices);
|
|
||||||
builder.ConfigureServices(s => s.AddSingleton<IBlazorStartup>(startup));
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the <see cref="IWebAssemblyHostBuilder"/> to use the provided startup class.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TStartup">A type that configures a Blazor application.</typeparam>
|
|
||||||
/// <param name="builder">The <see cref="IWebAssemblyHostBuilder"/>.</param>
|
|
||||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
|
||||||
public static IWebAssemblyHostBuilder UseBlazorStartup<TStartup>(this IWebAssemblyHostBuilder builder)
|
|
||||||
{
|
|
||||||
if (builder == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(builder));
|
|
||||||
}
|
|
||||||
|
|
||||||
return UseBlazorStartup(builder, typeof(TStartup));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for <see cref="IWebAssemblyHost"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static class WebAssemblyHostExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the application.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="host">The <see cref="IWebAssemblyHost"/> to run.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Currently, Blazor applications running in the browser don't have a lifecycle - the application does not
|
|
||||||
/// get a chance to gracefully shut down. For now, <see cref="Run(IWebAssemblyHost)"/> simply starts the host
|
|
||||||
/// and allows execution to continue.
|
|
||||||
/// </remarks>
|
|
||||||
public static void Run(this IWebAssemblyHost host)
|
|
||||||
{
|
|
||||||
// Behave like async void, because we don't yet support async-main properly on WebAssembly.
|
|
||||||
// However, don't actualy make this method async, because we rely on startup being synchronous
|
|
||||||
// for things like attaching navigation event handlers.
|
|
||||||
host.StartAsync().ContinueWith(task =>
|
|
||||||
{
|
|
||||||
if (task.Exception != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine(task.Exception);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +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.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
|
||||||
{
|
|
||||||
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs
|
|
||||||
|
|
||||||
internal class WebAssemblyServiceFactoryAdapter<TContainerBuilder> : IWebAssemblyServiceFactoryAdapter
|
|
||||||
{
|
|
||||||
private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
|
|
||||||
private readonly Func<WebAssemblyHostBuilderContext> _contextResolver;
|
|
||||||
private Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;
|
|
||||||
|
|
||||||
public WebAssemblyServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
|
|
||||||
{
|
|
||||||
_serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebAssemblyServiceFactoryAdapter(Func<WebAssemblyHostBuilderContext> contextResolver, Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
|
|
||||||
{
|
|
||||||
_contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver));
|
|
||||||
_factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver));
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateBuilder(IServiceCollection services)
|
|
||||||
{
|
|
||||||
if (_serviceProviderFactory == null)
|
|
||||||
{
|
|
||||||
_serviceProviderFactory = _factoryResolver(_contextResolver());
|
|
||||||
|
|
||||||
if (_serviceProviderFactory == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _serviceProviderFactory.CreateBuilder(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceProvider CreateServiceProvider(object containerBuilder)
|
|
||||||
{
|
|
||||||
if (_serviceProviderFactory == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.0</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.Options" />
|
|
||||||
</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,210 +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 Microsoft.AspNetCore.Blazor.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Components.Builder;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Hosting
|
|
||||||
{
|
|
||||||
public class ConventionBasedStartupTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_GetConfigureServicesMethod_FindsConfigureServices()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup1());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = startup.GetConfigureServicesMethod();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(typeof(IServiceCollection), method.GetParameters()[0].ParameterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup1
|
|
||||||
{
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored
|
|
||||||
public void ConfigureServices(DateTime x)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored
|
|
||||||
private void ConfigureServices(int x)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored
|
|
||||||
public static void ConfigureServices(string x)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_GetConfigureServicesMethod_NoMethodFound()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup2());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = startup.GetConfigureServicesMethod();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Null(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup2
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_ConfigureServices_CallsMethod()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup3());
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
startup.ConfigureServices(services);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotEmpty(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup3
|
|
||||||
{
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton("foo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_ConfigureServices_NoMethodFound()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup4());
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
startup.ConfigureServices(services);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup4
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_GetConfigureMethod_FindsConfigure()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup5());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = startup.GetConfigureMethod();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(method.GetParameters());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup5
|
|
||||||
{
|
|
||||||
public void Configure()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored
|
|
||||||
private void Configure(int x)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored
|
|
||||||
public static void Configure(string x)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_GetConfigureMethod_NoMethodFoundThrows()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup6());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => startup.GetConfigureMethod());
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("The startup class must define a 'Configure' method.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup6
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_GetConfigureMethod_OverloadedThrows()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var startup = new ConventionBasedStartup(new MyStartup7());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => startup.GetConfigureMethod());
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("Overloading the 'Configure' method is not supported.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup7
|
|
||||||
{
|
|
||||||
public void Configure()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Configure(string x)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConventionBasedStartup_Configure()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var instance = new MyStartup8();
|
|
||||||
var startup = new ConventionBasedStartup(instance);
|
|
||||||
|
|
||||||
var services = new ServiceCollection().AddSingleton("foo").BuildServiceProvider();
|
|
||||||
var builder = new WebAssemblyBlazorApplicationBuilder(services);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
startup.Configure(builder, services);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
instance.Arguments,
|
|
||||||
a => Assert.Same(builder, a),
|
|
||||||
a => Assert.Equal("foo", a));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup8
|
|
||||||
{
|
|
||||||
public List<object> Arguments { get; } = new List<object>();
|
|
||||||
|
|
||||||
public void Configure(IComponentsApplicationBuilder app, string foo)
|
|
||||||
{
|
|
||||||
Arguments.Add(app);
|
|
||||||
Arguments.Add(foo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,163 +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.Text;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting.Test
|
|
||||||
{
|
|
||||||
public class WebAssemblyHostBuilderTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_CanCallBuild_BuildsServices()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(host.Services.GetService(typeof(IWebAssemblyHost)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_CanConfigureAdditionalServices()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
builder.ConfigureServices((c, s) => s.AddSingleton<string>("foo"));
|
|
||||||
builder.ConfigureServices((c, s) => s.AddSingleton<StringBuilder>(new StringBuilder("bar")));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("foo", host.Services.GetService(typeof(string)));
|
|
||||||
Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_UseBlazorStartup_CanConfigureAdditionalServices()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
builder.UseBlazorStartup<MyStartup>();
|
|
||||||
builder.ConfigureServices((c, s) => s.AddSingleton<StringBuilder>(new StringBuilder("bar")));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("foo", host.Services.GetService(typeof(string)));
|
|
||||||
Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_UseBlazorStartup_DoesNotAllowMultiple()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
builder.UseBlazorStartup<MyStartup>();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseBlazorStartup<MyStartup>());
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("A startup class has already been registered.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyStartup
|
|
||||||
{
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<string>("foo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_CanCustomizeServiceFactory()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
builder.UseServiceProviderFactory(new TestServiceProviderFactory());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.IsType<TestServiceProvider>(host.Services);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void HostBuilder_CanCustomizeServiceFactoryWithContext()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
builder.UseServiceProviderFactory(context =>
|
|
||||||
{
|
|
||||||
Assert.NotNull(context.Properties);
|
|
||||||
Assert.Same(builder.Properties, context.Properties);
|
|
||||||
return new TestServiceProviderFactory();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.IsType<TestServiceProvider>(host.Services);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestServiceProvider : IServiceProvider
|
|
||||||
{
|
|
||||||
private readonly IServiceProvider _underlyingProvider;
|
|
||||||
|
|
||||||
public TestServiceProvider(IServiceProvider underlyingProvider)
|
|
||||||
{
|
|
||||||
_underlyingProvider = underlyingProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object GetService(Type serviceType)
|
|
||||||
{
|
|
||||||
if (serviceType == typeof(IWebAssemblyHost))
|
|
||||||
{
|
|
||||||
// Since the test will make assertions about the resulting IWebAssemblyHost,
|
|
||||||
// show that custom DI containers have the power to substitute themselves
|
|
||||||
// as the IServiceProvider
|
|
||||||
return new WebAssemblyHost(
|
|
||||||
this, _underlyingProvider.GetRequiredService<IJSRuntime>());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return _underlyingProvider.GetService(serviceType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
|
|
||||||
{
|
|
||||||
public IServiceCollection CreateBuilder(IServiceCollection services)
|
|
||||||
{
|
|
||||||
return new TestServiceCollection(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
|
|
||||||
{
|
|
||||||
Assert.IsType<TestServiceCollection>(serviceCollection);
|
|
||||||
return new TestServiceProvider(serviceCollection.BuildServiceProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestServiceCollection : List<ServiceDescriptor>, IServiceCollection
|
|
||||||
{
|
|
||||||
public TestServiceCollection(IEnumerable<ServiceDescriptor> collection)
|
|
||||||
: base(collection)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +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;
|
|
||||||
using Microsoft.AspNetCore.Components.Builder;
|
|
||||||
using Microsoft.AspNetCore.Components.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Mono.WebAssembly.Interop;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Hosting.Test
|
|
||||||
{
|
|
||||||
public class WebAssemblyHostTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task BrowserHost_StartAsync_ThrowsWithoutStartup()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await host.StartAsync());
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(
|
|
||||||
"Could not find a registered Blazor Startup class. " +
|
|
||||||
"Using IWebAssemblyHost requires a call to IWebAssemblyHostBuilder.UseBlazorStartup.",
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BrowserHost_StartAsync_RunsConfigureMethod()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new WebAssemblyHostBuilder();
|
|
||||||
|
|
||||||
var startup = new MockStartup();
|
|
||||||
builder.ConfigureServices((c, s) => { s.AddSingleton<IBlazorStartup>(startup); });
|
|
||||||
|
|
||||||
var host = builder.Build();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await host.StartAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(startup.ConfigureCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MockStartup : IBlazorStartup
|
|
||||||
{
|
|
||||||
public bool ConfigureCalled { get; set; }
|
|
||||||
|
|
||||||
public void Configure(IComponentsApplicationBuilder app, IServiceProvider services)
|
|
||||||
{
|
|
||||||
ConfigureCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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,61 +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.Extensions.CommandLineUtils;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
|
||||||
{
|
|
||||||
class ResolveRuntimeDependenciesCommand
|
|
||||||
{
|
|
||||||
public static void Command(CommandLineApplication command)
|
|
||||||
{
|
|
||||||
var referencesFile = command.Option("--references",
|
|
||||||
"The path to a file that lists the paths to given referenced dll files",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var baseClassLibrary = command.Option("--base-class-library",
|
|
||||||
"Full path to a directory in which BCL assemblies can be found",
|
|
||||||
CommandOptionType.MultipleValue);
|
|
||||||
|
|
||||||
var outputPath = command.Option("--output",
|
|
||||||
"Path to the output file that will contain the list with the full paths of the resolved assemblies",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var mainAssemblyPath = command.Argument("assembly",
|
|
||||||
"Path to the assembly containing the entry point of the application.");
|
|
||||||
|
|
||||||
command.OnExecute(() =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mainAssemblyPath.Value) ||
|
|
||||||
!baseClassLibrary.HasValue() || !outputPath.HasValue())
|
|
||||||
{
|
|
||||||
command.ShowHelp(command.Name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var referencesSources = referencesFile.HasValue()
|
|
||||||
? File.ReadAllLines(referencesFile.Value())
|
|
||||||
: Array.Empty<string>();
|
|
||||||
|
|
||||||
RuntimeDependenciesResolver.ResolveRuntimeDependencies(
|
|
||||||
mainAssemblyPath.Value,
|
|
||||||
referencesSources,
|
|
||||||
baseClassLibrary.Values.ToArray(),
|
|
||||||
outputPath.Value());
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"ERROR: {ex.Message}");
|
|
||||||
Console.WriteLine(ex.StackTrace);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +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.CommandLineUtils;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
|
||||||
{
|
|
||||||
internal class WriteBootJsonCommand
|
|
||||||
{
|
|
||||||
public static void Command(CommandLineApplication command)
|
|
||||||
{
|
|
||||||
var referencesFile = command.Option("--references",
|
|
||||||
"The path to a file that lists the paths to given referenced dll files",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var embeddedResourcesFile = command.Option("--embedded-resources",
|
|
||||||
"The path to a file that lists the paths of .NET assemblies that may contain embedded resources (typically, referenced assemblies in their pre-linked states)",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var outputPath = command.Option("--output",
|
|
||||||
"Path to the output file",
|
|
||||||
CommandOptionType.SingleValue);
|
|
||||||
|
|
||||||
var mainAssemblyPath = command.Argument("assembly",
|
|
||||||
"Path to the assembly containing the entry point of the application.");
|
|
||||||
|
|
||||||
var linkerEnabledFlag = command.Option("--linker-enabled",
|
|
||||||
"If set, specifies that the application is being built with linking enabled.",
|
|
||||||
CommandOptionType.NoValue);
|
|
||||||
|
|
||||||
command.OnExecute(() =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(mainAssemblyPath.Value) || !outputPath.HasValue())
|
|
||||||
{
|
|
||||||
command.ShowHelp(command.Name);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var referencesSources = referencesFile.HasValue()
|
|
||||||
? File.ReadAllLines(referencesFile.Value())
|
|
||||||
: Array.Empty<string>();
|
|
||||||
|
|
||||||
var embeddedResourcesSources = embeddedResourcesFile.HasValue()
|
|
||||||
? File.ReadAllLines(embeddedResourcesFile.Value())
|
|
||||||
: Array.Empty<string>();
|
|
||||||
|
|
||||||
BootJsonWriter.WriteFile(
|
|
||||||
mainAssemblyPath.Value,
|
|
||||||
referencesSources,
|
|
||||||
embeddedResourcesSources,
|
|
||||||
linkerEnabledFlag.HasValue(),
|
|
||||||
outputPath.Value());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"ERROR: {ex.Message}");
|
|
||||||
Console.WriteLine(ex.StackTrace);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +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.Blazor.Build.DevServer.Commands;
|
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
static class Program
|
|
||||||
{
|
|
||||||
static int Main(string[] args)
|
|
||||||
{
|
|
||||||
var app = new CommandLineApplication
|
|
||||||
{
|
|
||||||
Name = "Microsoft.AspNetCore.Blazor.Build"
|
|
||||||
};
|
|
||||||
app.HelpOption("-?|-h|--help");
|
|
||||||
|
|
||||||
app.Command("resolve-dependencies", ResolveRuntimeDependenciesCommand.Command);
|
|
||||||
app.Command("write-boot-json", WriteBootJsonCommand.Command);
|
|
||||||
|
|
||||||
if (args.Length > 0)
|
|
||||||
{
|
|
||||||
return app.Execute(args);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
app.ShowHelp();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +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.Text.Json;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Mono.Cecil;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class BootJsonWriter
|
|
||||||
{
|
|
||||||
public static void WriteFile(
|
|
||||||
string assemblyPath,
|
|
||||||
string[] assemblyReferences,
|
|
||||||
string[] embeddedResourcesSources,
|
|
||||||
bool linkerEnabled,
|
|
||||||
string outputPath)
|
|
||||||
{
|
|
||||||
var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources(
|
|
||||||
embeddedResourcesSources, Path.GetDirectoryName(outputPath));
|
|
||||||
var bootJsonText = GetBootJsonContent(
|
|
||||||
Path.GetFileName(assemblyPath),
|
|
||||||
GetAssemblyEntryPoint(assemblyPath),
|
|
||||||
assemblyReferences,
|
|
||||||
embeddedContent,
|
|
||||||
linkerEnabled);
|
|
||||||
var normalizedOutputPath = Path.GetFullPath(outputPath);
|
|
||||||
Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
|
|
||||||
File.WriteAllText(normalizedOutputPath, bootJsonText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable<EmbeddedResourceInfo> embeddedContent, bool linkerEnabled)
|
|
||||||
{
|
|
||||||
var data = new BootJsonData(
|
|
||||||
assemblyFileName,
|
|
||||||
entryPoint,
|
|
||||||
assemblyReferences,
|
|
||||||
embeddedContent,
|
|
||||||
linkerEnabled);
|
|
||||||
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetAssemblyEntryPoint(string assemblyPath)
|
|
||||||
{
|
|
||||||
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
|
|
||||||
{
|
|
||||||
var entryPoint = assemblyDefinition.EntryPoint;
|
|
||||||
if (entryPoint == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the structure of a Blazor boot JSON file
|
|
||||||
/// </summary>
|
|
||||||
class BootJsonData
|
|
||||||
{
|
|
||||||
public string Main { get; }
|
|
||||||
public string EntryPoint { get; }
|
|
||||||
public IEnumerable<string> AssemblyReferences { get; }
|
|
||||||
public IEnumerable<string> CssReferences { get; }
|
|
||||||
public IEnumerable<string> JsReferences { get; }
|
|
||||||
public bool LinkerEnabled { get; }
|
|
||||||
|
|
||||||
public BootJsonData(
|
|
||||||
string entrypointAssemblyWithExtension,
|
|
||||||
string entryPoint,
|
|
||||||
IEnumerable<string> assemblyReferences,
|
|
||||||
IEnumerable<EmbeddedResourceInfo> embeddedContent,
|
|
||||||
bool linkerEnabled)
|
|
||||||
{
|
|
||||||
Main = entrypointAssemblyWithExtension;
|
|
||||||
EntryPoint = entryPoint;
|
|
||||||
AssemblyReferences = assemblyReferences;
|
|
||||||
LinkerEnabled = linkerEnabled;
|
|
||||||
|
|
||||||
CssReferences = embeddedContent
|
|
||||||
.Where(c => c.Kind == EmbeddedResourceKind.Css)
|
|
||||||
.Select(c => c.RelativePath);
|
|
||||||
|
|
||||||
JsReferences = embeddedContent
|
|
||||||
.Where(c => c.Kind == EmbeddedResourceKind.JavaScript)
|
|
||||||
.Select(c => c.RelativePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class EmbeddedResourceInfo
|
|
||||||
{
|
|
||||||
public EmbeddedResourceKind Kind { get; }
|
|
||||||
public string RelativePath { get; }
|
|
||||||
|
|
||||||
public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath)
|
|
||||||
{
|
|
||||||
Kind = kind;
|
|
||||||
RelativePath = relativePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +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.Build
|
|
||||||
{
|
|
||||||
internal enum EmbeddedResourceKind
|
|
||||||
{
|
|
||||||
JavaScript,
|
|
||||||
Css,
|
|
||||||
Static
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +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 Mono.Cecil;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class EmbeddedResourcesProcessor
|
|
||||||
{
|
|
||||||
const string ContentSubdirName = "_content";
|
|
||||||
|
|
||||||
private readonly static Dictionary<string, EmbeddedResourceKind> _knownResourceKindsByNamePrefix = new Dictionary<string, EmbeddedResourceKind>
|
|
||||||
{
|
|
||||||
{ "blazor:js:", EmbeddedResourceKind.JavaScript },
|
|
||||||
{ "blazor:css:", EmbeddedResourceKind.Css },
|
|
||||||
{ "blazor:file:", EmbeddedResourceKind.Static },
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds Blazor-specific embedded resources in the specified assemblies, writes them
|
|
||||||
/// to disk, and returns a description of those resources in dependency order.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="referencedAssemblyPaths">The paths to assemblies that may contain embedded resources.</param>
|
|
||||||
/// <param name="outputDir">The path to the directory where output is being written.</param>
|
|
||||||
/// <returns>A description of the embedded resources that were written to disk.</returns>
|
|
||||||
public static IReadOnlyList<EmbeddedResourceInfo> ExtractEmbeddedResources(
|
|
||||||
IEnumerable<string> referencedAssemblyPaths, string outputDir)
|
|
||||||
{
|
|
||||||
// Clean away any earlier state
|
|
||||||
var contentDir = Path.Combine(outputDir, ContentSubdirName);
|
|
||||||
if (Directory.Exists(contentDir))
|
|
||||||
{
|
|
||||||
Directory.Delete(contentDir, recursive: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, get an ordered list of AssemblyDefinition instances
|
|
||||||
var referencedAssemblyDefinitions = referencedAssemblyPaths
|
|
||||||
.Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want
|
|
||||||
.Select(path => AssemblyDefinition.ReadAssembly(path))
|
|
||||||
.ToList();
|
|
||||||
referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst);
|
|
||||||
|
|
||||||
// Now process them in turn
|
|
||||||
return referencedAssemblyDefinitions
|
|
||||||
.SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir))
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<EmbeddedResourceInfo> ExtractEmbeddedResourcesFromSingleAssembly(
|
|
||||||
AssemblyDefinition assemblyDefinition, string outputDirPath)
|
|
||||||
{
|
|
||||||
var assemblyName = assemblyDefinition.Name.Name;
|
|
||||||
foreach (var res in assemblyDefinition.MainModule.Resources)
|
|
||||||
{
|
|
||||||
if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo))
|
|
||||||
{
|
|
||||||
yield return extractedResourceInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo)
|
|
||||||
{
|
|
||||||
if (resource is EmbeddedResource embeddedResource)
|
|
||||||
{
|
|
||||||
if (TryInterpretLogicalName(resource.Name, out var kind, out var name))
|
|
||||||
{
|
|
||||||
// Prefix the output path with the assembly name to ensure no clashes
|
|
||||||
// Also be invariant to the OS on which the package was built
|
|
||||||
name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar));
|
|
||||||
|
|
||||||
// Write the file content to disk, ensuring we don't try to write outside the output root
|
|
||||||
var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name));
|
|
||||||
if (!outputPath.StartsWith(outputDirPath))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}");
|
|
||||||
}
|
|
||||||
WriteResourceFile(embeddedResource, outputPath);
|
|
||||||
|
|
||||||
// The URLs we write into the boot json file need to use web-style directory separators
|
|
||||||
extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extractedResourceInfo = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void WriteResourceFile(EmbeddedResource resource, string outputPath)
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
|
||||||
using (var outputStream = File.OpenWrite(outputPath))
|
|
||||||
{
|
|
||||||
resource.GetResourceStream().CopyTo(outputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name
|
|
||||||
.Replace('\\', desiredSeparatorChar)
|
|
||||||
.Replace('/', desiredSeparatorChar);
|
|
||||||
|
|
||||||
private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName)
|
|
||||||
{
|
|
||||||
foreach (var kvp in _knownResourceKindsByNamePrefix)
|
|
||||||
{
|
|
||||||
if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
kind = kvp.Value;
|
|
||||||
resolvedName = logicalName.Substring(kvp.Key.Length);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = default;
|
|
||||||
resolvedName = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each assembly B that references A, we want the resources from A to be loaded before
|
|
||||||
// the references for B (because B's resources might depend on A's resources)
|
|
||||||
private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b)
|
|
||||||
=> AssemblyHasReference(a, b) ? 1
|
|
||||||
: AssemblyHasReference(b, a) ? -1
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to)
|
|
||||||
=> from.MainModule.AssemblyReferences
|
|
||||||
.Select(reference => reference.Name)
|
|
||||||
.Contains(to.Name.Name, StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,165 +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 Mono.Cecil;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build
|
|
||||||
{
|
|
||||||
internal class RuntimeDependenciesResolver
|
|
||||||
{
|
|
||||||
public static void ResolveRuntimeDependencies(
|
|
||||||
string entryPoint,
|
|
||||||
string[] applicationDependencies,
|
|
||||||
string[] monoBclDirectories,
|
|
||||||
string outputFile)
|
|
||||||
{
|
|
||||||
var paths = ResolveRuntimeDependenciesCore(entryPoint, applicationDependencies, monoBclDirectories);
|
|
||||||
File.WriteAllLines(outputFile, paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> ResolveRuntimeDependenciesCore(
|
|
||||||
string entryPoint,
|
|
||||||
string[] applicationDependencies,
|
|
||||||
string[] monoBclDirectories)
|
|
||||||
{
|
|
||||||
var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint));
|
|
||||||
|
|
||||||
var dependencies = applicationDependencies
|
|
||||||
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var bcl = monoBclDirectories
|
|
||||||
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f)))
|
|
||||||
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var assemblyResolutionContext = new AssemblyResolutionContext(
|
|
||||||
assembly,
|
|
||||||
dependencies,
|
|
||||||
bcl);
|
|
||||||
|
|
||||||
assemblyResolutionContext.ResolveAssemblies();
|
|
||||||
|
|
||||||
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
|
|
||||||
return paths.Concat(FindPdbs(paths));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
|
|
||||||
{
|
|
||||||
return dllPaths
|
|
||||||
.Select(path => Path.ChangeExtension(path, "pdb"))
|
|
||||||
.Where(path => File.Exists(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AssemblyResolutionContext
|
|
||||||
{
|
|
||||||
public AssemblyResolutionContext(
|
|
||||||
AssemblyEntry assembly,
|
|
||||||
AssemblyEntry[] dependencies,
|
|
||||||
AssemblyEntry[] bcl)
|
|
||||||
{
|
|
||||||
Assembly = assembly;
|
|
||||||
Dependencies = dependencies;
|
|
||||||
Bcl = bcl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssemblyEntry Assembly { get; }
|
|
||||||
public AssemblyEntry[] Dependencies { get; }
|
|
||||||
public AssemblyEntry[] Bcl { get; }
|
|
||||||
|
|
||||||
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
|
|
||||||
|
|
||||||
internal void ResolveAssemblies()
|
|
||||||
{
|
|
||||||
var visitedAssemblies = new HashSet<string>();
|
|
||||||
var pendingAssemblies = new Stack<AssemblyNameReference>();
|
|
||||||
pendingAssemblies.Push(Assembly.Definition.Name);
|
|
||||||
ResolveAssembliesCore();
|
|
||||||
|
|
||||||
void ResolveAssembliesCore()
|
|
||||||
{
|
|
||||||
while (pendingAssemblies.TryPop(out var current))
|
|
||||||
{
|
|
||||||
if (!visitedAssemblies.Contains(current.Name))
|
|
||||||
{
|
|
||||||
visitedAssemblies.Add(current.Name);
|
|
||||||
|
|
||||||
// Not all references will be resolvable within the Mono BCL, particularly
|
|
||||||
// when building for server-side Blazor as you will be running on CoreCLR
|
|
||||||
// and therefore may depend on System.* BCL assemblies that aren't present
|
|
||||||
// in Mono WebAssembly. Skipping unresolved assemblies here is equivalent
|
|
||||||
// to passing "--skip-unresolved true" to the Mono linker.
|
|
||||||
var resolved = Resolve(current);
|
|
||||||
if (resolved != null)
|
|
||||||
{
|
|
||||||
Results.Add(resolved);
|
|
||||||
var references = GetAssemblyReferences(resolved);
|
|
||||||
foreach (var reference in references)
|
|
||||||
{
|
|
||||||
pendingAssemblies.Push(reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<AssemblyNameReference> GetAssemblyReferences(AssemblyEntry current) =>
|
|
||||||
current.Definition.Modules.SelectMany(m => m.AssemblyReferences);
|
|
||||||
|
|
||||||
AssemblyEntry Resolve(AssemblyNameReference current)
|
|
||||||
{
|
|
||||||
if (Assembly.Definition.Name.Name == current.Name)
|
|
||||||
{
|
|
||||||
return Assembly;
|
|
||||||
}
|
|
||||||
|
|
||||||
var referencedAssemblyCandidate = FindCandidate(current, Dependencies);
|
|
||||||
var bclAssemblyCandidate = FindCandidate(current, Bcl);
|
|
||||||
|
|
||||||
// Resolution logic. For right now, we will prefer the mono BCL version of a given
|
|
||||||
// assembly if there is a candidate assembly and an equivalent mono assembly.
|
|
||||||
if (bclAssemblyCandidate != null)
|
|
||||||
{
|
|
||||||
return bclAssemblyCandidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return referencedAssemblyCandidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssemblyEntry FindCandidate(AssemblyNameReference current, AssemblyEntry[] candidates)
|
|
||||||
{
|
|
||||||
// Do simple name match. Assume no duplicates.
|
|
||||||
foreach (var candidate in candidates)
|
|
||||||
{
|
|
||||||
if (current.Name == candidate.Definition.Name.Name)
|
|
||||||
{
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("{ToString(),nq}")]
|
|
||||||
public class AssemblyEntry
|
|
||||||
{
|
|
||||||
public AssemblyEntry(string path, AssemblyDefinition definition)
|
|
||||||
{
|
|
||||||
Path = path;
|
|
||||||
Definition = definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Path { get; set; }
|
|
||||||
public AssemblyDefinition Definition { get; set; }
|
|
||||||
|
|
||||||
public override string ToString() => Definition.FullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
|
||||||
<Description>Build mechanism for ASP.NET Core Blazor applications.</Description>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<IsShippingPackage>false</IsShippingPackage>
|
|
||||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Pack settings -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- Producing this package requires building with NodeJS enabled. -->
|
|
||||||
<IsPackable Condition="'$(BuildNodeJS)' == 'false'">false</IsPackable>
|
|
||||||
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);Publish</GenerateNuspecDependsOn>
|
|
||||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
|
||||||
<NuspecFile>Microsoft.AspNetCore.Blazor.Build.nuspec</NuspecFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<NuspecProperty Include="configuration=$(Configuration)" />
|
|
||||||
<NuspecProperty Include="publishDir=$(PublishDir)" />
|
|
||||||
<NuspecProperty Include="componentsversion=$(ComponentsPackageVersion)" />
|
|
||||||
<NuspecProperty Include="razorversion=$(MicrosoftAspNetCoreRazorDesignPackageVersion)" />
|
|
||||||
<NuspecProperty Include="blazormonoversion=$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
|
||||||
<NuspecProperty Include="PackageThirdPartyNoticesFile=$(PackageThirdPartyNoticesFile)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
|
|
||||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
|
||||||
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
|
|
||||||
<Reference Include="Microsoft.Extensions.FileProviders.Composite" />
|
|
||||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
|
||||||
<Reference Include="Mono.Cecil" />
|
|
||||||
<Reference Include="System.CodeDom" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
|
||||||
<metadata>
|
|
||||||
$CommonMetadataElements$
|
|
||||||
<dependencies>
|
|
||||||
<dependency id="Microsoft.AspNetCore.Blazor.Mono" version="$blazormonoversion$" include="all" />
|
|
||||||
</dependencies>
|
|
||||||
</metadata>
|
|
||||||
<files>
|
|
||||||
$CommonFileElements$
|
|
||||||
<file src="$PackageThirdPartyNoticesFile$" target=".\THIRD-PARTY-NOTICES.txt" />
|
|
||||||
<file src="build\**" target="build" />
|
|
||||||
<file src="targets\**" target="targets" />
|
|
||||||
<file src="$publishdir$**\*" target="tools/" />
|
|
||||||
<file src="..\..\..\Web.JS\dist\$configuration$\blazor.*.js" target="tools/blazor" />
|
|
||||||
</files>
|
|
||||||
</package>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="Blazor.MonoRuntime.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>
|
|
||||||
|
|
||||||
<!-- By default, enable auto rebuilds for debug builds. Note that the server will not enable it in production environments regardless. -->
|
|
||||||
<BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug' AND '$(BlazorRebuildOnFileChange)' == ''">true</BlazorRebuildOnFileChange>
|
|
||||||
|
|
||||||
<!-- By default, enable debugging for debug builds. -->
|
|
||||||
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
|
|
||||||
|
|
||||||
<!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. -->
|
|
||||||
<NoRestartServerOnBuild>true</NoRestartServerOnBuild>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
<Project>
|
|
||||||
|
|
||||||
<!-- Require rebuild if the targets change -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)../tools/</BlazorToolsDir>
|
|
||||||
<BlazorBuildExe>dotnet "$(BlazorToolsDir)Microsoft.AspNetCore.Blazor.Build.dll"</BlazorBuildExe>
|
|
||||||
|
|
||||||
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Import Project="Blazor.MonoRuntime.targets" />
|
|
||||||
<Import Project="Publish.targets" />
|
|
||||||
|
|
||||||
<Target Name="GenerateBlazorMetadataFile" BeforeTargets="GetCopyToOutputDirectoryItems">
|
|
||||||
<PropertyGroup>
|
|
||||||
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
|
|
||||||
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
|
|
||||||
</PropertyGroup>
|
|
||||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
|
|
||||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
|
|
||||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/>
|
|
||||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorEnableDebugging)'=='true'" Lines="debug:true" Overwrite="false" Encoding="Unicode"/>
|
|
||||||
<ItemGroup>
|
|
||||||
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
|
||||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
|
||||||
_ClearCurrentStaticWebAssetsForReferenceDiscovery
|
|
||||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Target Name="_ClearCurrentStaticWebAssetsForReferenceDiscovery">
|
|
||||||
<ItemGroup>
|
|
||||||
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<Project>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(BlazorBuildReferenceFromSource)'==''">
|
|
||||||
<BlazorJsPath>$(MSBuildThisFileDirectory)../tools/blazor/blazor.*.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 --exclude-feature sre -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
|
||||||
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
|
|
||||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
|
|
||||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
|
|
||||||
<BaseBlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin/</BaseBlazorRuntimeBinOutputPath>
|
|
||||||
<BaseBlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm/</BaseBlazorRuntimeWasmOutputPath>
|
|
||||||
<BaseBlazorJsOutputPath>$(BaseBlazorRuntimeOutputPath)</BaseBlazorJsOutputPath>
|
|
||||||
<BaseBlazorIntermediateOutputPath>blazor/</BaseBlazorIntermediateOutputPath>
|
|
||||||
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
|
|
||||||
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
|
|
||||||
<BlazorBootJsonOutputPath>$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)</BlazorBootJsonOutputPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,653 +0,0 @@
|
||||||
<Project>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_BlazorCopyFilesToOutputDirectory"
|
|
||||||
DependsOnTargets="PrepareBlazorOutputs"
|
|
||||||
Inputs="@(BlazorItemOutput)"
|
|
||||||
Outputs="@(BlazorItemOutput->'%(TargetOutputPath)')"
|
|
||||||
AfterTargets="CopyFilesToOutputDirectory"
|
|
||||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
|
||||||
|
|
||||||
<!-- Copy the blazor output files -->
|
|
||||||
<Copy
|
|
||||||
SourceFiles="@(BlazorItemOutput)"
|
|
||||||
DestinationFiles="@(BlazorItemOutput->'%(TargetOutputPath)')"
|
|
||||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
|
||||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
|
||||||
Retries="$(CopyRetryCount)"
|
|
||||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
|
||||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
|
||||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
|
|
||||||
Condition="'@(BlazorItemOutput)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
|
|
||||||
</Copy>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_BlazorTrackResolveReferencesDidRun" AfterTargets="ResolveReferences">
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- So we know we can trust @(ReferenceCopyLocalPaths) later -->
|
|
||||||
<_BlazorResolveReferencesDidRun>true</_BlazorResolveReferencesDidRun>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_BlazorBuildReport"
|
|
||||||
AfterTargets="_BlazorCopyFilesToOutputDirectory">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<_BlazorStatisticsOutput Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup>
|
|
||||||
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' == ''">normal</_BlazorStatisticsReportImportance>
|
|
||||||
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' != ''">high</_BlazorStatisticsReportImportance>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Message Importance="high" Text="Blazor Build result -> @(_BlazorStatisticsOutput->Distinct()->Count()) files in $(TargetDir)dist" />
|
|
||||||
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<!-- Preparing blazor files for output:
|
|
||||||
PrepareBlazorOutputs
|
|
||||||
_PrepareBlazorOutputConfiguration
|
|
||||||
_DefineBlazorCommonInputs
|
|
||||||
_BlazorResolveOutputBinaries
|
|
||||||
When link on build:
|
|
||||||
_GenerateLinkerDescriptor
|
|
||||||
_CollectBlazorLinkerDescriptors
|
|
||||||
_LinkBlazorApplication
|
|
||||||
_CollectLinkerOutputs
|
|
||||||
When don't link on build:
|
|
||||||
_CollectResolvedAssemblies
|
|
||||||
_ResolveBlazorApplicationAssemblies
|
|
||||||
_ReadResolvedBlazorApplicationAssemblies
|
|
||||||
_IntermediateCopyBlazorApplicationAssemblies
|
|
||||||
_TouchBlazorApplicationAssemblies
|
|
||||||
_GenerateBlazorBootJson
|
|
||||||
_BlazorCopyFilesToOutputDirectory
|
|
||||||
|
|
||||||
The process for doing builds goes as follows:
|
|
||||||
Produce a hash file with the Hash SDK task and write that hash to a marker file.
|
|
||||||
Produce a marker file that saves whether we are linking or not in this build so that we can take that as
|
|
||||||
input in future builds and do the correct thing for incremental builds.
|
|
||||||
We only produce marker files when the input changes, if the input doesn't change the marker stays the
|
|
||||||
same.
|
|
||||||
|
|
||||||
If we are linking on this build the process is as follows:
|
|
||||||
1) We determine if there are linker descriptors available, if not generate one.
|
|
||||||
2) Collect the list of linker descriptors and create a marker for the linker if it doesn't exist or changed
|
|
||||||
from a previous build.
|
|
||||||
3) Run the linker in case the linker inputs marker is newer than the linker result file.
|
|
||||||
4) Collect the outputs from the linker.
|
|
||||||
|
|
||||||
If we are not linking in this build the process is as follows:
|
|
||||||
1) Resolve the assemblies for the application only if the inputs marker is newer than the resolved assemblies
|
|
||||||
result file.
|
|
||||||
2) Read the result file with the resolved assemblies.
|
|
||||||
3) Copy the resolved assemblies to an intermediate folder.
|
|
||||||
4) In case we are switching from linking to not linking, touch the files in the intermediate folder to ensure
|
|
||||||
that updated versions of the files get copied to the output folder.
|
|
||||||
|
|
||||||
Once the binary outputs are resolved:
|
|
||||||
1) Create a marker file with the resolved assemblies and the boot json data as inputs.
|
|
||||||
2) If the marker file is newer than the boot json in the output folder, regenerate the
|
|
||||||
boot json
|
|
||||||
|
|
||||||
Once all the outputs are resolved (static content + binary outputs + boot json)
|
|
||||||
Copy all the files to the output folder.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<PrepareBlazorOutputs>
|
|
||||||
_PrepareBlazorOutputConfiguration;
|
|
||||||
_DefineBlazorCommonInputs;
|
|
||||||
_BlazorResolveOutputBinaries;
|
|
||||||
_GenerateBlazorBootJson;
|
|
||||||
</PrepareBlazorOutputs>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Target Name="PrepareBlazorOutputs" DependsOnTargets="$(PrepareBlazorOutputs)" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Prepare blazor outputs preamble:
|
|
||||||
* Creates updated marker files (if necessary) for incremental builds.
|
|
||||||
* Computes intermediate and final output paths.
|
|
||||||
* Computes the list of static items to copy to the output folder.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<Target Name="_PrepareBlazorOutputConfiguration">
|
|
||||||
<!--
|
|
||||||
This task produces all the "final" paths for all the files we need to produce the final output.
|
|
||||||
|
|
||||||
The final folder is something like bin/<<Configuration>>/<<TargetFramework>>/dist
|
|
||||||
/_framework/_bin <- This will contain either the BCL + app assemblies or the result of linking the app.
|
|
||||||
/_framework/wasm <- This will contain the wsm runtime copied from the nuget package.
|
|
||||||
/_framework/blazor.js <- This is the blazor.js file copied from the nuget package.
|
|
||||||
/_framework/blazor.boot.json <- This is the boot json file
|
|
||||||
|
|
||||||
This task also defines some intermediate paths that we will use:
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker <- This will be used to create the output from the linker.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt <- This will be used to save the output files from
|
|
||||||
the linker and use that as marker to identify whether or not we need to run the linker.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker.descriptor.xml <- This will be used to generate an XML descriptor
|
|
||||||
for the mono linker.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache <- This is the marker file to track the inputs common
|
|
||||||
inputs to the output generation process.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt <- Paths to all the copy-local referenced assemblies found
|
|
||||||
during the build process (i.e., the @(ReferenceCopyLocalPaths) values). We need this because when publishing, the build doesn't
|
|
||||||
necessarily also run so this is the only way we know which assemblies to include in linking/resolveassemblies.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache <- This is the marker file to track the
|
|
||||||
switch from linking to not linking and viceversa.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache <- This is the marker file to track the inputs
|
|
||||||
to the linker.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ <- This will be used to store the resolved assemblies
|
|
||||||
before copying them to the output when linking is not enabled.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt <- This keeps track of all the resolved assemblies.
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json <- The generated boot json file
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache <- The marker file that track whether boot json needs to
|
|
||||||
be regenerated.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<PropertyGroup Label="Build properties">
|
|
||||||
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'false'"></_BlazorShouldLinkApplicationAssemblies>
|
|
||||||
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'true'">true</_BlazorShouldLinkApplicationAssemblies>
|
|
||||||
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup Label="Static content to copy to the output folder">
|
|
||||||
<MonoWasmFile Include="$(MonoWasmRuntimePath)**/*.*" />
|
|
||||||
<BlazorJsFile Include="$(BlazorJsPath)" />
|
|
||||||
<BlazorItemOutput Include="@(MonoWasmFile)">
|
|
||||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>WebAssembly</Type>
|
|
||||||
<IsStatic>true</IsStatic>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<BlazorItemOutput Include="@(BlazorJsFile)">
|
|
||||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorJsOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>BlazorRuntime</Type>
|
|
||||||
<IsStatic>true</IsStatic>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Error Condition="'@(BlazorJsFile->Count())' == '0'" Text="No JS files found in '$(BlazorJsPath)'" />
|
|
||||||
|
|
||||||
<ItemGroup Label="Static content supplied by NuGet packages">
|
|
||||||
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
|
|
||||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</_BlazorPackageContentOutput>
|
|
||||||
<BlazorItemOutput Include="@(_BlazorPackageContentOutput)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Label="Intermediate output paths">
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
|
|
||||||
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)$(BaseBlazorIntermediateOutputPath)</BlazorIntermediateOutputPath>
|
|
||||||
<BlazorIntermediateOutputPath Condition="! $([System.IO.Path]::IsPathRooted($(BlazorIntermediateOutputPath)))">$([MSBuild]::Escape($([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(BlazorIntermediateOutputPath)'))'))))</BlazorIntermediateOutputPath>
|
|
||||||
|
|
||||||
<!-- Common marker files paths -->
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache -->
|
|
||||||
<BlazorBuildCommonInputsCache>$(BlazorIntermediateOutputPath)inputs.basic.cache</BlazorBuildCommonInputsCache>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt -->
|
|
||||||
<BlazorLocalReferencesOutputPath>$(BlazorIntermediateOutputPath)inputs.copylocal.txt</BlazorLocalReferencesOutputPath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache -->
|
|
||||||
<BlazorBuildLinkerSwitchInputsCache>$(BlazorIntermediateOutputPath)inputs.linkerswitch.cache</BlazorBuildLinkerSwitchInputsCache>
|
|
||||||
|
|
||||||
<!-- Linker paths and marker files -->
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache -->
|
|
||||||
<BlazorBuildLinkerInputsCache>$(BlazorIntermediateOutputPath)inputs.linker.cache</BlazorBuildLinkerInputsCache>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
|
|
||||||
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
|
|
||||||
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linked.assemblies.txt -->
|
|
||||||
<BlazorIntermediateLinkerResultFilePath>$(BlazorIntermediateOutputPath)linked.assemblies.txt</BlazorIntermediateLinkerResultFilePath>
|
|
||||||
|
|
||||||
<!-- Resolved assemblies paths and marker files -->
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ -->
|
|
||||||
<BlazorIntermediateResolvedApplicationAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolvedassemblies/</BlazorIntermediateResolvedApplicationAssembliesOutputPath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt -->
|
|
||||||
<BlazorResolvedAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolved.assemblies.txt</BlazorResolvedAssembliesOutputPath>
|
|
||||||
|
|
||||||
<!-- boot json related paths and markers -->
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/ -->
|
|
||||||
<BlazorBootJsonIntermediateOutputDir>$(BlazorIntermediateOutputPath)</BlazorBootJsonIntermediateOutputDir>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
|
|
||||||
<BlazorBootJsonIntermediateOutputPath>$(BlazorBootJsonIntermediateOutputDir)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache -->
|
|
||||||
<BlazorBuildBootJsonInputsCache>$(BlazorIntermediateOutputPath)inputs.bootjson.cache</BlazorBuildBootJsonInputsCache>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolve-dependencies.txt -->
|
|
||||||
<BlazorResolveDependenciesFilePath>$(BlazorIntermediateOutputPath)resolve-dependencies.txt</BlazorResolveDependenciesFilePath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/bootjson-references.txt -->
|
|
||||||
<BlazorBootJsonReferencesFilePath>$(BlazorIntermediateOutputPath)bootjson-references.txt</BlazorBootJsonReferencesFilePath>
|
|
||||||
|
|
||||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/embedded.resources.txt -->
|
|
||||||
<BlazorEmbeddedResourcesConfigFilePath>$(BlazorIntermediateOutputPath)embedded.resources.txt</BlazorEmbeddedResourcesConfigFilePath>
|
|
||||||
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Label="Final output paths">
|
|
||||||
<BlazorRuntimeBinOutputPath>$(TargetDir)$(BaseBlazorRuntimeBinOutputPath)</BlazorRuntimeBinOutputPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_DefineBlazorCommonInputs">
|
|
||||||
<!-- If ResolveReferences hasn't yet run, we must be inside a VS publish process
|
|
||||||
that doesn't also do a build, so use the stored information. -->
|
|
||||||
<ReadLinesFromFile
|
|
||||||
Condition="'$(_BlazorResolveReferencesDidRun)'!='true'"
|
|
||||||
File="$(BlazorLocalReferencesOutputPath)">
|
|
||||||
<Output TaskParameter="Lines" ItemName="_BlazorDependencyInput"/>
|
|
||||||
</ReadLinesFromFile>
|
|
||||||
<ItemGroup Condition="'$(_BlazorResolveReferencesDidRun)'=='true'">
|
|
||||||
<!-- ... otherwise we can get the fresh info from @(ReferenceCopyLocalPaths) -->
|
|
||||||
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<_BlazorCommonInput Include="@(IntermediateAssembly)" />
|
|
||||||
<_BlazorCommonInput Include="@(_BlazorDependencyInput)" />
|
|
||||||
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
|
|
||||||
<_BlazorCommonInput Include="$(BlazorEnableDebugging)" />
|
|
||||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" />
|
|
||||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Hash ItemsToHash="@(_BlazorCommonInput)">
|
|
||||||
<Output TaskParameter="HashResult" PropertyName="_BlazorBuildBasicInputHash" />
|
|
||||||
</Hash>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
Lines="$(_BlazorBuildBasicInputHash)"
|
|
||||||
File="$(BlazorBuildCommonInputsCache)"
|
|
||||||
Overwrite="True"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
Lines="@(_BlazorDependencyInput)"
|
|
||||||
File="$(BlazorLocalReferencesOutputPath)"
|
|
||||||
Overwrite="True"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
<!-- Switch to detect when we switch from linking to not linking and viceversa -->
|
|
||||||
<WriteLinesToFile
|
|
||||||
Lines="@(_BlazorLinkingOption)"
|
|
||||||
File="$(BlazorBuildLinkerSwitchInputsCache)"
|
|
||||||
Overwrite="True"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="$(BlazorBuildLinkerSwitchInputsCache)" />
|
|
||||||
<FileWrites Include="$(BlazorBuildCommonInputsCache)" />
|
|
||||||
<FileWrites Include="$(BlazorLocalReferencesOutputPath)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_BlazorResolveOutputBinaries" DependsOnTargets="_CollectLinkerOutputs;_CollectResolvedAssemblies" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Linker enabled part of the pipeline:
|
|
||||||
|
|
||||||
* If there are no descriptors defined, generate a new linker descriptor.
|
|
||||||
* Collect the list of descriptors and produce a marker file to determine when the
|
|
||||||
inputs to the linker change in future builds.
|
|
||||||
* Invoke the linker if the linker inputs marker file is newer than the linker outputs.
|
|
||||||
* Read the outputs from the linker and add them to the list of blazor outputs.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<_CollectLinkerOutputsDependsOn>
|
|
||||||
_GenerateLinkerDescriptor;
|
|
||||||
_CollectBlazorLinkerDescriptors;
|
|
||||||
_LinkBlazorApplication
|
|
||||||
</_CollectLinkerOutputsDependsOn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_CollectLinkerOutputs"
|
|
||||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''"
|
|
||||||
DependsOnTargets="$(_CollectLinkerOutputsDependsOn)">
|
|
||||||
<!--
|
|
||||||
Read the outputs from the linker (from this run or a previous run) and set them in an item group for
|
|
||||||
later use.
|
|
||||||
-->
|
|
||||||
<ReadLinesFromFile File="$(BlazorIntermediateLinkerResultFilePath)">
|
|
||||||
<Output TaskParameter="Lines" ItemName="_OptimizedFiles"/>
|
|
||||||
</ReadLinesFromFile>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.dll'))">
|
|
||||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>Assembly</Type>
|
|
||||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.pdb'))">
|
|
||||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>Pdb</Type>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_GenerateLinkerDescriptor"
|
|
||||||
Inputs="$(BlazorBuildCommonInputsCache)"
|
|
||||||
Outputs="$(GeneratedBlazorLinkerDescriptor)"
|
|
||||||
Condition="$(_BlazorShouldLinkApplicationAssemblies) != '' and '@(BlazorLinkerDescriptor)' == ''">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
|
|
||||||
<_GeneratedLinkerDescriptorLine Include="<linker>" />
|
|
||||||
<_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" />
|
|
||||||
<_GeneratedLinkerDescriptorLine Include="</linker>" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
Lines="@(_GeneratedLinkerDescriptorLine)"
|
|
||||||
File="$(GeneratedBlazorLinkerDescriptor)"
|
|
||||||
Overwrite="true"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_CollectBlazorLinkerDescriptors">
|
|
||||||
|
|
||||||
<ItemGroup Condition="@(BlazorLinkerDescriptor) == ''">
|
|
||||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
|
||||||
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
|
|
||||||
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<_BlazorLinkerInput Include="@(IntermediateAssembly)" />
|
|
||||||
<_BlazorLinkerInput Include="@(_BlazorDependencyInput)" />
|
|
||||||
<_BlazorLinkerInput Include="@(BlazorLinkerDescriptor)" />
|
|
||||||
<_BlazorLinkerInput Include="$(AdditionalMonoLinkerOptions)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Hash ItemsToHash="@(_BlazorLinkerInput)">
|
|
||||||
<Output TaskParameter="HashResult" PropertyName="_BlazorLinkerInputHash" />
|
|
||||||
</Hash>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
Lines="$(_BlazorLinkerInputHash)"
|
|
||||||
File="$(BlazorBuildLinkerInputsCache)"
|
|
||||||
Overwrite="True"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="$(BlazorBuildLinkerInputsCache)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_LinkBlazorApplication"
|
|
||||||
Condition="$(_BlazorShouldLinkApplicationAssemblies) != ''"
|
|
||||||
Inputs="$(BlazorBuildLinkerInputsCache);
|
|
||||||
@(IntermediateAssembly);
|
|
||||||
@(_BlazorDependencyInput);
|
|
||||||
@(BlazorLinkerDescriptor)"
|
|
||||||
Outputs="$(BlazorIntermediateLinkerResultFilePath)"
|
|
||||||
>
|
|
||||||
<!--
|
|
||||||
At this point we have decided to run the mono linker on the Blazor assembly and its dependencies.
|
|
||||||
The steps to run the mono linker are the following:
|
|
||||||
1) Clear the linker output directory if not clean before hand, as we don't know what the outputs of
|
|
||||||
the linker will be.
|
|
||||||
2) Run the linker on the main assembly, its dependencies and pass in the BCL folders to do the lookup
|
|
||||||
for framework assemblies.
|
|
||||||
3) Once we've run the linker we need to capture the produced output and generate a marker file containing
|
|
||||||
the list of produced files. This file will act as a marker to skip running the linker if none of the inputs
|
|
||||||
has changed.
|
|
||||||
4) Add the file we just created to the list of file writes, to support incremental builds.
|
|
||||||
-->
|
|
||||||
<ItemGroup>
|
|
||||||
<_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath);$(MonoWasmFrameworkPath)" />
|
|
||||||
<_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a "%(Identity)"')" />
|
|
||||||
<_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a "%(FullPath)"')" />
|
|
||||||
<_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d "%(Identity)"')" />
|
|
||||||
<_BlazorAssemblyDescriptorFiles
|
|
||||||
Include="@(BlazorLinkerDescriptor->'-x "%(FullPath)"')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
|
|
||||||
<Delete Files="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
|
||||||
|
|
||||||
<!-- Run the linker and put the results in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
|
|
||||||
<Exec Command="dotnet "$(MonoLinkerPath)" $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o "$(BlazorIntermediateLinkerOutputPath)" @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToLink, ' ')" />
|
|
||||||
|
|
||||||
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
|
|
||||||
<ItemGroup>
|
|
||||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
|
||||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Write the list of files in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ into
|
|
||||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt
|
|
||||||
-->
|
|
||||||
<WriteLinesToFile
|
|
||||||
File="$(BlazorIntermediateLinkerResultFilePath)"
|
|
||||||
Lines="@(_BlazorLinkerOutput)"
|
|
||||||
Overwrite="true" />
|
|
||||||
|
|
||||||
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt to the list of written files. -->
|
|
||||||
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/*.dll to the list of written files. -->
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="$(BlazorIntermediateLinkerResultFilePath)" />
|
|
||||||
<FileWrites Include="@(_BlazorLinkerOutput)" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Linker disabled part of the pipeline:
|
|
||||||
|
|
||||||
* Run a CLI tool to produce the transitive closure of application references using the main application
|
|
||||||
as entry point.
|
|
||||||
* Read the list of resolved application references from the file produced by the previous step.
|
|
||||||
* Copy the resolved application references into an intermediate folder.
|
|
||||||
* If we are switching from linking to not linking
|
|
||||||
Touch the files in the intermediate folder to ensure they are copied to the output and replace
|
|
||||||
the linked versions with the same name.
|
|
||||||
* Collect the list of resolved assemblies in the intermediate output folder and prepare them to be
|
|
||||||
copied to their final destination in the output folder.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<_CollectResolvedAssembliesDependsOn>
|
|
||||||
_ResolveBlazorApplicationAssemblies;
|
|
||||||
_ReadResolvedBlazorApplicationAssemblies;
|
|
||||||
_IntermediateCopyBlazorApplicationAssemblies;
|
|
||||||
_TouchBlazorApplicationAssemblies
|
|
||||||
</_CollectResolvedAssembliesDependsOn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_CollectResolvedAssemblies"
|
|
||||||
DependsOnTargets="$(_CollectResolvedAssembliesDependsOn)"
|
|
||||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.dll'))">
|
|
||||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>Assembly</Type>
|
|
||||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.pdb'))">
|
|
||||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
<Type>Pdb</Type>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_ResolveBlazorApplicationAssemblies"
|
|
||||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''"
|
|
||||||
Inputs="$(BlazorBuildCommonInputsCache);
|
|
||||||
@(IntermediateAssembly);
|
|
||||||
@(_BlazorDependencyInput)"
|
|
||||||
Outputs="$(BlazorResolvedAssembliesOutputPath)"
|
|
||||||
>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references "$(BlazorResolveDependenciesFilePath)"</_ReferencesArg>
|
|
||||||
<_BclParameter>--base-class-library "$(MonoBaseClassLibraryPath)" --base-class-library "$(MonoBaseClassLibraryFacadesPath)" --base-class-library "$(MonoWasmFrameworkPath)"</_BclParameter>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
File="$(BlazorResolveDependenciesFilePath)"
|
|
||||||
Lines="@(_BlazorDependencyInput)"
|
|
||||||
Overwrite="true" />
|
|
||||||
|
|
||||||
<Exec Command="$(BlazorBuildExe) resolve-dependencies "@(IntermediateAssembly->'%(FullPath)')" $(_ReferencesArg) $(_BclParameter) --output "$(BlazorResolvedAssembliesOutputPath)"" />
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="_ReadResolvedBlazorApplicationAssemblies">
|
|
||||||
|
|
||||||
<ReadLinesFromFile File="$(BlazorResolvedAssembliesOutputPath)">
|
|
||||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
|
|
||||||
</ReadLinesFromFile>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="$(BlazorResolvedAssembliesOutputPath)" />
|
|
||||||
<FileWrites Include="@(_IntermediateResolvedRuntimeDependencies)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_IntermediateCopyBlazorApplicationAssemblies"
|
|
||||||
Inputs="@(_BlazorResolvedRuntimeDependencies)"
|
|
||||||
Outputs="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')">
|
|
||||||
|
|
||||||
<Copy
|
|
||||||
SourceFiles="@(_BlazorResolvedRuntimeDependencies)"
|
|
||||||
DestinationFiles="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')"
|
|
||||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
|
||||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
|
||||||
Retries="$(CopyRetryCount)"
|
|
||||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
|
||||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
|
||||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" />
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_TouchBlazorApplicationAssemblies"
|
|
||||||
Inputs="$(BlazorBuildLinkerSwitchInputsCache)"
|
|
||||||
Outputs="@(_IntermediateResolvedRuntimeDependencies)">
|
|
||||||
|
|
||||||
<Touch Files="@(_IntermediateResolvedRuntimeDependencies)" ForceTouch="true" />
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Final part of the build pipeline:
|
|
||||||
|
|
||||||
* Collect the blazor application assemblies to be copied to the output and create a marker file.
|
|
||||||
* Call our CLI tool to generate the boot json if the list of assemblies has changed.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<Target Name="_ResolveBlazorBootJsonInputs">
|
|
||||||
<ItemGroup>
|
|
||||||
<BlazorBootJsonInput Include="$(Configuration)" />
|
|
||||||
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
|
|
||||||
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FullPath)')" />
|
|
||||||
<BlazorBootJsonInput Include="@(_BlazorLinkingOption)" />
|
|
||||||
<BlazorBootJsonInput Include="$(BlazorEnableDebugging)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
File="$(BlazorBuildBootJsonInputsCache)"
|
|
||||||
Lines="@(BlazorBootJsonInput)"
|
|
||||||
Overwrite="true"
|
|
||||||
WriteOnlyWhenDifferent="True" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FileWrites Include="$(BlazorBuildBootJsonInputsCache)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target
|
|
||||||
Name="_GenerateBlazorBootJson"
|
|
||||||
DependsOnTargets="_ResolveBlazorBootJsonInputs"
|
|
||||||
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
|
|
||||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
|
||||||
<ItemGroup>
|
|
||||||
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
|
|
||||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
|
|
||||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup>
|
|
||||||
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
|
|
||||||
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)"</_ReferencesArg>
|
|
||||||
<_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources "$(BlazorEmbeddedResourcesConfigFilePath)"</_EmbeddedResourcesArg>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
File="$(BlazorBootJsonReferencesFilePath)"
|
|
||||||
Lines="@(_AppReferences)"
|
|
||||||
Overwrite="true" />
|
|
||||||
|
|
||||||
<WriteLinesToFile
|
|
||||||
Condition="'@(_UnlinkedAppReferencesPaths)' != ''"
|
|
||||||
File="$(BlazorEmbeddedResourcesConfigFilePath)"
|
|
||||||
Lines="@(_UnlinkedAppReferencesPaths)"
|
|
||||||
Overwrite="true" />
|
|
||||||
|
|
||||||
<Exec Command="$(BlazorBuildExe) write-boot-json "@(IntermediateAssembly)" $(_ReferencesArg) $(_EmbeddedResourcesArg) $(_LinkerEnabledFlag) --output "$(BlazorBootJsonIntermediateOutputPath)"" />
|
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
|
|
||||||
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
|
||||||
<_BlazorBootJsonEmbeddedContentFile Include="$(BlazorBootJsonIntermediateOutputDir)_content\**\*.*" />
|
|
||||||
<BlazorItemOutput Include="@(_BlazorBootJson)">
|
|
||||||
<TargetOutputPath>$(TargetDir)$(BlazorBootJsonOutputPath)</TargetOutputPath>
|
|
||||||
<Type>BootJson</Type>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<BlazorItemOutput Include="@(_BlazorBootJsonEmbeddedContentFile)">
|
|
||||||
<TargetOutputPath>$(TargetDir)dist/_content/%(RecursiveDir)%(FileName)%(Extension)</TargetOutputPath>
|
|
||||||
</BlazorItemOutput>
|
|
||||||
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
|
||||||
<FileWrites Include="@(_BlazorBootJsonEmbeddedContentFile)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,62 +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 -->
|
|
||||||
<_BlazorGCTPDIDistFiles Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
|
||||||
<_BlazorGCTPDI Include="@(_BlazorGCTPDIDistFiles)">
|
|
||||||
<TargetPath>$(BlazorPublishDistDir)$([MSBuild]::MakeRelative('$(TargetDir)dist\', %(Identity)))</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>
|
|
||||||
<WriteLinesToFile File="$(_BlazorConfigPath)" Lines="." Overwrite="true" />
|
|
||||||
<WriteLinesToFile File="$(_BlazorConfigPath)" Lines="$(AssemblyName)/" Overwrite="false" />
|
|
||||||
</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,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,62 +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 Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|
||||||
{
|
|
||||||
public class BootJsonWriterTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void ProducesJsonReferencingAssemblyAndDependencies()
|
|
||||||
{
|
|
||||||
// Arrange/Act
|
|
||||||
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
|
|
||||||
var content = BootJsonWriter.GetBootJsonContent(
|
|
||||||
"MyApp.Entrypoint.dll",
|
|
||||||
"MyNamespace.MyType::MyMethod",
|
|
||||||
assemblyReferences,
|
|
||||||
Enumerable.Empty<EmbeddedResourceInfo>(),
|
|
||||||
linkerEnabled: true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
|
||||||
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value<string>());
|
|
||||||
Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value<string>());
|
|
||||||
Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IncludesReferencesToEmbeddedContent()
|
|
||||||
{
|
|
||||||
// Arrange/Act
|
|
||||||
var embeddedContent = new[]
|
|
||||||
{
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"),
|
|
||||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"),
|
|
||||||
};
|
|
||||||
var content = BootJsonWriter.GetBootJsonContent(
|
|
||||||
"MyApp.Entrypoint",
|
|
||||||
"MyNamespace.MyType::MyMethod",
|
|
||||||
assemblyReferences: new[] { "Something.dll" },
|
|
||||||
embeddedContent: embeddedContent,
|
|
||||||
linkerEnabled: true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
|
||||||
Assert.Equal(
|
|
||||||
new[] { "css/first.css", "css/second.css" },
|
|
||||||
parsedContent["cssReferences"].Values<string>());
|
|
||||||
Assert.Equal(
|
|
||||||
new[] { "javascript/first.js", "javascript/second.js" },
|
|
||||||
parsedContent["jsReferences"].Values<string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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,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,7 +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,17 +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>
|
|
||||||
</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,25 +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>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
|
||||||
<Reference Include="Microsoft.AspNetCore.SpaServices.Extensions" />
|
|
||||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
|
||||||
<Reference Include="Microsoft.AspNetCore.WebSockets" />
|
|
||||||
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
|
|
||||||
<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,217 +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.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using WsProxy;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides infrastructure for debugging Blazor applications.
|
|
||||||
/// </summary>
|
|
||||||
public static class BlazorMonoDebugProxyAppBuilderExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Adds middleware for needed for debugging Blazor applications
|
|
||||||
/// inside Chromium dev tools.
|
|
||||||
/// </summary>
|
|
||||||
public static void UseBlazorDebugging(this IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
app.UseWebSockets();
|
|
||||||
|
|
||||||
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 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 = "http://localhost:9222";
|
|
||||||
var debuggerTabsListUrl = $"{debuggerHost}/json";
|
|
||||||
IEnumerable<BrowserTab> availableTabs;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
availableTabs = await GetOpenedBrowserTabs(debuggerHost);
|
|
||||||
}
|
|
||||||
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 devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?ws={proxyEndpoint}";
|
|
||||||
context.Response.Redirect(devToolsUrlWithProxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetLaunchChromeInstructions(string appRootUrl)
|
|
||||||
{
|
|
||||||
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
|
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
return $@"<p>Press Win+R and enter the following:</p>
|
|
||||||
<p><strong><code>chrome --remote-debugging-port=9222 --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=9222 --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=9222 --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-chrome-debug");
|
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
return $@"<p>Press Win+R and enter the following:</p>
|
|
||||||
<p><strong><code>msedge --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {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=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unknown OS platform");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs(string debuggerHost)
|
|
||||||
{
|
|
||||||
using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
|
|
||||||
{
|
|
||||||
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
|
|
||||||
return JsonConvert.DeserializeObject<BrowserTab[]>(jsonResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowserTab
|
|
||||||
{
|
|
||||||
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,771 +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);
|
|
||||||
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 RuntimeReady (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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
|
|
||||||
|
|
||||||
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_list.Add(JObject.FromObject(new
|
|
||||||
{
|
|
||||||
name = fieldName,
|
|
||||||
value = values[i+1]["value"]
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
|
||||||
o = JObject.FromObject(new
|
|
||||||
{
|
|
||||||
result = var_list
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = values [i] ["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_list.Add (JObject.FromObject (new {
|
|
||||||
name = name,
|
|
||||||
value = values [i+1] ["value"]
|
|
||||||
}));
|
|
||||||
i = i + 2;
|
|
||||||
}
|
|
||||||
o = JObject.FromObject (new {
|
|
||||||
result = var_list
|
|
||||||
});
|
|
||||||
SendResponse (msg_id, Result.Ok (o), 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,323 +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;
|
|
||||||
|
|
||||||
namespace WsProxy {
|
|
||||||
|
|
||||||
internal struct Result {
|
|
||||||
public JObject Value { get; private set; }
|
|
||||||
public JObject Error { get; private set; }
|
|
||||||
|
|
||||||
public bool IsOk => Value != null;
|
|
||||||
public bool IsErr => Error != null;
|
|
||||||
|
|
||||||
Result (JObject result, JObject error)
|
|
||||||
{
|
|
||||||
this.Value = result;
|
|
||||||
this.Error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Result FromJson (JObject obj)
|
|
||||||
{
|
|
||||||
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Result Ok (JObject ok)
|
|
||||||
{
|
|
||||||
return new Result (ok, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Result Err (JObject err)
|
|
||||||
{
|
|
||||||
return new Result (null, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JObject ToJObject (int id) {
|
|
||||||
if (IsOk) {
|
|
||||||
return JObject.FromObject (new {
|
|
||||||
id = id,
|
|
||||||
result = Value
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return JObject.FromObject (new {
|
|
||||||
id = id,
|
|
||||||
error = Error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WsQueue {
|
|
||||||
Task current_send;
|
|
||||||
List<byte []> pending;
|
|
||||||
|
|
||||||
public WebSocket Ws { get; private set; }
|
|
||||||
public Task CurrentSend { get { return current_send; } }
|
|
||||||
public WsQueue (WebSocket sock)
|
|
||||||
{
|
|
||||||
this.Ws = sock;
|
|
||||||
pending = new List<byte []> ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Send (byte [] bytes, CancellationToken token)
|
|
||||||
{
|
|
||||||
pending.Add (bytes);
|
|
||||||
if (pending.Count == 1) {
|
|
||||||
if (current_send != null)
|
|
||||||
throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send");
|
|
||||||
//Console.WriteLine ("sending {0} bytes", bytes.Length);
|
|
||||||
current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
|
|
||||||
return current_send;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Pump (CancellationToken token)
|
|
||||||
{
|
|
||||||
current_send = null;
|
|
||||||
pending.RemoveAt (0);
|
|
||||||
|
|
||||||
if (pending.Count > 0) {
|
|
||||||
if (current_send != null)
|
|
||||||
throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send");
|
|
||||||
//Console.WriteLine ("sending more {0} bytes", pending[0].Length);
|
|
||||||
current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token);
|
|
||||||
return current_send;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class WsProxy {
|
|
||||||
TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
|
|
||||||
List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> ();
|
|
||||||
ClientWebSocket browser;
|
|
||||||
WebSocket ide;
|
|
||||||
int next_cmd_id;
|
|
||||||
List<Task> pending_ops = new List<Task> ();
|
|
||||||
List<WsQueue> queues = new List<WsQueue> ();
|
|
||||||
|
|
||||||
protected virtual Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
return Task.FromResult (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
return Task.FromResult (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<string> ReadOne (WebSocket socket, CancellationToken token)
|
|
||||||
{
|
|
||||||
byte [] buff = new byte [4000];
|
|
||||||
var mem = new MemoryStream ();
|
|
||||||
while (true) {
|
|
||||||
var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
|
|
||||||
if (result.MessageType == WebSocketMessageType.Close) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.EndOfMessage) {
|
|
||||||
mem.Write (buff, 0, result.Count);
|
|
||||||
return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length);
|
|
||||||
} else {
|
|
||||||
mem.Write (buff, 0, result.Count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WsQueue GetQueueForSocket (WebSocket ws)
|
|
||||||
{
|
|
||||||
return queues.FirstOrDefault (q => q.Ws == ws);
|
|
||||||
}
|
|
||||||
|
|
||||||
WsQueue GetQueueForTask (Task task) {
|
|
||||||
return queues.FirstOrDefault (q => q.CurrentSend == task);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Send (WebSocket to, JObject o, CancellationToken token)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
|
|
||||||
|
|
||||||
var queue = GetQueueForSocket (to);
|
|
||||||
var task = queue.Send (bytes, token);
|
|
||||||
if (task != null)
|
|
||||||
pending_ops.Add (task);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task OnEvent (string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (!await AcceptEvent (method, args, token)) {
|
|
||||||
//Console.WriteLine ("proxy browser: {0}::{1}",method, args);
|
|
||||||
SendEventInternal (method, args, token);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
side_exception.TrySetException (e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task OnCommand (int id, string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (!await AcceptCommand (id, method, args, token)) {
|
|
||||||
var res = await SendCommandInternal (method, args, token);
|
|
||||||
SendResponseInternal (id, res, token);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
side_exception.TrySetException (e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnResponse (int id, Result result)
|
|
||||||
{
|
|
||||||
//Console.WriteLine ("got id {0} res {1}", id, result);
|
|
||||||
var idx = pending_cmds.FindIndex (e => e.Item1 == id);
|
|
||||||
var item = pending_cmds [idx];
|
|
||||||
pending_cmds.RemoveAt (idx);
|
|
||||||
|
|
||||||
item.Item2.SetResult (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessBrowserMessage (string msg, CancellationToken token)
|
|
||||||
{
|
|
||||||
// Debug ($"browser: {msg}");
|
|
||||||
var res = JObject.Parse (msg);
|
|
||||||
|
|
||||||
if (res ["id"] == null)
|
|
||||||
pending_ops.Add (OnEvent (res ["method"].Value<string> (), res ["params"] as JObject, token));
|
|
||||||
else
|
|
||||||
OnResponse (res ["id"].Value<int> (), Result.FromJson (res));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessIdeMessage (string msg, CancellationToken token)
|
|
||||||
{
|
|
||||||
var res = JObject.Parse (msg);
|
|
||||||
|
|
||||||
pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) {
|
|
||||||
// Debug ($"sending command {method}: {args}");
|
|
||||||
return await SendCommandInternal (method, args, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<Result> SendCommandInternal (string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
int id = ++next_cmd_id;
|
|
||||||
|
|
||||||
var o = JObject.FromObject (new {
|
|
||||||
id = id,
|
|
||||||
method = method,
|
|
||||||
@params = args
|
|
||||||
});
|
|
||||||
var tcs = new TaskCompletionSource<Result> ();
|
|
||||||
//Console.WriteLine ("add cmd id {0}", id);
|
|
||||||
pending_cmds.Add ((id, tcs));
|
|
||||||
|
|
||||||
Send (this.browser, o, token);
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendEvent (string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
//Debug ($"sending event {method}: {args}");
|
|
||||||
SendEventInternal (method, args, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendEventInternal (string method, JObject args, CancellationToken token)
|
|
||||||
{
|
|
||||||
var o = JObject.FromObject (new {
|
|
||||||
method = method,
|
|
||||||
@params = args
|
|
||||||
});
|
|
||||||
|
|
||||||
Send (this.ide, o, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SendResponse (int id, Result result, CancellationToken token)
|
|
||||||
{
|
|
||||||
//Debug ($"sending response: {id}: {result.ToJObject (id)}");
|
|
||||||
SendResponseInternal (id, result, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendResponseInternal (int id, Result result, CancellationToken token)
|
|
||||||
{
|
|
||||||
JObject o = result.ToJObject (id);
|
|
||||||
|
|
||||||
Send (this.ide, o, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// , HttpContext context)
|
|
||||||
public async Task Run (Uri browserUri, WebSocket ideSocket)
|
|
||||||
{
|
|
||||||
Debug ("wsproxy start");
|
|
||||||
using (this.ide = ideSocket) {
|
|
||||||
Debug ("ide connected");
|
|
||||||
queues.Add (new WsQueue (this.ide));
|
|
||||||
using (this.browser = new ClientWebSocket ()) {
|
|
||||||
this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
|
|
||||||
await this.browser.ConnectAsync (browserUri, CancellationToken.None);
|
|
||||||
queues.Add (new WsQueue (this.browser));
|
|
||||||
|
|
||||||
Debug ("client connected");
|
|
||||||
var x = new CancellationTokenSource ();
|
|
||||||
|
|
||||||
pending_ops.Add (ReadOne (browser, x.Token));
|
|
||||||
pending_ops.Add (ReadOne (ide, x.Token));
|
|
||||||
pending_ops.Add (side_exception.Task);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!x.IsCancellationRequested) {
|
|
||||||
var task = await Task.WhenAny (pending_ops.ToArray ());
|
|
||||||
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
|
||||||
if (task == pending_ops [0]) {
|
|
||||||
var msg = ((Task<string>)task).Result;
|
|
||||||
pending_ops [0] = ReadOne (browser, x.Token); //queue next read
|
|
||||||
ProcessBrowserMessage (msg, x.Token);
|
|
||||||
} else if (task == pending_ops [1]) {
|
|
||||||
var msg = ((Task<string>)task).Result;
|
|
||||||
pending_ops [1] = ReadOne (ide, x.Token); //queue next read
|
|
||||||
ProcessIdeMessage (msg, x.Token);
|
|
||||||
} else if (task == pending_ops [2]) {
|
|
||||||
var res = ((Task<bool>)task).Result;
|
|
||||||
throw new Exception ("side task must always complete with an exception, what's going on???");
|
|
||||||
} else {
|
|
||||||
//must be a background task
|
|
||||||
pending_ops.Remove (task);
|
|
||||||
var queue = GetQueueForTask (task);
|
|
||||||
if (queue != null) {
|
|
||||||
var tsk = queue.Pump (x.Token);
|
|
||||||
if (tsk != null)
|
|
||||||
pending_ops.Add (tsk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Debug ($"got exception {e}");
|
|
||||||
//throw;
|
|
||||||
} finally {
|
|
||||||
x.Cancel ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Debug (string msg)
|
|
||||||
{
|
|
||||||
Console.WriteLine (msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Info (string msg)
|
|
||||||
{
|
|
||||||
Console.WriteLine (msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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,15 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
|
||||||
<IncludeSymbols>false</IncludeSymbols>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Label="Package Versions">
|
|
||||||
<!-- Used only in development when running the template contents directly from source -->
|
|
||||||
<TemplateBlazorPackageVersion>0.8.0-preview-19064-0339</TemplateBlazorPackageVersion>
|
|
||||||
<TemplateComponentsPackageVersion>3.0.0-preview-19064-0339</TemplateComponentsPackageVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<Project>
|
|
||||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
|
||||||
|
|
||||||
<Target Name="SetTemplateJsonSymbolReplacements">
|
|
||||||
<PropertyGroup>
|
|
||||||
<!--
|
|
||||||
Properties here will be injected into the template config *.json files
|
|
||||||
during the build, replacing tokens of the form ${PropertyName}
|
|
||||||
-->
|
|
||||||
<GeneratedContentProperties>
|
|
||||||
TemplateBlazorVersion=$(PackageVersion);
|
|
||||||
TemplateComponentsVersion=$(ComponentsPackageVersion);
|
|
||||||
RepositoryCommit=$(SourceRevisionId);
|
|
||||||
</GeneratedContentProperties>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -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,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
|
||||||
<metadata>
|
|
||||||
$CommonMetadataElements$
|
|
||||||
<packageTypes>
|
|
||||||
<packageType name="Template" />
|
|
||||||
</packageTypes>
|
|
||||||
</metadata>
|
|
||||||
<files>
|
|
||||||
$CommonFileElements$
|
|
||||||
<file
|
|
||||||
src="content/**"
|
|
||||||
exclude="**/bin/**;**/obj/**;**/.template.config.src/**;content/Directory.Build.props;content/Directory.Build.targets;"
|
|
||||||
target="Content" />
|
|
||||||
</files>
|
|
||||||
</package>
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/dotnetcli.host",
|
|
||||||
"symbolInfo": {
|
|
||||||
"skipRestore": {
|
|
||||||
"longName": "no-restore",
|
|
||||||
"shortName": ""
|
|
||||||
},
|
|
||||||
"Hosted": {
|
|
||||||
"longName": "hosted"
|
|
||||||
},
|
|
||||||
"Framework": {
|
|
||||||
"longName": "framework"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
{
|
|
||||||
"author": "Microsoft",
|
|
||||||
"classifications": [
|
|
||||||
"Web",
|
|
||||||
"Blazor",
|
|
||||||
"WebAssembly"
|
|
||||||
],
|
|
||||||
"defaultName": "WebApplication",
|
|
||||||
"description": "A project template for creating a Blazor app that runs on WebAssembly and is optionally hosted by an ASP.NET Core app. This template can be used for web apps with rich dynamic user interfaces (UIs).",
|
|
||||||
"groupIdentity": "Microsoft.Web.Blazor.Wasm",
|
|
||||||
"guids": [
|
|
||||||
"4C26868E-5E7C-458D-82E3-040509D0C71F",
|
|
||||||
"5990939C-7E7B-4CFA-86FF-44CA5756498A",
|
|
||||||
"650B3CE7-2E93-4CC4-9F46-466686815EAA",
|
|
||||||
"0AFFA7FD-4E37-4636-AB91-3753E746DB98"
|
|
||||||
],
|
|
||||||
"identity": "Microsoft.Web.Blazor.Wasm.CSharp",
|
|
||||||
"name": "Blazor WebAssembly App",
|
|
||||||
"preferNameDirectory": true,
|
|
||||||
"primaryOutputs": [
|
|
||||||
{
|
|
||||||
"condition": "(Hosted && (HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\"))",
|
|
||||||
"path": "BlazorWasm-CSharp.sln"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
|
||||||
"path": "Server/BlazorWasm-CSharp.Server.csproj"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(!Hosted)",
|
|
||||||
"path": "BlazorWasm-CSharp.Client.csproj"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
|
||||||
"path": "Client/BlazorWasm-CSharp.Client.csproj"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
|
||||||
"path": "Shared/BlazorWasm-CSharp.Shared.csproj"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shortName": "blazorwasm",
|
|
||||||
"sourceName": "BlazorWasm-CSharp",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"source": "./",
|
|
||||||
"target": "./",
|
|
||||||
"exclude": [
|
|
||||||
".template.config/**"
|
|
||||||
],
|
|
||||||
"modifiers": [
|
|
||||||
{
|
|
||||||
"condition": "(!Hosted)",
|
|
||||||
"exclude": [
|
|
||||||
"Server/**",
|
|
||||||
"Shared/**",
|
|
||||||
"*.sln"
|
|
||||||
],
|
|
||||||
"rename": {
|
|
||||||
".Client.csproj": ".csproj",
|
|
||||||
"Client": "."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(Hosted)",
|
|
||||||
"exclude": [
|
|
||||||
"Client/wwwroot/sample-data/**"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
|
||||||
"exclude": [
|
|
||||||
"*.sln"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"symbols": {
|
|
||||||
"Framework": {
|
|
||||||
"type": "parameter",
|
|
||||||
"description": "The target framework for the project.",
|
|
||||||
"datatype": "choice",
|
|
||||||
"choices": [
|
|
||||||
{
|
|
||||||
"choice": "netcoreapp3.1",
|
|
||||||
"description": "Target netcoreapp3.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"replaces": "netcoreapp3.1",
|
|
||||||
"defaultValue": "netcoreapp3.1"
|
|
||||||
},
|
|
||||||
"HostIdentifier": {
|
|
||||||
"type": "bind",
|
|
||||||
"binding": "HostIdentifier"
|
|
||||||
},
|
|
||||||
"TemplateBlazorVersionSymbol": {
|
|
||||||
"type": "parameter",
|
|
||||||
"datatype": "string",
|
|
||||||
"description": "Specifies which version of Blazor packages to use.",
|
|
||||||
"replaces": "$(TemplateBlazorPackageVersion)",
|
|
||||||
"defaultValue": "${TemplateBlazorVersion}"
|
|
||||||
},
|
|
||||||
"TemplateComponentsVersionSymbol": {
|
|
||||||
"type": "parameter",
|
|
||||||
"datatype": "string",
|
|
||||||
"description": "Specifies which version of Components packages to use.",
|
|
||||||
"replaces": "$(TemplateComponentsPackageVersion)",
|
|
||||||
"defaultValue": "${TemplateComponentsVersion}"
|
|
||||||
},
|
|
||||||
"skipRestore": {
|
|
||||||
"type": "parameter",
|
|
||||||
"datatype": "bool",
|
|
||||||
"description": "If specified, skips the automatic restore of the project on create.",
|
|
||||||
"defaultValue": "false"
|
|
||||||
},
|
|
||||||
"Hosted": {
|
|
||||||
"type": "parameter",
|
|
||||||
"datatype": "bool",
|
|
||||||
"defaultValue": "false",
|
|
||||||
"description": "If specified, includes an ASP.NET Core host for the Blazor app."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"language": "C#",
|
|
||||||
"type": "project"
|
|
||||||
},
|
|
||||||
"postActions": [
|
|
||||||
{
|
|
||||||
"condition": "(!skipRestore && Hosted)",
|
|
||||||
"description": "Restore NuGet packages required by this project.",
|
|
||||||
"manualInstructions": [
|
|
||||||
{
|
|
||||||
"text": "Run 'dotnet restore'"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
|
||||||
"continueOnError": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": "(!skipRestore && !Hosted)",
|
|
||||||
"description": "Restore NuGet packages required by this project.",
|
|
||||||
"manualInstructions": [
|
|
||||||
{
|
|
||||||
"text": "Run 'dotnet restore'"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"args": {
|
|
||||||
"files": [ "BlazorWasm-CSharp.Client.csproj" ]
|
|
||||||
},
|
|
||||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
|
||||||
"continueOnError": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<Router AppAssembly="@typeof(Program).Assembly">
|
|
||||||
<Found Context="routeData">
|
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
|
||||||
</Found>
|
|
||||||
<NotFound>
|
|
||||||
<LayoutView Layout="@typeof(MainLayout)">
|
|
||||||
<p>Sorry, there's nothing at this address.</p>
|
|
||||||
</LayoutView>
|
|
||||||
</NotFound>
|
|
||||||
</Router>
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<RazorLangVersion>3.0</RazorLangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(TemplateBlazorPackageVersion)" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="$(TemplateBlazorPackageVersion)" PrivateAssets="all" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="$(TemplateBlazorPackageVersion)" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="$(TemplateBlazorPackageVersion)" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
<!--#if Hosted -->
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<!--#endif -->
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Blazor.Hosting;
|
|
||||||
|
|
||||||
#if (Hosted)
|
|
||||||
namespace BlazorWasm_CSharp.Client
|
|
||||||
#else
|
|
||||||
namespace BlazorWasm_CSharp
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
CreateHostBuilder(args).Build().Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
|
|
||||||
BlazorWebAssemblyHost.CreateDefaultBuilder()
|
|
||||||
.UseBlazorStartup<Startup>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Components.Builder;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
#if (Hosted)
|
|
||||||
namespace BlazorWasm_CSharp.Client
|
|
||||||
#else
|
|
||||||
namespace BlazorWasm_CSharp
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
public class Startup
|
|
||||||
{
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Configure(IComponentsApplicationBuilder app)
|
|
||||||
{
|
|
||||||
app.AddComponent<App>("app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
@using System.Net.Http
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
|
||||||
@using Microsoft.JSInterop
|
|
||||||
@*#if (!Hosted)
|
|
||||||
@using BlazorWasm_CSharp
|
|
||||||
@using BlazorWasm_CSharp.Shared
|
|
||||||
#else
|
|
||||||
@using BlazorWasm_CSharp.Client
|
|
||||||
@using BlazorWasm_CSharp.Client.Shared
|
|
||||||
#endif*@
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="$(TemplateBlazorPackageVersion)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Client\BlazorWasm-CSharp.Client.csproj" />
|
|
||||||
<ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace BlazorWasm_CSharp.Server
|
|
||||||
{
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
BuildWebHost(args).Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IWebHost BuildWebHost(string[] args) =>
|
|
||||||
WebHost.CreateDefaultBuilder(args)
|
|
||||||
.UseConfiguration(new ConfigurationBuilder()
|
|
||||||
.AddCommandLine(args)
|
|
||||||
.Build())
|
|
||||||
.UseStartup<Startup>()
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue