Merge pull request #22296 from dotnet/prkrishn/merge-into-master
Merge blazor-wasm in to master
This commit is contained in:
commit
78d6ce201f
|
|
@ -0,0 +1,59 @@
|
|||
# Uses Scheduled Triggers, which aren't supported in YAML yet.
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=vsts&tabs=yaml#scheduled
|
||||
|
||||
# Daily Tests for Blazor
|
||||
# These use Sauce Labs resources, hence they run daily rather than per-commit.
|
||||
|
||||
# We just need one Windows machine because all it does is trigger SauceLabs.
|
||||
variables:
|
||||
SAUCE_CONNECT_DOWNLOAD_ON_INSTALL: true
|
||||
E2ETESTS_SauceTest: true
|
||||
E2ETESTS_Sauce__TunnelIdentifier: 'blazor-e2e-sc-proxy-tunnel'
|
||||
E2ETESTS_Sauce__HostName: 'sauce.local'
|
||||
jobs:
|
||||
- template: jobs/default-build.yml
|
||||
parameters:
|
||||
buildDirectory: src/Components
|
||||
isTestingJob: true
|
||||
agentOs: Windows
|
||||
jobName: BlazorDailyTests
|
||||
jobDisplayName: "Blazor Daily Tests"
|
||||
afterBuild:
|
||||
|
||||
# macOS/Safari
|
||||
- script: 'dotnet test --filter "StandaloneAppTest"'
|
||||
workingDirectory: 'src/Components/test/E2ETest'
|
||||
displayName: 'Run Blazor tests - macOS/Safari'
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
# Secrets need to be explicitly mapped to env variables.
|
||||
E2ETESTS_Sauce__Username: '$(asplab-sauce-labs-username)'
|
||||
E2ETESTS_Sauce__AccessKey: '$(asplab-sauce-labs-access-key)'
|
||||
# Set platform/browser configuration.
|
||||
E2ETESTS_Sauce__TestName: 'Blazor Daily Tests - macOS/Safari'
|
||||
E2ETESTS_Sauce__PlatformName: 'macOS 10.14'
|
||||
E2ETESTS_Sauce__BrowserName: 'Safari'
|
||||
# Need to explicitly set version here because some older versions don't support timeouts in Safari.
|
||||
E2ETESTS_Sauce__SeleniumVersion: '3.4.0'
|
||||
|
||||
# Android/Chrome
|
||||
- script: 'dotnet test --filter "StandaloneAppTest"'
|
||||
workingDirectory: 'src/Components/test/E2ETest'
|
||||
displayName: 'Run Blazor tests - Android/Chrome'
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
# Secrets need to be explicitly mapped to env variables.
|
||||
E2ETESTS_Sauce__Username: '$(asplab-sauce-labs-username)'
|
||||
E2ETESTS_Sauce__AccessKey: '$(asplab-sauce-labs-access-key)'
|
||||
# Set platform/browser configuration.
|
||||
E2ETESTS_Sauce__TestName: 'Blazor Daily Tests - Android/Chrome'
|
||||
E2ETESTS_Sauce__PlatformName: 'Android'
|
||||
E2ETESTS_Sauce__PlatformVersion: '10.0'
|
||||
E2ETESTS_Sauce__BrowserName: 'Chrome'
|
||||
E2ETESTS_Sauce__DeviceName: 'Android GoogleAPI Emulator'
|
||||
E2ETESTS_Sauce__DeviceOrientation: 'portrait'
|
||||
E2ETESTS_Sauce__AppiumVersion: '1.9.1'
|
||||
artifacts:
|
||||
- name: Windows_Logs
|
||||
path: ../../artifacts/log/
|
||||
publishOnError: true
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-serve": {
|
||||
"version": "1.5.0",
|
||||
"version": "1.7.125",
|
||||
"commands": [
|
||||
"dotnet-serve"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@
|
|||
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<!-- Package: Microsoft.Authentication.WebAssembly.Msal-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' ">
|
||||
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' AND '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="[3.2.0, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.ApiAuthorization.IdentityServer-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' ">
|
||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||
|
|
@ -240,6 +247,49 @@
|
|||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" />
|
||||
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' ">
|
||||
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<BaselinePackageReference Include="Microsoft.JSInterop.WebAssembly" Version="[3.2.0, )" />
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.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-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' ">
|
||||
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Update this list when preparing for a new patch.
|
|||
<Package Id="AspNetCoreRuntime.3.0.x64" Version="3.0.3" />
|
||||
<Package Id="AspNetCoreRuntime.3.0.x86" Version="3.0.3" />
|
||||
<Package Id="dotnet-sql-cache" Version="3.1.4" />
|
||||
<Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.4" />
|
||||
|
|
@ -36,6 +37,12 @@ Update this list when preparing for a new patch.
|
|||
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="3.2.0" />
|
||||
<Package Id="Microsoft.JSInterop.WebAssembly" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
|
||||
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" />
|
||||
|
|
@ -86,4 +93,4 @@ Update this list when preparing for a new patch.
|
|||
<Package Id="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.4" />
|
||||
<Package Id="Microsoft.Extensions.Identity.Core" Version="3.1.4" />
|
||||
<Package Id="Microsoft.Extensions.Identity.Stores" Version="3.1.4" />
|
||||
</Baseline>
|
||||
</Baseline>
|
||||
|
|
@ -25,8 +25,7 @@
|
|||
$(RepoRoot)src\Installers\**\*.*proj;
|
||||
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
|
||||
$(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ and are generated based on the last package release.
|
|||
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
|
||||
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
||||
<LatestPackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="$(SystemRuntimeInteropServicesRuntimeInformationPackageVersion)" />
|
||||
|
|
@ -106,7 +107,7 @@ and are generated based on the last package release.
|
|||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.3.1.x64" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.3.1.x86" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="$(MicrosoftBclAsyncInterfacesPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -62,13 +62,15 @@
|
|||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
|
||||
<ProjectReferenceProvider Include="Mono.WebAssembly.Interop" ProjectPath="$(RepoRoot)src\Components\Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" />
|
||||
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Build" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" />
|
||||
<ProjectReferenceProvider Include="blazor-brotli" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
|
||||
<ProjectReferenceProvider Include="System.Net.Http.WebAssemblyHttpHandler" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Testing" ProjectPath="$(RepoRoot)src\Testing\src\Microsoft.AspNetCore.Testing.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" />
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
-->
|
||||
<Dependencies>
|
||||
<ProductDependencies>
|
||||
<Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.2.0-preview1.20067.1">
|
||||
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0">
|
||||
<Uri>https://github.com/dotnet/blazor</Uri>
|
||||
<Sha>dd7fb4d3931d556458f62642c2edfc59f6295bfb</Sha>
|
||||
<Sha>cc449601d638ffaab58ae9487f0fd010bb178a12</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="dotnet-ef" Version="5.0.0-preview.6.20278.10">
|
||||
<Uri>https://github.com/dotnet/efcore</Uri>
|
||||
|
|
@ -205,6 +205,10 @@
|
|||
<Uri>https://github.com/dotnet/runtime</Uri>
|
||||
<Sha>c44dc40b763b7c74012622a0a6120cd8ffa35ce4</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="System.Net.Http.Json" Version="5.0.0-preview.6.20271.10">
|
||||
<Uri>https://github.com/dotnet/runtime</Uri>
|
||||
<Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="System.Net.Http.WinHttpHandler" Version="5.0.0-preview.6.20271.10">
|
||||
<Uri>https://github.com/dotnet/runtime</Uri>
|
||||
<Sha>c44dc40b763b7c74012622a0a6120cd8ffa35ce4</Sha>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
<SystemDiagnosticsEventLogPackageVersion>5.0.0-preview.6.20271.10</SystemDiagnosticsEventLogPackageVersion>
|
||||
<SystemDrawingCommonPackageVersion>5.0.0-preview.6.20271.10</SystemDrawingCommonPackageVersion>
|
||||
<SystemIOPipelinesPackageVersion>5.0.0-preview.6.20271.10</SystemIOPipelinesPackageVersion>
|
||||
<SystemNetHttpJsonPackageVersion>5.0.0-preview.6.20271.10</SystemNetHttpJsonPackageVersion>
|
||||
<SystemNetHttpWinHttpHandlerPackageVersion>5.0.0-preview.6.20271.10</SystemNetHttpWinHttpHandlerPackageVersion>
|
||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>5.0.0-preview.6.20271.10</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||
<SystemReflectionMetadataPackageVersion>5.0.0-preview.6.20271.10</SystemReflectionMetadataPackageVersion>
|
||||
|
|
@ -128,7 +129,7 @@
|
|||
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
||||
<MicrosoftNETCorePlatformsPackageVersion>5.0.0-preview.6.20271.10</MicrosoftNETCorePlatformsPackageVersion>
|
||||
<!-- Packages from dotnet/blazor -->
|
||||
<MicrosoftAspNetCoreBlazorMonoPackageVersion>3.2.0-preview1.20067.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
|
||||
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
|
||||
<!-- Packages from dotnet/efcore -->
|
||||
<dotnetefPackageVersion>5.0.0-preview.6.20278.10</dotnetefPackageVersion>
|
||||
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>5.0.0-preview.6.20278.10</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ try {
|
|||
| ? {
|
||||
# These .sln files are used by the templating engine.
|
||||
($_.Name -ne "BlazorServerWeb_CSharp.sln") -and
|
||||
($_.Name -ne "BlazorWasm-CSharp.sln")
|
||||
($_.Name -ne "ComponentsWebAssembly-CSharp.sln")
|
||||
} `
|
||||
| % {
|
||||
Write-Host " Checking $(Split-Path -Leaf $_)"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@
|
|||
],
|
||||
"dotnet/x86": [
|
||||
"$(MicrosoftNETCoreAppInternalPackageVersion)"
|
||||
],
|
||||
"aspnetcore/x64": [
|
||||
"3.1.0"
|
||||
],
|
||||
"aspnetcore/x86": [
|
||||
"3.1.0"
|
||||
]
|
||||
},
|
||||
"Git": "2.22.0",
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
<Project>
|
||||
</Project>
|
||||
|
|
@ -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/dotnet/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,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.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)
|
||||
{
|
||||
// To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types
|
||||
GC.KeepAlive(typeof(EntrypointInvoker));
|
||||
GC.KeepAlive(typeof(JSInteropMethods));
|
||||
GC.KeepAlive(typeof(WebAssemblyEventDispatcher));
|
||||
|
||||
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 actually 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/dotnet/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,29 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.WebAssembly.Interop" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Web" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
|
||||
<!--
|
||||
Supress a "BUILD001: Reference to 'Microsoft.Extensions.Configuration' was removed since the last stable release
|
||||
of this package." warning.
|
||||
-->
|
||||
<SuppressBaselineReference Include="Microsoft.Extensions.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" />
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" />
|
||||
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,39 +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)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class GenerateBlazorBootJson : Task
|
||||
{
|
||||
[Required]
|
||||
public string AssemblyPath { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] References { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool LinkerEnabled { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputPath { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
|
||||
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
|
||||
|
||||
using var fileStream = File.Create(OutputPath);
|
||||
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
|
||||
|
||||
return true;
|
||||
|
||||
static string GetUriPath(ITaskItem item)
|
||||
{
|
||||
var outputPath = item.GetMetadata("RelativeOutputPath");
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
outputPath = Path.GetFileName(item.ItemSpec);
|
||||
}
|
||||
|
||||
return outputPath.Replace('\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
|
||||
{
|
||||
var data = new BootJsonData
|
||||
{
|
||||
entryAssembly = entryAssemblyName,
|
||||
assemblies = assemblies,
|
||||
linkerEnabled = linkerEnabled,
|
||||
};
|
||||
|
||||
var serializer = new DataContractJsonSerializer(typeof(BootJsonData));
|
||||
serializer.WriteObject(stream, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the structure of a Blazor boot JSON file
|
||||
/// </summary>
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public class BootJsonData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the assembly with the application entry point
|
||||
/// </summary>
|
||||
public string entryAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
|
||||
/// </summary>
|
||||
public string[] assemblies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if the linker is enabled.
|
||||
/// </summary>
|
||||
public bool linkerEnabled { get; set; }
|
||||
}
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
|
||||
</Project>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<!-- Require rebuild if the targets change -->
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir>
|
||||
<_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM>
|
||||
<_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM>
|
||||
<BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath>
|
||||
|
||||
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
||||
<!-- By default, enable debugging for debug builds. -->
|
||||
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Blazor.MonoRuntime.targets" />
|
||||
<Import Project="Publish.targets" />
|
||||
<Import Project="StaticWebAssets.targets" />
|
||||
|
||||
<Target Name="GenerateBlazorMetadataFile"
|
||||
BeforeTargets="GetCopyToOutputDirectoryItems">
|
||||
<PropertyGroup>
|
||||
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
|
||||
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorConfigContent Include="$(MSBuildProjectFullPath)" />
|
||||
<_BlazorConfigContent Include="$(TargetPath)" />
|
||||
<_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorMetadataFilePath)"
|
||||
Lines="@(_BlazorConfigContent)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Blazor build outputs">
|
||||
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
|
||||
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
||||
<BaseBlazorDistPath>dist\</BaseBlazorDistPath>
|
||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath>
|
||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath>
|
||||
<BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath>
|
||||
<BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath>
|
||||
<BlazorWebRootName>wwwroot\</BlazorWebRootName>
|
||||
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
|
||||
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention -->
|
||||
<DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath>
|
||||
<DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath>
|
||||
<DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath>
|
||||
<DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''">
|
||||
<!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds -->
|
||||
<DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath>
|
||||
<DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath>
|
||||
<DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath>
|
||||
<DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target
|
||||
Name="_BlazorCopyFilesToOutputDirectory"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
AfterTargets="CopyFilesToOutputDirectory"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
|
||||
<!-- Copy the blazor output files -->
|
||||
<Copy
|
||||
SourceFiles="@(BlazorOutputWithTargetPath)"
|
||||
DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
|
||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
||||
Retries="$(CopyRetryCount)"
|
||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
|
||||
</Copy>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" />
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="PrepareBlazorOutputs"
|
||||
DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson">
|
||||
|
||||
<ItemGroup>
|
||||
<MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" />
|
||||
<BlazorJSFile Include="$(BlazorJSPath)" />
|
||||
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
|
||||
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
|
||||
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Static content supplied by NuGet packages">
|
||||
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
|
||||
<TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
|
||||
</_BlazorPackageContentOutput>
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
|
||||
<PropertyGroup>
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
|
||||
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
|
||||
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
|
||||
|
||||
<_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
|
||||
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
|
||||
<BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
|
||||
|
||||
<_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache>
|
||||
|
||||
<_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_WebAssemblyBCLFolder Include="
|
||||
$(DotNetWebAssemblyBCLPath);
|
||||
$(DotNetWebAssemblyBCLFacadesPath);
|
||||
$(DotNetWebAssemblyFrameworkPath)" />
|
||||
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
|
||||
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
|
||||
-->
|
||||
<ItemGroup>
|
||||
<!-- Assemblies from packages -->
|
||||
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
|
||||
|
||||
<!-- Assemblies from other references -->
|
||||
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
|
||||
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
|
||||
|
||||
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
|
||||
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
|
||||
</ItemGroup>
|
||||
|
||||
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
|
||||
<Error
|
||||
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
|
||||
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
|
||||
|
||||
<ItemGroup>
|
||||
<!--
|
||||
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
|
||||
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
|
||||
-->
|
||||
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
|
||||
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Linker enabled part of the pipeline:
|
||||
|
||||
* If there are no descriptors defined, generate a new linker descriptor.
|
||||
* Invoke the linker and write linked files to a well-known directory.
|
||||
* Collect the outputs of the linker.
|
||||
-->
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenLinked"
|
||||
Condition="'$(BlazorLinkOnBuild)' == 'true'"
|
||||
DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication">
|
||||
|
||||
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
|
||||
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
|
||||
</ReadLinesFromFile>
|
||||
</Target>
|
||||
|
||||
<Target Name="_PrepareBlazorLinkerInputs">
|
||||
<ItemGroup>
|
||||
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
|
||||
|
||||
<!--
|
||||
Any assembly from a package reference that starts with System. file name is allowed to be linked.
|
||||
Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity.
|
||||
-->
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
|
||||
|
||||
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
|
||||
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
|
||||
|
||||
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
|
||||
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
|
||||
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target Name="_GenerateBlazorLinkerDescriptor"
|
||||
Inputs="@(IntermediateAssembly)"
|
||||
Outputs="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Condition="'@(BlazorLinkerDescriptor)' == ''">
|
||||
|
||||
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
|
||||
|
||||
<BlazorCreateRootDescriptorFile
|
||||
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
|
||||
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target Name="_GenerateTypeGranularLinkerDescriptor"
|
||||
Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
|
||||
Outputs="$(_TypeGranularityLinkerDescriptor)">
|
||||
|
||||
<GenerateTypeGranularityLinkingConfig
|
||||
Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
|
||||
OutputPath="$(_TypeGranularityLinkerDescriptor)" />
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" />
|
||||
<FileWrites Include="$(_TypeGranularityLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_LinkBlazorApplication"
|
||||
Inputs="$(ProjectAssetsFile);
|
||||
@(_BlazorManagedRuntimeAssemby);
|
||||
@(BlazorLinkerDescriptor);
|
||||
$(MSBuildAllProjects)"
|
||||
Outputs="$(_BlazorLinkerOutputCache)">
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
|
||||
</ItemGroup>
|
||||
|
||||
<Delete Files="@(_OldLinkedFile)" />
|
||||
|
||||
<!--
|
||||
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
|
||||
In this case, explicitly specify the path to the dotnet host.
|
||||
-->
|
||||
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
|
||||
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
|
||||
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
|
||||
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<BlazorILLink
|
||||
ILLinkPath="$(MonoLinkerPath)"
|
||||
AssemblyPaths="@(_BlazorAssemblyToLink)"
|
||||
RootAssemblyNames="@(_BlazorLinkerRoot)"
|
||||
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
|
||||
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
|
||||
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
|
||||
ToolExe="$(_DotNetHostFileName)"
|
||||
ToolPath="$(_DotNetHostDirectory)" />
|
||||
|
||||
<ItemGroup>
|
||||
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenNotLinked"
|
||||
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
|
||||
Condition="'$(BlazorLinkOnBuild)' != 'true'">
|
||||
|
||||
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
|
||||
</ReadLinesFromFile>
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorRuntimeDependencies"
|
||||
Inputs="$(ProjectAssetsFile);
|
||||
@(IntermediateAssembly);
|
||||
@(_BlazorManagedRuntimeAssemby)"
|
||||
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
|
||||
|
||||
<!--
|
||||
At this point we have decided not to run the linker and instead to just copy the assemblies
|
||||
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
|
||||
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
|
||||
-->
|
||||
<ResolveBlazorRuntimeDependencies
|
||||
EntryPoint="@(IntermediateAssembly)"
|
||||
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
|
||||
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
|
||||
|
||||
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
|
||||
</ResolveBlazorRuntimeDependencies>
|
||||
|
||||
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" />
|
||||
|
||||
<Target
|
||||
Name="_GenerateBlazorBootJson"
|
||||
Inputs="@(BlazorOutputWithTargetPath)"
|
||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
||||
<ItemGroup>
|
||||
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<GenerateBlazorBootJson
|
||||
AssemblyPath="@(IntermediateAssembly)"
|
||||
References="@(_BlazorRuntimeFile)"
|
||||
LinkerEnabled="$(BlazorLinkOnBuild)"
|
||||
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" />
|
||||
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild>
|
||||
<BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir>
|
||||
|
||||
<!-- Disable unwanted parts of the default publish process -->
|
||||
<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BlazorGetCopyToPublishDirectoryItems"
|
||||
BeforeTargets="GetCopyToPublishDirectoryItems"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
<ItemGroup>
|
||||
<!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. -->
|
||||
<ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" />
|
||||
|
||||
<!-- Move wwwroot files to output root -->
|
||||
<ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))">
|
||||
<TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
||||
<!-- Publish all the 'dist' files -->
|
||||
<_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)">
|
||||
<TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath>
|
||||
</_BlazorGCTPDI>
|
||||
|
||||
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">
|
||||
<TargetPath>%(TargetPath)</TargetPath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Replace the .blazor.config contents with what we need to serve in production -->
|
||||
<PropertyGroup>
|
||||
<_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorPublishConfigContent Include="." />
|
||||
<_BlazorPublishConfigContent Include="$(AssemblyName)/" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(_BlazorConfigPath)"
|
||||
Lines="@(_BlazorPublishConfigContent)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="true" />
|
||||
</Target>
|
||||
|
||||
<!-- The following target runs only for standalone publishing -->
|
||||
<Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory">
|
||||
<!-- Add a suitable web.config file if there isn't one already -->
|
||||
<ItemGroup>
|
||||
<_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/>
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile
|
||||
Condition="!Exists('$(PublishDir)web.config')"
|
||||
File="$(PublishDir)web.config"
|
||||
Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" />
|
||||
|
||||
<!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing -->
|
||||
<Delete Files="$(PublishDir)$(AssemblyName).blazor.config" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<ResolveStaticWebAssetsInputsDependsOn>
|
||||
$(ResolveStaticWebAssetsInputsDependsOn);
|
||||
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
|
||||
</ResolveStaticWebAssetsInputsDependsOn>
|
||||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
||||
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<Target Name="_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="BlazorStaticWebAssetsComputeFilesToPublish"
|
||||
AfterTargets="_StaticWebAssetsComputeFilesToPublish">
|
||||
|
||||
<ItemGroup>
|
||||
<!-- We need to update the external static web assets to follow the blazor publish output convention that puts them inside $(TargetName)/dist instead of wwwroot -->
|
||||
<_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''">
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))'))</RelativePath>
|
||||
</_StandaloneExternalPublishStaticWebAsset>
|
||||
|
||||
<!-- Update doesn't work inside targets so we need to remove the items and re-add them. See https://github.com/microsoft/msbuild/issues/2835 for details -->
|
||||
<ResolvedFileToPublish Remove="@(_StandaloneExternalPublishStaticWebAsset)" />
|
||||
<ResolvedFileToPublish Include="@(_StandaloneExternalPublishStaticWebAsset)" />
|
||||
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class BindRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public BindRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> ValueChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> OnChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElement_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", null, ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementWithSuffix_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindDuplicates_ReportsDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
// Act
|
||||
var result = CompileToCSharp(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(result.Diagnostics);
|
||||
Assert.Equal("RZ9989", diagnostic.Id);
|
||||
Assert.Equal(
|
||||
"The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
|
||||
"Test.BindAttributes" + Environment.NewLine +
|
||||
"Test.BindAttributes",
|
||||
diagnostic.GetMessage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
|
||||
public string Format { get; set; } = ""MM/dd/yyyy"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""checkbox"" @bind=""@Enabled"" />
|
||||
@code {
|
||||
public bool Enabled { get; set; }
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "checkbox", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" />
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
|
||||
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "visible", 2),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 3),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
|
||||
}
|
||||
|
||||
[Fact] // See https://github.com/dotnet/blazor/issues/703
|
||||
public void Workaround_703()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input @bind-value=""@ParentValue"" @bind-value:event=""onchange"" type=""text"" visible />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The workaround for 703 is that the value attribute MUST be after the type
|
||||
// attribute.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "visible", 2),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 3),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
|
||||
}
|
||||
|
||||
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
|
||||
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_BodyContent()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind-value=""@ParentValue"" @bind-value:event=""onchange"">
|
||||
<span>@(42.ToString())</span>
|
||||
</div>
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 7, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "span", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "42", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindFallback_InvalidSyntax_TooManyParts()
|
||||
{
|
||||
// Arrange & Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<input type=""text"" @bind-first-second-third=""Text"" />
|
||||
@code {
|
||||
public string Text { get; set; } = ""text"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("RZ9991", diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindFallback_InvalidSyntax_TrailingDash()
|
||||
{
|
||||
// Arrange & Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<input type=""text"" @bind-first-=""Text"" />
|
||||
@code {
|
||||
public string Text { get; set; } = ""text"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("RZ9991", diagnostic.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class BootJsonWriterTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task ProducesJsonReferencingAssemblyAndDependencies()
|
||||
{
|
||||
// Arrange/Act
|
||||
var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
// Act
|
||||
GenerateBlazorBootJson.WriteBootJson(
|
||||
stream,
|
||||
"MyApp.Entrypoint.dll",
|
||||
assemblyReferences,
|
||||
linkerEnabled: true);
|
||||
|
||||
// Assert
|
||||
stream.Position = 0;
|
||||
using var parsedContent = await JsonDocument.ParseAsync(stream);
|
||||
var rootElement = parsedContent.RootElement;
|
||||
Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString());
|
||||
var assembliesElement = rootElement.GetProperty("assemblies");
|
||||
Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength());
|
||||
for (var i = 0; i < assemblyReferences.Length; i++)
|
||||
{
|
||||
Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString());
|
||||
}
|
||||
Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class BuildIncrementalismTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Build_WithLinker_IsIncremental()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone");
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
|
||||
// Act
|
||||
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
|
||||
|
||||
// Assert
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
|
||||
Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
|
||||
for (var j = 0; j < thumbPrint.Count; j++)
|
||||
{
|
||||
Assert.Equal(thumbPrint[j], newThumbPrint[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class BuildIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Build_WithDefaultSettings_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone");
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_Hosted_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
|
||||
project.TargetFramework = "net5.0";
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
var blazorConfig = Path.Combine(buildOutputDirectory, "standalone.blazor.config");
|
||||
Assert.FileExists(result, blazorConfig);
|
||||
|
||||
var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll"));
|
||||
Assert.FileContains(result, blazorConfig, path);
|
||||
Assert.FileDoesNotExist(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_WithLinkOnBuildDisabled_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone");
|
||||
project.AddProjectFileContent(
|
||||
@"<PropertyGroup>
|
||||
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
|
||||
</PropertyGroup>");
|
||||
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
|
||||
project.AddProjectFileContent(
|
||||
@"
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
|
||||
</ItemGroup>");
|
||||
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
|
||||
|
||||
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
|
||||
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
|
||||
project.AddProjectFileContent(
|
||||
@"
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
|
||||
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
|
||||
</ItemGroup>");
|
||||
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
|
||||
|
||||
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
|
||||
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class PublishIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Publish_WithDefaultSettings_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
|
||||
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify referenced static web assets
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publish_WithNoBuild_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" });
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
|
||||
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify static web assets from referenced projects are copied.
|
||||
// Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved.
|
||||
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publish_WithLinkOnBuildDisabled_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
|
||||
project.AddProjectFileContent(
|
||||
@"<PropertyGroup>
|
||||
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
|
||||
</PropertyGroup>");
|
||||
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
|
||||
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify referenced static web assets
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
|
||||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
|
||||
project.AddProjectFileContent(
|
||||
@"
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
|
||||
</ItemGroup>");
|
||||
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
|
||||
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
|
||||
|
||||
var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
|
||||
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publish_HostedApp_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
|
||||
project.TargetFramework = "net5.0";
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
// Make sure the main project exists
|
||||
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
|
||||
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify static web assets from referenced projects are copied.
|
||||
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
var blazorConfig = Path.Combine(result.Project.DirectoryPath, publishDirectory, "standalone.blazor.config");
|
||||
var blazorConfigLines = File.ReadAllLines(blazorConfig);
|
||||
Assert.Equal(".", blazorConfigLines[0]);
|
||||
Assert.Equal("standalone/", blazorConfigLines[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publish_HostedApp_WithNoBuild_Works()
|
||||
{
|
||||
// Arrange
|
||||
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
|
||||
project.TargetFramework = "net5.0";
|
||||
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
|
||||
|
||||
var publishDirectory = project.PublishOutputDirectory;
|
||||
// Make sure the main project exists
|
||||
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
|
||||
|
||||
var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify static web assets from referenced projects are copied.
|
||||
// Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved.
|
||||
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
|
||||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class ChildContentRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
private readonly CSharpSyntaxTree RenderChildContentComponent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderChildContent : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, ChildContent);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
private readonly CSharpSyntaxTree RenderChildContentStringComponent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderChildContentString : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, ChildContent, Value);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
private readonly CSharpSyntaxTree RenderMultipleChildContent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderMultipleChildContent : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, Header, Name);
|
||||
builder.AddContent(1, ChildContent, Value);
|
||||
builder.AddContent(2, Footer);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> Header { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Footer { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
public ChildContentRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContent>
|
||||
<div></div>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent_Generic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContentString Value=""HI"">
|
||||
<div>@context.ToLowerInvariant()</div>
|
||||
</RenderChildContentString>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 1),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 2),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "hi", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ExplicitChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContent>
|
||||
<ChildContent>
|
||||
<div></div>
|
||||
</ChildContent>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent_Recursive()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
|
||||
<RenderChildContent>
|
||||
<RenderChildContent>
|
||||
<div></div>
|
||||
</RenderChildContent>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 2),
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 4),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_RenderFragmentOfString()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContentString ChildContent=""@template"" Value=""HI"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 4),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_NoArgTemplate()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment template = @<div>@(""HI"".ToLowerInvariant())</div>; }
|
||||
<RenderChildContent ChildContent=""@template"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_IgnoresEmptyBody()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")""></RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_IgnoresWhitespaceBody()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")"">
|
||||
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> header = context => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderMultipleChildContent Name=""billg"" Header=@header Value=""HI"">
|
||||
<ChildContent>Some @context.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 2),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 3),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 4),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 5),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "billg", 1),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""Context"">Some @Context.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
// Verifies that our check for reuse of parameter names isn't too aggressive.
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent_SetsSameName()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""item"">Some @item.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,616 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_Simple()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent/>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithParameters()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class SomeType
|
||||
{
|
||||
}
|
||||
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter] public int IntProperty { get; set; }
|
||||
[Parameter] public bool BoolProperty { get; set; }
|
||||
[Parameter] public string StringProperty { get; set; }
|
||||
[Parameter] public SomeType ObjectProperty { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent
|
||||
IntProperty=""123""
|
||||
BoolProperty=""true""
|
||||
StringProperty=""My string""
|
||||
ObjectProperty=""new SomeType()"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
||||
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
||||
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
||||
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_TriesToSetNonParameter()
|
||||
{
|
||||
// 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/dotnet/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/dotnet/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/dotnet/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,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RazorProjectItem"/> that does not exist.
|
||||
/// </summary>
|
||||
internal class NotFoundProjectItem : RazorProjectItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="NotFoundProjectItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
public NotFoundProjectItem(string basePath, string path)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string BasePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FilePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Exists => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string PhysicalPath => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream Read() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class TestFile
|
||||
{
|
||||
private TestFile(string resourceName, Assembly assembly)
|
||||
{
|
||||
Assembly = assembly;
|
||||
ResourceName = Assembly.GetName().Name + "." + resourceName.Replace('/', '.').Replace('\\', '.');
|
||||
}
|
||||
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
public string ResourceName { get; }
|
||||
|
||||
public static TestFile Create(string resourceName, Type type)
|
||||
{
|
||||
return new TestFile(resourceName, type.GetTypeInfo().Assembly);
|
||||
}
|
||||
|
||||
public static TestFile Create(string resourceName, Assembly assembly)
|
||||
{
|
||||
return new TestFile(resourceName, assembly);
|
||||
}
|
||||
|
||||
public Stream OpenRead()
|
||||
{
|
||||
var stream = Assembly.GetManifestResourceStream(ResourceName);
|
||||
if (stream == null)
|
||||
{
|
||||
Assert.True(false, string.Format("Manifest resource: {0} not found", ResourceName));
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
var resourceNames = Assembly.GetManifestResourceNames();
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
// Resource names are case-sensitive.
|
||||
if (string.Equals(ResourceName, resourceName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ReadAllText()
|
||||
{
|
||||
using (var reader = new StreamReader(OpenRead()))
|
||||
{
|
||||
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
|
||||
// since all the unit tests rely on the assumption that the files will have \r\n endings.
|
||||
return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the specified path.
|
||||
/// </summary>
|
||||
public void Save(string filePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
using (var outStream = File.Create(filePath))
|
||||
{
|
||||
using (var inStream = OpenRead())
|
||||
{
|
||||
inStream.CopyTo(outStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public static class TestProject
|
||||
{
|
||||
public static string GetProjectDirectory(Type type)
|
||||
{
|
||||
var solutionDir = GetSolutionRootDirectory("Components");
|
||||
|
||||
var assemblyName = type.Assembly.GetName().Name;
|
||||
|
||||
var projectDirectory = Path.Combine(solutionDir, "test", assemblyName);
|
||||
if (!Directory.Exists(projectDirectory))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$@"Could not locate project directory for type {type.FullName}.
|
||||
Directory probe path: {projectDirectory}.");
|
||||
}
|
||||
|
||||
return projectDirectory;
|
||||
}
|
||||
|
||||
public static string GetSolutionRootDirectory(string solution)
|
||||
{
|
||||
var applicationBasePath = AppContext.BaseDirectory;
|
||||
var directoryInfo = new DirectoryInfo(applicationBasePath);
|
||||
|
||||
do
|
||||
{
|
||||
var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln"));
|
||||
if (projectFileInfo.Exists)
|
||||
{
|
||||
return projectFileInfo.DirectoryName;
|
||||
}
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
}
|
||||
while (directoryInfo.Parent != null);
|
||||
|
||||
throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class VirtualRazorProjectFileSystem : RazorProjectFileSystem
|
||||
{
|
||||
private readonly DirectoryNode _root = new DirectoryNode("/");
|
||||
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
|
||||
{
|
||||
basePath = NormalizeAndEnsureValidPath(basePath);
|
||||
var directory = _root.GetDirectory(basePath);
|
||||
return directory?.EnumerateItems() ?? Enumerable.Empty<RazorProjectItem>();
|
||||
}
|
||||
|
||||
[Obsolete("Use GetItem(string path, string fileKind)] instead")]
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
return GetItem(path, fileKind: null);
|
||||
}
|
||||
|
||||
public override RazorProjectItem GetItem(string path, string fileKind)
|
||||
{
|
||||
// We ignore fileKind here because the _root is pre-filled with project items that already have fileKinds defined. This is
|
||||
// a unique circumstance where the RazorProjectFileSystem is actually pre-filled with all of its project items on construction.
|
||||
|
||||
path = NormalizeAndEnsureValidPath(path);
|
||||
return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path);
|
||||
}
|
||||
|
||||
public void Add(RazorProjectItem projectItem)
|
||||
{
|
||||
if (projectItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath);
|
||||
_root.AddFile(new FileNode(filePath, projectItem));
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal class DirectoryNode
|
||||
{
|
||||
public DirectoryNode(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public List<DirectoryNode> Directories { get; } = new List<DirectoryNode>();
|
||||
|
||||
public List<FileNode> Files { get; } = new List<FileNode>();
|
||||
|
||||
public void AddFile(FileNode fileNode)
|
||||
{
|
||||
var filePath = fileNode.Path;
|
||||
if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = "Error";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
// Look for the first / that appears in the path after the current directory path.
|
||||
var directoryPath = GetDirectoryPath(filePath);
|
||||
var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true);
|
||||
Debug.Assert(directory != null);
|
||||
directory.Files.Add(fileNode);
|
||||
}
|
||||
|
||||
public DirectoryNode GetDirectory(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = "Error";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return GetOrAddDirectory(this, path);
|
||||
}
|
||||
|
||||
public IEnumerable<RazorProjectItem> EnumerateItems()
|
||||
{
|
||||
foreach (var file in Files)
|
||||
{
|
||||
yield return file.ProjectItem;
|
||||
}
|
||||
|
||||
foreach (var directory in Directories)
|
||||
{
|
||||
foreach (var file in directory.EnumerateItems())
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RazorProjectItem GetItem(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Error");
|
||||
}
|
||||
|
||||
var directoryPath = GetDirectoryPath(path);
|
||||
var directory = GetOrAddDirectory(this, directoryPath);
|
||||
if (directory == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in directory.Files)
|
||||
{
|
||||
var filePath = file.Path;
|
||||
var directoryLength = directory.Path.Length;
|
||||
|
||||
// path, filePath -> /Views/Home/Index.cshtml
|
||||
// directory.Path -> /Views/Home/
|
||||
// We only need to match the file name portion since we've already matched the directory segment.
|
||||
if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return file.ProjectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetDirectoryPath(string path)
|
||||
{
|
||||
// /dir1/dir2/file.cshtml -> /dir1/dir2/
|
||||
var fileNameIndex = path.LastIndexOf('/');
|
||||
if (fileNameIndex == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.Substring(0, fileNameIndex + 1);
|
||||
}
|
||||
|
||||
private static DirectoryNode GetOrAddDirectory(
|
||||
DirectoryNode directory,
|
||||
string path,
|
||||
bool createIfNotExists = false)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||
if (path[path.Length - 1] != '/')
|
||||
{
|
||||
path += '/';
|
||||
}
|
||||
|
||||
int index;
|
||||
while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length)
|
||||
{
|
||||
var subDirectory = FindSubDirectory(directory, path);
|
||||
|
||||
if (subDirectory == null)
|
||||
{
|
||||
if (createIfNotExists)
|
||||
{
|
||||
var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash
|
||||
subDirectory = new DirectoryNode(directoryPath);
|
||||
directory.Directories.Add(subDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
directory = subDirectory;
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path)
|
||||
{
|
||||
for (var i = 0; i < parentDirectory.Directories.Count; i++)
|
||||
{
|
||||
// ParentDirectory.Path -> /Views/Home/
|
||||
// CurrentDirectory.Path -> /Views/Home/SubDir/
|
||||
// Path -> /Views/Home/SubDir/MorePath/File.cshtml
|
||||
// Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file.
|
||||
|
||||
var currentDirectory = parentDirectory.Directories[i];
|
||||
var directoryPath = currentDirectory.Path;
|
||||
var startIndex = parentDirectory.Path.Length;
|
||||
var directoryNameLength = directoryPath.Length - startIndex;
|
||||
|
||||
if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return currentDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal struct FileNode
|
||||
{
|
||||
public FileNode(string path, RazorProjectItem projectItem)
|
||||
{
|
||||
Path = path;
|
||||
ProjectItem = projectItem;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public RazorProjectItem ProjectItem { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class VirtualProjectItem : RazorProjectItem
|
||||
{
|
||||
private readonly byte[] _content;
|
||||
|
||||
public VirtualProjectItem(
|
||||
string basePath,
|
||||
string filePath,
|
||||
string physicalPath,
|
||||
string relativePhysicalPath,
|
||||
string fileKind,
|
||||
byte[] content)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = filePath;
|
||||
PhysicalPath = physicalPath;
|
||||
RelativePhysicalPath = relativePhysicalPath;
|
||||
_content = content;
|
||||
|
||||
// Base class will detect based on file-extension.
|
||||
FileKind = fileKind ?? base.FileKind;
|
||||
}
|
||||
|
||||
public override string BasePath { get; }
|
||||
|
||||
public override string RelativePhysicalPath { get; }
|
||||
|
||||
public override string FileKind { get; }
|
||||
|
||||
public override string FilePath { get; }
|
||||
|
||||
public override string PhysicalPath { get; }
|
||||
|
||||
public override bool Exists => true;
|
||||
|
||||
public override Stream Read()
|
||||
{
|
||||
return new MemoryStream(_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,542 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class RazorIntegrationTestBase
|
||||
{
|
||||
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
|
||||
|
||||
internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test";
|
||||
internal const string ArbitraryMacLinuxPath = "/dir/subdir/Test";
|
||||
|
||||
// Creating the initial compilation + reading references is on the order of 250ms without caching
|
||||
// so making sure it doesn't happen for each test.
|
||||
private static readonly CSharpCompilation BaseCompilation;
|
||||
|
||||
private static CSharpParseOptions CSharpParseOptions { get; }
|
||||
|
||||
static RazorIntegrationTestBase()
|
||||
{
|
||||
var referenceAssemblyRoots = new[]
|
||||
{
|
||||
typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime
|
||||
typeof(ComponentBase).Assembly,
|
||||
typeof(RazorIntegrationTestBase).Assembly, // Reference this assembly, so that we can refer to test component types
|
||||
};
|
||||
|
||||
var referenceAssemblies = referenceAssemblyRoots
|
||||
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() }))
|
||||
.Distinct()
|
||||
.Select(Assembly.Load)
|
||||
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
|
||||
.ToList();
|
||||
BaseCompilation = CSharpCompilation.Create(
|
||||
"TestAssembly",
|
||||
Array.Empty<SyntaxTree>(),
|
||||
referenceAssemblies,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.Preview);
|
||||
}
|
||||
|
||||
public RazorIntegrationTestBase(ITestOutputHelper output)
|
||||
{
|
||||
_output.Value = output;
|
||||
|
||||
AdditionalSyntaxTrees = new List<SyntaxTree>();
|
||||
AdditionalRazorItems = new List<RazorProjectItem>();
|
||||
|
||||
Configuration = RazorConfiguration.Create(RazorLanguageVersion.Latest, "MVC-3.0", Array.Empty<RazorExtension>());
|
||||
FileKind = FileKinds.Component; // Treat input files as components by default.
|
||||
FileSystem = new VirtualRazorProjectFileSystem();
|
||||
PathSeparator = Path.DirectorySeparatorChar.ToString();
|
||||
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
|
||||
|
||||
// Many of the rendering tests include line endings in the output.
|
||||
LineEnding = "\n";
|
||||
NormalizeSourceLineEndings = true;
|
||||
|
||||
DefaultRootNamespace = "Test"; // Matches the default working directory
|
||||
DefaultFileName = "TestComponent.cshtml";
|
||||
}
|
||||
|
||||
internal List<RazorProjectItem> AdditionalRazorItems { get; }
|
||||
|
||||
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
|
||||
|
||||
internal virtual RazorConfiguration Configuration { get; }
|
||||
|
||||
internal virtual string DefaultRootNamespace { get; }
|
||||
|
||||
internal virtual string DefaultFileName { get; }
|
||||
|
||||
internal virtual bool DesignTime { get; }
|
||||
|
||||
internal virtual string FileKind { get; }
|
||||
|
||||
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
|
||||
|
||||
// Used to force a specific style of line-endings for testing. This matters
|
||||
// for the baseline tests that exercise line mappings. Even though we normalize
|
||||
// newlines for testing, the difference between platforms affects the data through
|
||||
// the *count* of characters written.
|
||||
internal virtual string LineEnding { get; }
|
||||
|
||||
internal virtual string PathSeparator { get; }
|
||||
|
||||
internal virtual bool NormalizeSourceLineEndings { get; }
|
||||
|
||||
internal virtual bool UseTwoPhaseCompilation { get; }
|
||||
|
||||
internal virtual string WorkingDirectory { get; }
|
||||
|
||||
// Intentionally private, we don't want tests messing with this because it's fragile.
|
||||
private RazorProjectEngine CreateProjectEngine(MetadataReference[] references)
|
||||
{
|
||||
return RazorProjectEngine.Create(Configuration, FileSystem, b =>
|
||||
{
|
||||
b.SetRootNamespace(DefaultRootNamespace);
|
||||
|
||||
// Turn off checksums, we're testing code generation.
|
||||
b.Features.Add(new SuppressChecksum());
|
||||
|
||||
if (LineEnding != null)
|
||||
{
|
||||
b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding));
|
||||
}
|
||||
|
||||
// Including MVC here so that we can find any issues that arise from mixed MVC + Components.
|
||||
Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions.Register(b);
|
||||
|
||||
// Features that use Roslyn are mandatory for components
|
||||
Microsoft.CodeAnalysis.Razor.CompilerFeatures.Register(b);
|
||||
|
||||
b.Features.Add(new CompilationTagHelperFeature());
|
||||
b.Features.Add(new DefaultMetadataReferenceFeature()
|
||||
{
|
||||
References = references,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
var fullPath = WorkingDirectory + PathSeparator + cshtmlRelativePath;
|
||||
|
||||
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
|
||||
var filePath = cshtmlRelativePath.Replace('\\', '/');
|
||||
if (!filePath.StartsWith('/'))
|
||||
{
|
||||
filePath = '/' + filePath;
|
||||
}
|
||||
|
||||
if (NormalizeSourceLineEndings)
|
||||
{
|
||||
cshtmlContent = cshtmlContent.Replace("\r", "").Replace("\n", LineEnding);
|
||||
}
|
||||
|
||||
return new VirtualProjectItem(
|
||||
WorkingDirectory,
|
||||
filePath,
|
||||
fullPath,
|
||||
cshtmlRelativePath,
|
||||
FileKind,
|
||||
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
|
||||
{
|
||||
return CompileToCSharp(DefaultFileName, cshtmlContent);
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
if (UseTwoPhaseCompilation)
|
||||
{
|
||||
// The first phase won't include any metadata references for component discovery. This mirrors
|
||||
// what the build does.
|
||||
var projectEngine = CreateProjectEngine(Array.Empty<MetadataReference>());
|
||||
|
||||
RazorCodeDocument codeDocument;
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
// Result of generating declarations
|
||||
codeDocument = projectEngine.ProcessDeclarationOnly(item);
|
||||
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||
|
||||
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||
}
|
||||
|
||||
// Result of generating declarations
|
||||
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||
codeDocument = projectEngine.ProcessDeclarationOnly(projectItem);
|
||||
var declaration = new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
|
||||
// Result of doing 'temp' compilation
|
||||
var tempAssembly = CompileToAssembly(declaration);
|
||||
|
||||
// Add the 'temp' compilation as a metadata reference
|
||||
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
|
||||
projectEngine = CreateProjectEngine(references);
|
||||
|
||||
// Now update the any additional files
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
// Result of generating declarations
|
||||
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(item) : projectEngine.Process(item);
|
||||
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||
|
||||
// Replace the 'declaration' syntax tree
|
||||
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||
AdditionalSyntaxTrees.RemoveAll(st => st.FilePath == item.FilePath);
|
||||
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||
}
|
||||
|
||||
// Result of real code generation for the document under test
|
||||
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||
|
||||
_output.Value.WriteLine("Use this output when opening an issue");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(projectItem));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
_output.Value.WriteLine($"### Additional source file ({item.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(item));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
_output.Value.WriteLine("## Generated C#:");
|
||||
_output.Value.WriteLine("```C#");
|
||||
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
|
||||
_output.Value.WriteLine("```");
|
||||
|
||||
return new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// For single phase compilation tests just use the base compilation's references.
|
||||
// This will include the built-in Blazor components.
|
||||
var projectEngine = CreateProjectEngine(BaseCompilation.References.ToArray());
|
||||
|
||||
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||
var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||
|
||||
// Log the generated code for test results.
|
||||
_output.Value.WriteLine("Use this output when opening an issue");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(projectItem));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine("## Generated C#:");
|
||||
_output.Value.WriteLine("```C#");
|
||||
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
|
||||
_output.Value.WriteLine("```");
|
||||
|
||||
return new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent);
|
||||
return CompileToAssembly(cSharpResult);
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, bool throwOnFailure = true)
|
||||
{
|
||||
if (cSharpResult.Diagnostics.Any())
|
||||
{
|
||||
var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray());
|
||||
throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}");
|
||||
}
|
||||
|
||||
var syntaxTrees = new[]
|
||||
{
|
||||
Parse(cSharpResult.Code),
|
||||
};
|
||||
|
||||
var compilation = cSharpResult.BaseCompilation.AddSyntaxTrees(syntaxTrees);
|
||||
|
||||
var diagnostics = compilation
|
||||
.GetDiagnostics()
|
||||
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
|
||||
|
||||
if (diagnostics.Any() && throwOnFailure)
|
||||
{
|
||||
throw new CompilationFailedException(compilation);
|
||||
}
|
||||
else if (diagnostics.Any())
|
||||
{
|
||||
return new CompileToAssemblyResult
|
||||
{
|
||||
Compilation = compilation,
|
||||
Diagnostics = diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
using (var peStream = new MemoryStream())
|
||||
{
|
||||
compilation.Emit(peStream);
|
||||
|
||||
return new CompileToAssemblyResult
|
||||
{
|
||||
Compilation = compilation,
|
||||
Diagnostics = diagnostics,
|
||||
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(string cshtmlSource)
|
||||
{
|
||||
var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource);
|
||||
|
||||
var componentFullTypeName = $"{DefaultRootNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
|
||||
return CompileToComponent(assemblyResult, componentFullTypeName);
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(CompileToCSharpResult cSharpResult, string fullTypeName)
|
||||
{
|
||||
return CompileToComponent(CompileToAssembly(cSharpResult), fullTypeName);
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
|
||||
{
|
||||
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
|
||||
if (componentType == null)
|
||||
{
|
||||
throw new XunitException(
|
||||
$"Failed to find component type '{fullTypeName}'. Found types:" + Environment.NewLine +
|
||||
string.Join(Environment.NewLine, assemblyResult.Assembly.ExportedTypes.Select(t => t.FullName)));
|
||||
}
|
||||
|
||||
return (IComponent)Activator.CreateInstance(componentType);
|
||||
}
|
||||
|
||||
protected static CSharpSyntaxTree Parse(string text, string path = null)
|
||||
{
|
||||
return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions, path: path);
|
||||
}
|
||||
|
||||
protected static string FullTypeName<T>() => typeof(T).FullName.Replace('+', '.');
|
||||
|
||||
protected RenderTreeFrame[] GetRenderTree(IComponent component)
|
||||
{
|
||||
var renderer = new TestRenderer();
|
||||
return GetRenderTree(renderer, component);
|
||||
}
|
||||
|
||||
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
|
||||
{
|
||||
renderer.AttachComponent(component);
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterView.Empty));
|
||||
// we will have to change this method if we add a test that does actual async work.
|
||||
Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted));
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
|
||||
}
|
||||
return renderer.LatestBatchReferenceFrames;
|
||||
}
|
||||
|
||||
protected ArrayRange<RenderTreeFrame> GetFrames(RenderFragment fragment)
|
||||
{
|
||||
var builder = new RenderTreeBuilder();
|
||||
fragment(builder);
|
||||
return builder.GetFrames();
|
||||
}
|
||||
|
||||
protected static void AssertSourceEquals(string expected, CompileToCSharpResult generated)
|
||||
{
|
||||
// Normalize the paths inside the expected result to match the OS paths
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var windowsPath = Path.Combine(ArbitraryWindowsPath, generated.CodeDocument.Source.RelativePath).Replace('/', '\\');
|
||||
expected = expected.Replace(windowsPath, generated.CodeDocument.Source.FilePath);
|
||||
}
|
||||
|
||||
expected = expected.Trim();
|
||||
Assert.Equal(expected, generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
private static string ReadProjectItem(RazorProjectItem item)
|
||||
{
|
||||
using (var reader = new StreamReader(item.Read()))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
protected class CompileToCSharpResult
|
||||
{
|
||||
// A compilation that can be used *with* this code to compile an assembly
|
||||
public Compilation BaseCompilation { get; set; }
|
||||
public RazorCodeDocument CodeDocument { get; set; }
|
||||
public string Code { get; set; }
|
||||
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
protected class CompileToAssemblyResult
|
||||
{
|
||||
public Assembly Assembly { get; set; }
|
||||
public Compilation Compilation { get; set; }
|
||||
public string VerboseLog { get; set; }
|
||||
public IEnumerable<Diagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
protected class TestRenderer : Renderer
|
||||
{
|
||||
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
|
||||
|
||||
public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; }
|
||||
|
||||
public void AttachComponent(IComponent component)
|
||||
=> AssignRootComponentId(component);
|
||||
|
||||
protected override void HandleException(Exception exception)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
}
|
||||
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
{
|
||||
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.AsEnumerable().ToArray();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationFailedException : XunitException
|
||||
{
|
||||
public CompilationFailedException(Compilation compilation)
|
||||
{
|
||||
Compilation = compilation;
|
||||
}
|
||||
|
||||
public Compilation Compilation { get; }
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("Compilation failed: ");
|
||||
|
||||
var diagnostics = Compilation.GetDiagnostics();
|
||||
var syntaxTreesWithErrors = new HashSet<SyntaxTree>();
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
builder.AppendLine(diagnostic.ToString());
|
||||
|
||||
if (diagnostic.Location.IsInSource)
|
||||
{
|
||||
syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree);
|
||||
}
|
||||
}
|
||||
|
||||
if (syntaxTreesWithErrors.Any())
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
|
||||
foreach (var syntaxTree in syntaxTreesWithErrors)
|
||||
{
|
||||
builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:");
|
||||
builder.AppendLine(syntaxTree.GetText().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SuppressChecksum : IConfigureRazorCodeGenerationOptionsFeature
|
||||
{
|
||||
public int Order => 0;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Configure(RazorCodeGenerationOptionsBuilder options)
|
||||
{
|
||||
options.SuppressChecksum = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ForceLineEndingPhase : RazorEnginePhaseBase
|
||||
{
|
||||
public ForceLineEndingPhase(string lineEnding)
|
||||
{
|
||||
LineEnding = lineEnding;
|
||||
}
|
||||
|
||||
public string LineEnding { get; }
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var field = typeof(CodeRenderingContext).GetField("NewLineString", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var key = field.GetValue(null);
|
||||
codeDocument.Items[key] = LineEnding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,744 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
// Integration tests for the end-to-end of successful Razor compilation of component definitions
|
||||
// Includes running the component code to verify the output.
|
||||
public class RenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public RenderingRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsPlainText()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some plain text");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Some plain text", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsCSharpExpressions()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@(""Hello"")
|
||||
@((object)null)
|
||||
@(123)
|
||||
@(new object())
|
||||
");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello", 0),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 1),
|
||||
frame => AssertFrame.TextWhitespace(frame, 2), // @((object)null)
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Text(frame, "123", 4),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 5),
|
||||
frame => AssertFrame.Text(frame, new object().ToString(), 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsCSharpFunctionsBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@foreach(var item in items) {
|
||||
@item
|
||||
}
|
||||
@code {
|
||||
string[] items = new[] { ""First"", ""Second"", ""Third"" };
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "First", 0),
|
||||
frame => AssertFrame.Text(frame, "Second", 0),
|
||||
frame => AssertFrame.Text(frame, "Third", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsElementsWithDynamicContent()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<myelem>Hello @(\"there\")</myelem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "myelem", 3, 0),
|
||||
frame => AssertFrame.Text(frame, "Hello ", 1),
|
||||
frame => AssertFrame.Text(frame, "there", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsElementsAsStaticBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<myelem>Hello</myelem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "<myelem>Hello</myelem>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatesSeparateMarkupFrameForEachTopLevelStaticElement()
|
||||
{
|
||||
// The JavaScript-side rendering code does not rely on this behavior. It supports
|
||||
// inserting markup frames with arbitrary markup (e.g., multiple top-level elements
|
||||
// or none). This test exists only as an observation of the current behavior rather
|
||||
// than a promise that we never want to change it.
|
||||
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"<root>@(\"Hi\") <child1>a</child1> <child2><another>b</another></child2> </root>");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "root", 5, 0),
|
||||
frame => AssertFrame.Text(frame, "Hi", 1),
|
||||
frame => AssertFrame.Text(frame, " ", 2),
|
||||
frame => AssertFrame.Markup(frame, "<child1>a</child1> ", 3),
|
||||
frame => AssertFrame.Markup(frame, "<child2><another>b</another></child2> ", 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendersMarkupStringAsMarkupFrame()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var someMarkup = new MarkupString(\"<div>Hello</div>\"); }"
|
||||
+ "<p>@someMarkup</p>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "p", 2, 0),
|
||||
frame => AssertFrame.Markup(frame, "<div>Hello</div>", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsSelfClosingElementsWithDynamicContent()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <myelem myattr=@(\"val\") />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0),
|
||||
frame => AssertFrame.Element(frame, "myelem", 2, 1),
|
||||
frame => AssertFrame.Attribute(frame, "myattr", "val", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsSelfClosingElementsAsStaticBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <input attr='123' />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <input attr=\"123\">", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsVoidHtmlElements()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <img>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <img>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsComments()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Start<!-- My comment -->End");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Markup(frame, "StartEnd", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithLiteralValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<elem attrib-one=\"Value 1\" a2='v2'>@(\"Hello\")</elem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attrib-one", "Value 1", 1),
|
||||
frame => AssertFrame.Attribute(frame, "a2", "v2", 2),
|
||||
frame => AssertFrame.Text(frame, "Hello", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"My string\"; }"
|
||||
+ "<elem attr=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "My string", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithNonStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = 123; }"
|
||||
+ "<elem attr=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "123", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithInterpolatedStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"world\"; var myNum=123; }"
|
||||
+ "<elem attr=\"Hello, @myValue.ToUpperInvariant() with number @(myNum*2)!\" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithInterpolatedTernaryExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"world\"; }"
|
||||
+ "<elem attr=\"Hello, @(true ? myValue : \"nothing\")!\" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "Hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsHyphenedAttributesWithCSharpExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"My string\"; }"
|
||||
+ "<elem abc-def=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "abc-def", "My string", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsDataDashAttributes()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@{
|
||||
var myValue = ""Expression value"";
|
||||
}
|
||||
<elem data-abc=""Literal value"" data-def=""@myValue"" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "data-abc", "Literal value", 1),
|
||||
frame => AssertFrame.Attribute(frame, "data-def", "Expression value", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsUsingStatements()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
@"@using System.Collections.Generic
|
||||
@(typeof(List<string>).FullName)");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, typeof(List<string>).FullName, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForTextboxes()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {
|
||||
public string MyValue { get; set; } = ""Initial value"";
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForTextareas()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<textarea @bind=""MyValue"" ></textarea>
|
||||
@code {
|
||||
public string MyValue { get; set; } = ""Initial value"";
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForDateValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyDate"" />
|
||||
@code {
|
||||
public DateTime MyDate { get; set; } = new DateTime(2018, 3, 4, 1, 2, 3);
|
||||
}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = newDateValue.ToString(), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testDateFormat = "ddd yyyy-MM-dd";
|
||||
var component = CompileToComponent($@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""@MyDate"" @bind:format=""{testDateFormat}"" />
|
||||
@code {{
|
||||
public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4);
|
||||
}}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact] // In this case, onclick is just a normal HTML attribute
|
||||
public void SupportsEventHandlerWithString()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<button onclick=""function(){console.log('hello');};"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Markup(frame, "<button onclick=\"function(){console.log('hello');};\"></button>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsEventHandlerWithLambda()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<button @onclick=""x => Clicked = true"" />
|
||||
@code {
|
||||
public bool Clicked { get; set; }
|
||||
}");
|
||||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "button", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onclick", 1);
|
||||
|
||||
var func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.False((bool)clicked.GetValue(component));
|
||||
|
||||
func(new MouseEventArgs());
|
||||
Assert.True((bool)clicked.GetValue(component));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsEventHandlerWithMethodGroup()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<button @onclick=""OnClick"" />
|
||||
@code {
|
||||
public void OnClick(MouseEventArgs e) { Clicked = true; }
|
||||
public bool Clicked { get; set; }
|
||||
}");
|
||||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Action<MouseEventArgs> func = default; // Since this is a method group, we don't need to create an EventCallback
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "button", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onclick", 1);
|
||||
|
||||
func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.False((bool)clicked.GetValue(component));
|
||||
|
||||
|
||||
});
|
||||
|
||||
func.Invoke(new MouseEventArgs());
|
||||
Assert.True((bool)clicked.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForBoolValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {
|
||||
public bool MyValue { get; set; } = true;
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", true, 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs() { Value = false, }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.False((bool)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForEnumValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var myEnumType = FullTypeName<MyEnum>();
|
||||
var component = CompileToComponent($@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {{
|
||||
public {myEnumType} MyValue {{ get; set; }} = {myEnumType}.{nameof(MyEnum.FirstValue)};
|
||||
}}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = MyEnum.SecondValue.ToString(), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
public enum MyEnum { FirstValue, SecondValue }
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_NonGeneric_CanBeUsedFromRazorCode()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment template = @<div>@(""Hello, World!"".ToLower())</div>; }
|
||||
@for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@template;
|
||||
}
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_Generic_CanBeUsedFromRazorCode()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
|
||||
@for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@template(""Hello, World!"");
|
||||
}
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_NonGeneric_CanBeUsedFromMethod()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@(Repeat(@<div>@(""Hello, World!"".ToLower())</div>, 3))
|
||||
|
||||
@code {
|
||||
RenderFragment Repeat(RenderFragment template, int count)
|
||||
{
|
||||
return (b) =>
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
b.AddContent(i, template);
|
||||
}
|
||||
};
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
|
||||
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
|
||||
// from the same logical sequence as the surrounding code.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_Generic_CanBeUsedFromMethod()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@(Repeat((context) => @<div>@context.ToLower()</div>, ""Hello, World!"", 3))
|
||||
|
||||
@code {
|
||||
RenderFragment Repeat<T>(RenderFragment<T> template, T value, int count)
|
||||
{
|
||||
return (b) =>
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
b.AddContent(i, template, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
|
||||
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
|
||||
// from the same logical sequence as the surrounding code.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="$(ReferenceBlazorBuildFromSourceProps)" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Test Placeholder -->
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\razorclasslibrary\RazorClassLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for working with JSON APIs.
|
||||
/// </summary>
|
||||
public static class HttpClientJsonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a GET request to the specified URI, and parses the JSON response body
|
||||
/// to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
|
||||
{
|
||||
var stringContent = await httpClient.GetStringAsync(requestUri);
|
||||
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task PostJsonAsync(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync(HttpMethod.Post, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task<T> PostJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<T>(HttpMethod.Post, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
public static Task PutJsonAsync(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync(HttpMethod.Put, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task<T> PutJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<T>(HttpMethod.Put, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
|
||||
{
|
||||
var requestJson = JsonSerializer.Serialize(content, JsonSerializerOptionsProvider.Options);
|
||||
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
|
||||
{
|
||||
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
|
||||
});
|
||||
|
||||
// Make sure the call was successful before we
|
||||
// attempt to process the response content
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (typeof(T) == typeof(IgnoreResponse))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
var stringContent = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoreResponse { }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Test
|
||||
{
|
||||
public class HttpClientJsonExtensionsTest
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
const string TestUri = "http://example.com/some/uri";
|
||||
|
||||
[Fact]
|
||||
public async Task GetJson_Success()
|
||||
{
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
return Task.FromResult(CreateJsonResponse(HttpStatusCode.OK, new Person
|
||||
{
|
||||
Name = "Abc",
|
||||
Age = 123
|
||||
}));
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Abc", result.Name);
|
||||
Assert.Equal(123, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJson_Failure()
|
||||
{
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||
}));
|
||||
|
||||
// Act/Assert
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(
|
||||
() => httpClient.GetJsonAsync<Person>(TestUri));
|
||||
Assert.Contains("404 (Not Found)", ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("Post")]
|
||||
[InlineData("Patch")]
|
||||
[InlineData("Delete")]
|
||||
[InlineData("MyArtificialMethod")]
|
||||
public async Task SendJson_Success(string httpMethodString)
|
||||
{
|
||||
var httpMethod = new HttpMethod(httpMethodString);
|
||||
var requestContent = new { MyProp = true, OtherProp = "Hello" };
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
|
||||
{
|
||||
Assert.Equal(httpMethod, req.Method);
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
|
||||
return CreateJsonResponse(HttpStatusCode.OK, new Person
|
||||
{
|
||||
Name = "Abc",
|
||||
Age = 123
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await Send(httpClient, httpMethodString, requestContent);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Abc", result.Name);
|
||||
Assert.Equal(123, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsJsonAsync_ReadsCamelCasedJson()
|
||||
{
|
||||
var input = "{\"name\": \"TestPerson\", \"age\": 23 }";
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(input)
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestPerson", result.Name);
|
||||
Assert.Equal(23, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsJsonAsync_ReadsPascalCasedJson()
|
||||
{
|
||||
var input = "{\"Name\": \"TestPerson\", \"Age\": 23 }";
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(input)
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestPerson", result.Name);
|
||||
Assert.Equal(23, result.Age);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("Post")]
|
||||
[InlineData("Patch")]
|
||||
[InlineData("Delete")]
|
||||
[InlineData("MyArtificialMethod")]
|
||||
public async Task SendJson_Failure(string httpMethodString)
|
||||
{
|
||||
var httpMethod = new HttpMethod(httpMethodString);
|
||||
var requestContent = new { MyProp = true, OtherProp = "Hello" };
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
|
||||
{
|
||||
Assert.Equal(httpMethod, req.Method);
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
|
||||
return new HttpResponseMessage(HttpStatusCode.BadGateway);
|
||||
}));
|
||||
|
||||
// Act/Assert
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(
|
||||
() => Send(httpClient, httpMethodString, requestContent));
|
||||
Assert.Contains("502 (Bad Gateway)", ex.Message);
|
||||
}
|
||||
|
||||
HttpResponseMessage CreateJsonResponse(HttpStatusCode statusCode, object content)
|
||||
{
|
||||
return new HttpResponseMessage(statusCode)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(content, _jsonSerializerOptions))
|
||||
};
|
||||
}
|
||||
|
||||
Task<Person> Send(HttpClient httpClient, string httpMethodString, object requestContent)
|
||||
{
|
||||
// For methods with convenience overloads, show those overloads work
|
||||
switch (httpMethodString)
|
||||
{
|
||||
case "post":
|
||||
return httpClient.PostJsonAsync<Person>(TestUri, requestContent);
|
||||
case "put":
|
||||
return httpClient.PutJsonAsync<Person>(TestUri, requestContent);
|
||||
default:
|
||||
return httpClient.SendJsonAsync<Person>(new HttpMethod(httpMethodString), TestUri, requestContent);
|
||||
}
|
||||
}
|
||||
|
||||
class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
class TestHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>> _sendDelegate;
|
||||
|
||||
public TestHttpMessageHandler(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendDelegate)
|
||||
{
|
||||
_sendDelegate = sendDelegate;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
=> base.Dispose(disposing);
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
=> _sendDelegate(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.Blazor.Server.AutoRebuild;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
internal static class AutoRebuildExtensions
|
||||
{
|
||||
// Note that we don't need to watch typical static-file extensions (.css, .js, etc.)
|
||||
// because anything in wwwroot is just served directly from disk on each reload.
|
||||
// TODO: Make the set of extensions and exclusions configurable in csproj
|
||||
private static string[] _includedSuffixes = new[] { ".cs", ".cshtml" };
|
||||
private static string[] _excludedDirectories = new[] { "obj", "bin" };
|
||||
|
||||
// To ensure the FileSystemWatchers aren't collected, reference them
|
||||
// in this static list. They never need to be removed because there's no
|
||||
// way to remove middleware once it's registered.
|
||||
private static List<object> _uncollectableWatchers = new List<object>();
|
||||
|
||||
public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath)
|
||||
{
|
||||
var isFirstFileWrite = true;
|
||||
WatchFileSystem(config, () =>
|
||||
{
|
||||
if (isFirstFileWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Touch any .cs file to force the host project to rebuild
|
||||
// (which in turn rebuilds the client, since it's referenced)
|
||||
var fileToTouch = Directory.EnumerateFiles(
|
||||
hostAppContentRootPath,
|
||||
"*.cs",
|
||||
SearchOption.AllDirectories).FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(fileToTouch))
|
||||
{
|
||||
File.SetLastWriteTime(fileToTouch, DateTime.Now);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If we don't have permission to write these files, autorebuild will not be enabled
|
||||
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions));
|
||||
logger?.LogWarning(ex,
|
||||
"Cannot autorebuild because there was an error when writing to a file in '{0}'.",
|
||||
hostAppContentRootPath);
|
||||
}
|
||||
|
||||
isFirstFileWrite = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config)
|
||||
{
|
||||
// Currently this only supports VS for Windows. Later on we can add
|
||||
// an IRebuildService implementation for VS for Mac, etc.
|
||||
if (!VSForWindowsRebuildService.TryCreate(out var rebuildService))
|
||||
{
|
||||
return; // You're not on Windows, or you didn't launch this process from VS
|
||||
}
|
||||
|
||||
// Assume we're up to date when the app starts.
|
||||
var buildToken = new RebuildToken(new DateTime(1970, 1, 1)) { BuildTask = Task.CompletedTask, };
|
||||
|
||||
WatchFileSystem(config, () =>
|
||||
{
|
||||
// Don't start the recompilation immediately. We only start it when the next
|
||||
// HTTP request arrives, because it's annoying if the IDE is constantly rebuilding
|
||||
// when you're making changes to multiple files and aren't ready to reload
|
||||
// in the browser yet.
|
||||
//
|
||||
// Replacing the token means that new requests that come in will trigger a rebuild,
|
||||
// and will all 'join' that build until a new file change occurs.
|
||||
buildToken = new RebuildToken(DateTime.Now);
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = buildToken;
|
||||
if (token.BuildTask == null)
|
||||
{
|
||||
// The build is out of date, but a new build is not yet started.
|
||||
//
|
||||
// We can count on VS to only allow one build at a time, this is a safe race
|
||||
// because if we request a second concurrent build, it will 'join' the current one.
|
||||
var task = rebuildService.PerformRebuildAsync(
|
||||
config.SourceMSBuildPath,
|
||||
token.LastChange);
|
||||
token.BuildTask = task;
|
||||
}
|
||||
|
||||
// In the general case it's safe to await this task, it will be a completed task
|
||||
// if everything is up to date.
|
||||
await token.BuildTask;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If there's no listener on the other end of the pipe, or if anything
|
||||
// else goes wrong, we just let the incoming request continue.
|
||||
// There's nowhere useful to log this information so if people report
|
||||
// problems we'll just have to get a repro and debug it.
|
||||
// If it was an error on the VS side, it logs to the output window.
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
private static void WatchFileSystem(BlazorConfig config, Action onWrite)
|
||||
{
|
||||
var clientAppRootDir = Path.GetDirectoryName(config.SourceMSBuildPath);
|
||||
var excludePathPrefixes = _excludedDirectories.Select(subdir
|
||||
=> Path.Combine(clientAppRootDir, subdir) + Path.DirectorySeparatorChar);
|
||||
|
||||
var fsw = new FileSystemWatcher(clientAppRootDir);
|
||||
fsw.Created += OnEvent;
|
||||
fsw.Changed += OnEvent;
|
||||
fsw.Deleted += OnEvent;
|
||||
fsw.Renamed += OnEvent;
|
||||
fsw.IncludeSubdirectories = true;
|
||||
fsw.EnableRaisingEvents = true;
|
||||
|
||||
// Ensure the watcher is not GCed for as long as the app lives
|
||||
lock (_uncollectableWatchers)
|
||||
{
|
||||
_uncollectableWatchers.Add(fsw);
|
||||
}
|
||||
|
||||
void OnEvent(object sender, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
if (!File.Exists(eventArgs.FullPath))
|
||||
{
|
||||
// It's probably a directory rather than a file
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_includedSuffixes.Any(ext => eventArgs.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Not a candidate file type
|
||||
return;
|
||||
}
|
||||
|
||||
if (excludePathPrefixes.Any(prefix => eventArgs.FullPath.StartsWith(prefix, StringComparison.Ordinal)))
|
||||
{
|
||||
// In an excluded subdirectory
|
||||
return;
|
||||
}
|
||||
|
||||
onWrite();
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a three-state value for the state of the build
|
||||
//
|
||||
// BuildTask == null means the build is out of date, but no build has started
|
||||
// BuildTask.IsCompleted == false means the build has been started, but has not completed
|
||||
// BuildTask.IsCompleted == true means the build has completed
|
||||
private class RebuildToken
|
||||
{
|
||||
public RebuildToken(DateTime lastChange)
|
||||
{
|
||||
LastChange = lastChange;
|
||||
}
|
||||
|
||||
public DateTime LastChange { get; }
|
||||
|
||||
public Task BuildTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mechanism for rebuilding a .NET project. For example, it
|
||||
/// could be a way of signalling to a VS process to perform a build.
|
||||
/// </summary>
|
||||
internal interface IRebuildService
|
||||
{
|
||||
Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
internal static class ProcessUtils
|
||||
{
|
||||
// Based on https://stackoverflow.com/a/3346055
|
||||
|
||||
public static Process GetParent(Process process)
|
||||
{
|
||||
var result = new ProcessBasicInformation();
|
||||
var handle = process.Handle;
|
||||
var status = NtQueryInformationProcess(handle, 0, ref result, Marshal.SizeOf(result), out var returnLength);
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Win32Exception(status);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parentProcessId = result.InheritedFromUniqueProcessId.ToInt32();
|
||||
return parentProcessId > 0 ? Process.GetProcessById(parentProcessId) : null;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null; // Process not found
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct ProcessBasicInformation
|
||||
{
|
||||
// These members must match PROCESS_BASIC_INFORMATION
|
||||
public IntPtr Reserved1;
|
||||
public IntPtr PebBaseAddress;
|
||||
public IntPtr Reserved2_0;
|
||||
public IntPtr Reserved2_1;
|
||||
public IntPtr UniqueProcessId;
|
||||
public IntPtr InheritedFromUniqueProcessId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
internal static class StreamProtocolExtensions
|
||||
{
|
||||
public static async Task WriteStringAsync(this Stream stream, string str)
|
||||
{
|
||||
var utf8Bytes = Encoding.UTF8.GetBytes(str);
|
||||
await stream.WriteAsync(BitConverter.GetBytes(utf8Bytes.Length), 0, 4);
|
||||
await stream.WriteAsync(utf8Bytes, 0, utf8Bytes.Length);
|
||||
}
|
||||
|
||||
public static async Task WriteDateTimeAsync(this Stream stream, DateTime value)
|
||||
{
|
||||
var ticksBytes = BitConverter.GetBytes(value.Ticks);
|
||||
await stream.WriteAsync(ticksBytes, 0, 8);
|
||||
}
|
||||
|
||||
public static async Task<bool> ReadBoolAsync(this Stream stream)
|
||||
{
|
||||
var responseBuf = new byte[1];
|
||||
await stream.ReadAsync(responseBuf, 0, 1);
|
||||
return responseBuf[0] == 1;
|
||||
}
|
||||
|
||||
public static async Task<int> ReadIntAsync(this Stream stream)
|
||||
{
|
||||
var responseBuf = new byte[4];
|
||||
await stream.ReadAsync(responseBuf, 0, 4);
|
||||
return BitConverter.ToInt32(responseBuf, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the VS process that launched this app process (if any), and uses
|
||||
/// named pipes to communicate with its AutoRebuild listener (if any).
|
||||
/// </summary>
|
||||
internal class VSForWindowsRebuildService : IRebuildService
|
||||
{
|
||||
private const int _connectionTimeoutMilliseconds = 3000;
|
||||
private readonly Process _vsProcess;
|
||||
|
||||
public static bool TryCreate(out VSForWindowsRebuildService result)
|
||||
{
|
||||
var vsProcess = FindAncestorVSProcess();
|
||||
if (vsProcess != null)
|
||||
{
|
||||
result = new VSForWindowsRebuildService(vsProcess);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince)
|
||||
{
|
||||
var pipeName = $"BlazorAutoRebuild\\{_vsProcess.Id}";
|
||||
using (var pipeClient = new NamedPipeClientStream(pipeName))
|
||||
{
|
||||
await pipeClient.ConnectAsync(_connectionTimeoutMilliseconds);
|
||||
|
||||
// Protocol:
|
||||
// 1. Receive protocol version number from the VS listener
|
||||
// If we're incompatible with it, send back special string "abort" and end
|
||||
// 2. Send the project path to the VS listener
|
||||
// 3. Send the 'if not rebuilt since' timestamp to the VS listener
|
||||
// 4. Wait for it to send back a bool representing the result
|
||||
// Keep in sync with AutoRebuildService.cs in the BlazorExtension project
|
||||
// In the future we may extend this to getting back build error details
|
||||
var remoteProtocolVersion = await pipeClient.ReadIntAsync();
|
||||
if (remoteProtocolVersion == 1)
|
||||
{
|
||||
await pipeClient.WriteStringAsync(projectFullPath);
|
||||
await pipeClient.WriteDateTimeAsync(ifNotBuiltSince);
|
||||
return await pipeClient.ReadBoolAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await pipeClient.WriteStringAsync("abort");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private VSForWindowsRebuildService(Process vsProcess)
|
||||
{
|
||||
_vsProcess = vsProcess ?? throw new ArgumentNullException(nameof(vsProcess));
|
||||
}
|
||||
|
||||
private static Process FindAncestorVSProcess()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidateProcess = Process.GetCurrentProcess();
|
||||
try
|
||||
{
|
||||
while (candidateProcess != null && !candidateProcess.HasExited)
|
||||
{
|
||||
// It's unlikely that anyone's going to have a non-VS process in the process
|
||||
// hierarchy called 'devenv', but if that turns out to be a scenario, we could
|
||||
// (for example) write the VS PID to the obj directory during build, and then
|
||||
// only consider processes with that ID. We still want to be sure there really
|
||||
// is such a process in our ancestor chain, otherwise if you did "dotnet run"
|
||||
// in a command prompt, we'd be confused and think it was launched from VS.
|
||||
if (candidateProcess.ProcessName.Equals("devenv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return candidateProcess;
|
||||
}
|
||||
|
||||
candidateProcess = ProcessUtils.GetParent(candidateProcess);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// There's probably some permissions issue that prevents us from seeing
|
||||
// further up the ancestor list, so we have to stop looking here.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server
|
||||
{
|
||||
internal class BlazorConfig
|
||||
{
|
||||
public string SourceMSBuildPath { get; }
|
||||
public string SourceOutputAssemblyPath { get; }
|
||||
public string WebRootPath { get; }
|
||||
public string DistPath
|
||||
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
|
||||
public bool EnableAutoRebuilding { get; }
|
||||
public bool EnableDebugging { get; }
|
||||
|
||||
public static BlazorConfig Read(string assemblyPath)
|
||||
=> new BlazorConfig(assemblyPath);
|
||||
|
||||
private BlazorConfig(string assemblyPath)
|
||||
{
|
||||
// TODO: Instead of assuming the lines are in a specific order, either JSON-encode
|
||||
// the whole thing, or at least give the lines key prefixes (e.g., "reload:<someuri>")
|
||||
// so we're not dependent on order and all lines being present.
|
||||
|
||||
var configFilePath = Path.ChangeExtension(assemblyPath, ".blazor.config");
|
||||
var configLines = File.ReadLines(configFilePath).ToList();
|
||||
SourceMSBuildPath = configLines[0];
|
||||
|
||||
if (SourceMSBuildPath == ".")
|
||||
{
|
||||
SourceMSBuildPath = assemblyPath;
|
||||
}
|
||||
|
||||
var sourceMsBuildDir = Path.GetDirectoryName(SourceMSBuildPath);
|
||||
SourceOutputAssemblyPath = Path.Combine(sourceMsBuildDir, configLines[1]);
|
||||
|
||||
var webRootPath = Path.Combine(sourceMsBuildDir, "wwwroot");
|
||||
if (Directory.Exists(webRootPath))
|
||||
{
|
||||
WebRootPath = webRootPath;
|
||||
}
|
||||
|
||||
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
|
||||
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public string FindIndexHtmlFile()
|
||||
{
|
||||
// Before publishing, the client project may have a wwwroot directory.
|
||||
// If so, and if it contains index.html, use that.
|
||||
if (!string.IsNullOrEmpty(WebRootPath))
|
||||
{
|
||||
var wwwrootIndexHtmlPath = Path.Combine(WebRootPath, "index.html");
|
||||
if (File.Exists(wwwrootIndexHtmlPath))
|
||||
{
|
||||
return wwwrootIndexHtmlPath;
|
||||
}
|
||||
}
|
||||
|
||||
// After publishing, the client project won't have a wwwroot directory.
|
||||
// The contents from that dir will have been copied to "dist" during publish.
|
||||
// So if "dist/index.html" now exists, use that.
|
||||
var distIndexHtmlPath = Path.Combine(DistPath, "index.html");
|
||||
if (File.Exists(distIndexHtmlPath))
|
||||
{
|
||||
return distIndexHtmlPath;
|
||||
}
|
||||
|
||||
// Since there's no index.html, we'll use the default DefaultPageStaticFileOptions,
|
||||
// hence we'll look for index.html in the host server app's wwwroot.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mime;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
|
||||
/// </summary>
|
||||
public static class BlazorHostingApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
|
||||
/// specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
UseClientSideBlazorFiles(app, typeof(TClientApp).Assembly.Location);
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
|
||||
/// specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseClientSideBlazorFiles(this IApplicationBuilder app, string clientAssemblyFilePath)
|
||||
{
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
var fileProviders = new List<IFileProvider>();
|
||||
|
||||
// TODO: Make the .blazor.config file contents sane
|
||||
// Currently the items in it are bizarre and don't relate to their purpose,
|
||||
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
|
||||
var config = BlazorConfig.Read(clientAssemblyFilePath);
|
||||
|
||||
// First, match the request against files in the client app dist directory
|
||||
fileProviders.Add(new PhysicalFileProvider(config.DistPath));
|
||||
|
||||
// * Before publishing, we serve the wwwroot files directly from source
|
||||
// (and don't require them to be copied into dist).
|
||||
// In this case, WebRootPath will be nonempty if that directory exists.
|
||||
// * After publishing, the wwwroot files are already copied to 'dist' and
|
||||
// will be served by the above middleware, so we do nothing here.
|
||||
// In this case, WebRootPath will be empty (the publish process sets this).
|
||||
if (!string.IsNullOrEmpty(config.WebRootPath))
|
||||
{
|
||||
fileProviders.Add(new PhysicalFileProvider(config.WebRootPath));
|
||||
}
|
||||
|
||||
// We can't modify an IFileContentTypeProvider, so we have to decorate.
|
||||
var contentTypeProvider = new FileExtensionContentTypeProvider();
|
||||
AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet);
|
||||
if (config.EnableDebugging)
|
||||
{
|
||||
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
|
||||
}
|
||||
|
||||
var options = new StaticFileOptions()
|
||||
{
|
||||
ContentTypeProvider = contentTypeProvider,
|
||||
FileProvider = new CompositeFileProvider(fileProviders),
|
||||
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
|
||||
};
|
||||
|
||||
app.UseStaticFiles(options);
|
||||
return app;
|
||||
|
||||
static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
|
||||
{
|
||||
if (!provider.Mappings.ContainsKey(name))
|
||||
{
|
||||
provider.Mappings.Add(name, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
|
||||
/// </summary>
|
||||
public static class BlazorHostingEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, clientAssemblyFilePath, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="pattern">The route pattern to match.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string pattern, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, pattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="pattern">The route pattern to match.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string pattern, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
var config = BlazorConfig.Read(clientAssemblyFilePath);
|
||||
|
||||
// We want to serve "index.html" from whichever directory contains it in this priority order:
|
||||
// 1. Client app "dist" directory
|
||||
// 2. Client app "wwwroot" directory
|
||||
// 3. Server app "wwwroot" directory
|
||||
var directory = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>().WebRootPath;
|
||||
var indexHtml = config.FindIndexHtmlFile();
|
||||
if (indexHtml != null)
|
||||
{
|
||||
directory = Path.GetDirectoryName(indexHtml);
|
||||
}
|
||||
|
||||
var options = new StaticFileOptions()
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(directory),
|
||||
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
|
||||
};
|
||||
|
||||
return endpoints.MapFallbackToFile(pattern, filePath, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<Description>Runtime server features for ASP.NET Core Blazor applications.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
</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,317 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using WsProxy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides infrastructure for debugging Blazor applications.
|
||||
/// </summary>
|
||||
public static class BlazorMonoDebugProxyAppBuilderExtensions
|
||||
{
|
||||
private static JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
IgnoreNullValues = true
|
||||
};
|
||||
|
||||
private static string DefaultDebuggerHost = "http://localhost:9222";
|
||||
|
||||
/// <summary>
|
||||
/// Adds middleware for needed for debugging Blazor applications
|
||||
/// inside Chromium dev tools.
|
||||
/// </summary>
|
||||
public static void UseBlazorDebugging(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
app.UseVisualStudioDebuggerConnectionRequestHandlers();
|
||||
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
var requestPath = context.Request.Path;
|
||||
if (!requestPath.StartsWithSegments("/_framework/debug"))
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugWebSocketProxyRequest(context);
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugHome(context);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetDebuggerHost()
|
||||
{
|
||||
var envVar = Environment.GetEnvironmentVariable("ASPNETCORE_WEBASSEMBLYDEBUGHOST");
|
||||
|
||||
if (string.IsNullOrEmpty(envVar))
|
||||
{
|
||||
return DefaultDebuggerHost;
|
||||
}
|
||||
else
|
||||
{
|
||||
return envVar;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetDebuggerPort()
|
||||
{
|
||||
var host = GetDebuggerHost();
|
||||
return new Uri(host).Port;
|
||||
}
|
||||
|
||||
private static void UseVisualStudioDebuggerConnectionRequestHandlers(this IApplicationBuilder app)
|
||||
{
|
||||
// Unfortunately VS doesn't send any deliberately distinguishing information so we know it's
|
||||
// not a regular browser or API client. The closest we can do is look for the *absence* of a
|
||||
// User-Agent header. In the future, we should try to get VS to send a special header to indicate
|
||||
// this is a debugger metadata request.
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var request = context.Request;
|
||||
var requestPath = request.Path;
|
||||
if (requestPath.StartsWithSegments("/json")
|
||||
&& !request.Headers.ContainsKey("User-Agent"))
|
||||
{
|
||||
if (requestPath.Equals("/json", StringComparison.OrdinalIgnoreCase) || requestPath.Equals("/json/list", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var availableTabs = await GetOpenedBrowserTabs();
|
||||
|
||||
// Filter the list to only include tabs displaying the requested app,
|
||||
// but only during the "choose application to debug" phase. We can't apply
|
||||
// the same filter during the "connecting" phase (/json/list), nor do we need to.
|
||||
if (requestPath.Equals("/json"))
|
||||
{
|
||||
availableTabs = availableTabs.Where(tab => tab.Url.StartsWith($"{request.Scheme}://{request.Host}{request.PathBase}/"));
|
||||
}
|
||||
|
||||
var proxiedTabInfos = availableTabs.Select(tab =>
|
||||
{
|
||||
var underlyingV8Endpoint = tab.WebSocketDebuggerUrl;
|
||||
var proxiedV8Endpoint = $"ws://{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
|
||||
return new
|
||||
{
|
||||
description = "",
|
||||
devtoolsFrontendUrl = "",
|
||||
id = tab.Id,
|
||||
title = tab.Title,
|
||||
type = tab.Type,
|
||||
url = tab.Url,
|
||||
webSocketDebuggerUrl = proxiedV8Endpoint
|
||||
};
|
||||
});
|
||||
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(proxiedTabInfos));
|
||||
}
|
||||
else if (requestPath.Equals("/json/version", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var browserVersionJson = await GetBrowserVersionInfoAsync();
|
||||
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(browserVersionJson);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task DebugWebSocketProxyRequest(HttpContext context)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
var browserUri = new Uri(context.Request.Query["browser"]);
|
||||
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await new MonoProxy().Run(browserUri, ideSocket);
|
||||
}
|
||||
|
||||
private static async Task DebugHome(HttpContext context)
|
||||
{
|
||||
context.Response.ContentType = "text/html";
|
||||
|
||||
var request = context.Request;
|
||||
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
|
||||
var targetTabUrl = request.Query["url"];
|
||||
if (string.IsNullOrEmpty(targetTabUrl))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
await context.Response.WriteAsync("No value specified for 'url'");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Allow overriding port (but not hostname, as we're connecting to the
|
||||
// local browser, not to the webserver serving the app)
|
||||
var debuggerHost = GetDebuggerHost();
|
||||
var debuggerTabsListUrl = $"{debuggerHost}/json";
|
||||
IEnumerable<BrowserTab> availableTabs;
|
||||
|
||||
try
|
||||
{
|
||||
availableTabs = await GetOpenedBrowserTabs();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
|
||||
Ensure your browser is running with debugging enabled.
|
||||
</p>
|
||||
<h2>Resolution</h2>
|
||||
<p>
|
||||
<h4>If you are using Google Chrome for your development, follow these instructions:</h4>
|
||||
{GetLaunchChromeInstructions(appRootUrl)}
|
||||
</p>
|
||||
<p>
|
||||
<h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4>
|
||||
{GetLaunchEdgeInstructions(appRootUrl)}
|
||||
</p>
|
||||
<strong>This should launch a new browser window with debugging enabled..</p>
|
||||
<h2>Underlying exception:</h2>
|
||||
<pre>{ex}</pre>
|
||||
");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var matchingTabs = availableTabs
|
||||
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
if (matchingTabs.Count == 0)
|
||||
{
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
The response from <code>{debuggerTabsListUrl}</code> does not include
|
||||
any entry for <code>{targetTabUrl}</code>.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
else if (matchingTabs.Count > 1)
|
||||
{
|
||||
// TODO: Automatically disambiguate by adding a GUID to the page title
|
||||
// when you press the debugger hotkey, include it in the querystring passed
|
||||
// here, then remove it once the debugger connects.
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Multiple matching tabs are open</h1>
|
||||
<p>
|
||||
There is more than one browser tab at <code>{targetTabUrl}</code>.
|
||||
Close the ones you do not wish to debug, then refresh this page.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we know uniquely which tab to debug, construct the URL to the debug
|
||||
// page and redirect there
|
||||
var tabToDebug = matchingTabs.Single();
|
||||
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
|
||||
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
|
||||
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
|
||||
var wsParamName = request.IsHttps ? "wss" : "ws";
|
||||
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{wsParamName}={proxyEndpoint}";
|
||||
context.Response.Redirect(devToolsUrlWithProxy);
|
||||
}
|
||||
|
||||
private static string GetLaunchChromeInstructions(string appRootUrl)
|
||||
{
|
||||
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug");
|
||||
var debuggerPort = GetDebuggerPort();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $@"<p>Press Win+R and enter the following:</p>
|
||||
<p><strong><code>chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return $@"<p>In a terminal window execute the following:</p>
|
||||
<p><strong><code>google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return $@"<p>Execute the following:</p>
|
||||
<p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown OS platform");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLaunchEdgeInstructions(string appRootUrl)
|
||||
{
|
||||
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
|
||||
var debugggerPort = GetDebuggerPort();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $@"<p>Press Win+R and enter the following:</p>
|
||||
<p><strong><code>msedge --remote-debugging-port={debugggerPort} --user-data-dir=""{profilePath}"" --no-first-run {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return $@"<p>In a terminal window execute the following:</p>
|
||||
<p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debugggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown OS platform");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> GetBrowserVersionInfoAsync()
|
||||
{
|
||||
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var debuggerHost = GetDebuggerHost();
|
||||
return await httpClient.GetStringAsync($"{debuggerHost}/json/version");
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs()
|
||||
{
|
||||
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var debuggerHost = GetDebuggerHost();
|
||||
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
|
||||
return JsonSerializer.Deserialize<BrowserTab[]>(jsonResponse, JsonOptions);
|
||||
}
|
||||
|
||||
class BrowserTab
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string DevtoolsFrontendUrl { get; set; }
|
||||
public string WebSocketDebuggerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,651 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net.Http;
|
||||
using Mono.Cecil.Pdb;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WsProxy {
|
||||
internal class BreakPointRequest {
|
||||
public string Assembly { get; private set; }
|
||||
public string File { get; private set; }
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
|
||||
public override string ToString () {
|
||||
return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
}
|
||||
|
||||
public static BreakPointRequest Parse (JObject args, DebugStore store)
|
||||
{
|
||||
if (args == null)
|
||||
return null;
|
||||
|
||||
var url = args? ["url"]?.Value<string> ();
|
||||
if (url == null) {
|
||||
var urlRegex = args?["urlRegex"].Value<string>();
|
||||
var sourceFile = store.GetFileByUrlRegex (urlRegex);
|
||||
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) {
|
||||
var sourceFile = store.GetFileByUrl (url);
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
var parts = ParseDocumentUrl (url);
|
||||
if (parts.Assembly == null)
|
||||
return null;
|
||||
|
||||
var line = args? ["lineNumber"]?.Value<int> ();
|
||||
var column = args? ["columnNumber"]?.Value<int> ();
|
||||
if (line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new BreakPointRequest () {
|
||||
Assembly = parts.Assembly,
|
||||
File = parts.DocumentPath,
|
||||
Line = line.Value,
|
||||
Column = column.Value
|
||||
};
|
||||
}
|
||||
|
||||
static (string Assembly, string DocumentPath) ParseDocumentUrl (string url)
|
||||
{
|
||||
if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") {
|
||||
return (
|
||||
docUri.Host,
|
||||
docUri.PathAndQuery.Substring (1)
|
||||
);
|
||||
} else {
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class VarInfo {
|
||||
public VarInfo (VariableDebugInformation v)
|
||||
{
|
||||
this.Name = v.Name;
|
||||
this.Index = v.Index;
|
||||
}
|
||||
|
||||
public VarInfo (ParameterDefinition p)
|
||||
{
|
||||
this.Name = p.Name;
|
||||
this.Index = (p.Index + 1) * -1;
|
||||
}
|
||||
public string Name { get; private set; }
|
||||
public int Index { get; private set; }
|
||||
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"(var-info [{Index}] '{Name}')";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CliLocation {
|
||||
|
||||
private MethodInfo method;
|
||||
private int offset;
|
||||
|
||||
public CliLocation (MethodInfo method, int offset)
|
||||
{
|
||||
this.method = method;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get => method; }
|
||||
public int Offset { get => offset; }
|
||||
}
|
||||
|
||||
|
||||
internal class SourceLocation {
|
||||
SourceId id;
|
||||
int line;
|
||||
int column;
|
||||
CliLocation cliLoc;
|
||||
|
||||
public SourceLocation (SourceId id, int line, int column)
|
||||
{
|
||||
this.id = id;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public SourceLocation (MethodInfo mi, SequencePoint sp)
|
||||
{
|
||||
this.id = mi.SourceId;
|
||||
this.line = sp.StartLine - 1;
|
||||
this.column = sp.StartColumn - 1;
|
||||
this.cliLoc = new CliLocation (mi, sp.Offset);
|
||||
}
|
||||
|
||||
public SourceId Id { get => id; }
|
||||
public int Line { get => line; }
|
||||
public int Column { get => column; }
|
||||
public CliLocation CliLocation => this.cliLoc;
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"{id}:{Line}:{Column}";
|
||||
}
|
||||
|
||||
public static SourceLocation Parse (JObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var id = SourceId.TryParse (obj ["scriptId"]?.Value<string> ());
|
||||
var line = obj ["lineNumber"]?.Value<int> ();
|
||||
var column = obj ["columnNumber"]?.Value<int> ();
|
||||
if (id == null || line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new SourceLocation (id, line.Value, column.Value);
|
||||
}
|
||||
|
||||
internal JObject ToJObject ()
|
||||
{
|
||||
return JObject.FromObject (new {
|
||||
scriptId = id.ToString (),
|
||||
lineNumber = line,
|
||||
columnNumber = column
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class SourceId {
|
||||
readonly int assembly, document;
|
||||
|
||||
public int Assembly => assembly;
|
||||
public int Document => document;
|
||||
|
||||
internal SourceId (int assembly, int document)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
|
||||
public SourceId (string id)
|
||||
{
|
||||
id = id.Substring ("dotnet://".Length);
|
||||
var sp = id.Split ('_');
|
||||
this.assembly = int.Parse (sp [0]);
|
||||
this.document = int.Parse (sp [1]);
|
||||
}
|
||||
|
||||
public static SourceId TryParse (string id)
|
||||
{
|
||||
if (!id.StartsWith ("dotnet://", StringComparison.InvariantCulture))
|
||||
return null;
|
||||
return new SourceId (id);
|
||||
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"dotnet://{assembly}_{document}";
|
||||
}
|
||||
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
SourceId that = obj as SourceId;
|
||||
return that.assembly == this.assembly && that.document == this.document;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return this.assembly.GetHashCode () ^ this.document.GetHashCode ();
|
||||
}
|
||||
|
||||
public static bool operator == (SourceId a, SourceId b)
|
||||
{
|
||||
if ((object)a == null)
|
||||
return (object)b == null;
|
||||
return a.Equals (b);
|
||||
}
|
||||
|
||||
public static bool operator != (SourceId a, SourceId b)
|
||||
{
|
||||
return !a.Equals (b);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MethodInfo {
|
||||
AssemblyInfo assembly;
|
||||
internal MethodDefinition methodDef;
|
||||
SourceFile source;
|
||||
|
||||
public SourceId SourceId => source.SourceId;
|
||||
|
||||
public string Name => methodDef.Name;
|
||||
|
||||
public SourceLocation StartLocation { get; private set; }
|
||||
public SourceLocation EndLocation { get; private set; }
|
||||
public AssemblyInfo Assembly => assembly;
|
||||
public int Token => (int)methodDef.MetadataToken.RID;
|
||||
|
||||
public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.methodDef = methodDef;
|
||||
this.source = source;
|
||||
|
||||
var sps = methodDef.DebugInformation.SequencePoints;
|
||||
if (sps != null && sps.Count > 0) {
|
||||
StartLocation = new SourceLocation (this, sps [0]);
|
||||
EndLocation = new SourceLocation (this, sps [sps.Count - 1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SourceLocation GetLocationByIl (int pos)
|
||||
{
|
||||
SequencePoint prev = null;
|
||||
foreach (var sp in methodDef.DebugInformation.SequencePoints) {
|
||||
if (sp.Offset > pos)
|
||||
break;
|
||||
prev = sp;
|
||||
}
|
||||
|
||||
if (prev != null)
|
||||
return new SourceLocation (this, prev);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VarInfo [] GetLiveVarsAt (int offset)
|
||||
{
|
||||
var res = new List<VarInfo> ();
|
||||
|
||||
res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p)));
|
||||
|
||||
res.AddRange (methodDef.DebugInformation.GetScopes ()
|
||||
.Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
|
||||
.SelectMany (s => s.Variables)
|
||||
.Where (v => !v.IsDebuggerHidden)
|
||||
.Select (v => new VarInfo (v)));
|
||||
|
||||
|
||||
return res.ToArray ();
|
||||
}
|
||||
}
|
||||
|
||||
internal class AssemblyInfo {
|
||||
static int next_id;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> ();
|
||||
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
|
||||
readonly List<SourceFile> sources = new List<SourceFile>();
|
||||
|
||||
public AssemblyInfo (byte[] assembly, byte[] pdb)
|
||||
{
|
||||
lock (typeof (AssemblyInfo)) {
|
||||
this.id = ++next_id;
|
||||
}
|
||||
|
||||
try {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PortablePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
} catch (BadImageFormatException ex) {
|
||||
Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
}
|
||||
|
||||
if (this.image == null) {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new NativePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
}
|
||||
|
||||
Populate ();
|
||||
}
|
||||
|
||||
public AssemblyInfo ()
|
||||
{
|
||||
}
|
||||
|
||||
void Populate ()
|
||||
{
|
||||
ProcessSourceLink();
|
||||
|
||||
var d2s = new Dictionary<Document, SourceFile> ();
|
||||
|
||||
Func<Document, SourceFile> get_src = (doc) => {
|
||||
if (doc == null)
|
||||
return null;
|
||||
if (d2s.ContainsKey (doc))
|
||||
return d2s [doc];
|
||||
var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url));
|
||||
sources.Add (src);
|
||||
d2s [doc] = src;
|
||||
return src;
|
||||
};
|
||||
|
||||
foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) {
|
||||
Document first_doc = null;
|
||||
foreach (var sp in m.DebugInformation.SequencePoints) {
|
||||
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) {
|
||||
first_doc = sp.Document;
|
||||
}
|
||||
// else if (first_doc != sp.Document) {
|
||||
// //FIXME this is needed for (c)ctors in corlib
|
||||
// throw new Exception ($"Cant handle multi-doc methods in {m}");
|
||||
//}
|
||||
}
|
||||
|
||||
if (first_doc == null) {
|
||||
// all generated files
|
||||
first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document;
|
||||
}
|
||||
|
||||
if (first_doc != null) {
|
||||
var src = get_src (first_doc);
|
||||
var mi = new MethodInfo (this, m, src);
|
||||
int mt = (int)m.MetadataToken.RID;
|
||||
this.methods [mt] = mi;
|
||||
if (src != null)
|
||||
src.AddMethod (mi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSourceLink ()
|
||||
{
|
||||
var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink);
|
||||
|
||||
if (sourceLinkDebugInfo != null) {
|
||||
var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
|
||||
|
||||
if (sourceLinkContent != null) {
|
||||
var jObject = JObject.Parse (sourceLinkContent) ["documents"];
|
||||
sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Uri GetSourceLinkUrl (string document)
|
||||
{
|
||||
if (sourceLinkMappings.TryGetValue (document, out string url)) {
|
||||
return new Uri (url);
|
||||
}
|
||||
|
||||
foreach (var sourceLinkDocument in sourceLinkMappings) {
|
||||
string key = sourceLinkDocument.Key;
|
||||
|
||||
if (Path.GetFileName (key) != "*") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyTrim = key.TrimEnd ('*');
|
||||
|
||||
if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) {
|
||||
var docUrlPart = document.Replace (keyTrim, "");
|
||||
return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetRelativePath (string relativeTo, string path)
|
||||
{
|
||||
var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute);
|
||||
var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) {
|
||||
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> Sources {
|
||||
get { return this.sources; }
|
||||
}
|
||||
|
||||
public int Id => id;
|
||||
public string Name => image.Name;
|
||||
|
||||
public SourceFile GetDocById (int document)
|
||||
{
|
||||
return sources.FirstOrDefault (s => s.SourceId.Document == document);
|
||||
}
|
||||
|
||||
public MethodInfo GetMethodByToken (int token)
|
||||
{
|
||||
methods.TryGetValue (token, out var value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SourceFile {
|
||||
HashSet<MethodInfo> methods;
|
||||
AssemblyInfo assembly;
|
||||
int id;
|
||||
Document doc;
|
||||
|
||||
internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
|
||||
{
|
||||
this.methods = new HashSet<MethodInfo> ();
|
||||
this.SourceLinkUri = sourceLinkUri;
|
||||
this.assembly = assembly;
|
||||
this.id = id;
|
||||
this.doc = doc;
|
||||
this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", "");
|
||||
|
||||
this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
|
||||
if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) {
|
||||
this.Url = this.SourceUri.ToString ();
|
||||
} else {
|
||||
this.Url = DotNetUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void AddMethod (MethodInfo mi)
|
||||
{
|
||||
this.methods.Add (mi);
|
||||
}
|
||||
public string DebuggerFileName { get; }
|
||||
public string Url { get; }
|
||||
public string AssemblyName => assembly.Name;
|
||||
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
|
||||
public string DocHashCode => "abcdee" + id;
|
||||
public SourceId SourceId => new SourceId (assembly.Id, this.id);
|
||||
public Uri SourceLinkUri { get; }
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public IEnumerable<MethodInfo> Methods => this.methods;
|
||||
}
|
||||
|
||||
internal class DebugStore {
|
||||
List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
|
||||
|
||||
public DebugStore (string [] loaded_files)
|
||||
{
|
||||
bool MatchPdb (string asm, string pdb)
|
||||
{
|
||||
return Path.ChangeExtension (asm, "pdb") == pdb;
|
||||
}
|
||||
|
||||
var asm_files = new List<string> ();
|
||||
var pdb_files = new List<string> ();
|
||||
foreach (var f in loaded_files) {
|
||||
var file_name = f;
|
||||
if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase))
|
||||
pdb_files.Add (file_name);
|
||||
else
|
||||
asm_files.Add (file_name);
|
||||
}
|
||||
|
||||
//FIXME make this parallel
|
||||
foreach (var p in asm_files) {
|
||||
try {
|
||||
var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n));
|
||||
HttpClient h = new HttpClient ();
|
||||
var assembly_bytes = h.GetByteArrayAsync (p).Result;
|
||||
byte [] pdb_bytes = null;
|
||||
if (pdb != null)
|
||||
pdb_bytes = h.GetByteArrayAsync (pdb).Result;
|
||||
|
||||
this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes));
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine ($"Failed to read {p} ({e.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> AllSources ()
|
||||
{
|
||||
foreach (var a in assemblies) {
|
||||
foreach (var s in a.Sources)
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
public SourceFile GetFileById (SourceId id)
|
||||
{
|
||||
return AllSources ().FirstOrDefault (f => f.SourceId.Equals (id));
|
||||
}
|
||||
|
||||
public AssemblyInfo GetAssemblyByName (string name)
|
||||
{
|
||||
return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
|
||||
{
|
||||
var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
|
||||
var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
|
||||
|
||||
if (start.Line > spStart.Line)
|
||||
return false;
|
||||
if (start.Column > spStart.Column && start.Line == sp.StartLine)
|
||||
return false;
|
||||
|
||||
if (end.Line < spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (end.Column < spEnd.Column && end.Line == spEnd.Line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end)
|
||||
{
|
||||
//XXX FIXME no idea what todo with locations on different files
|
||||
if (start.Id != end.Id)
|
||||
return null;
|
||||
var src_id = start.Id;
|
||||
|
||||
var doc = GetFileById (src_id);
|
||||
|
||||
var res = new List<SourceLocation> ();
|
||||
if (doc == null) {
|
||||
//FIXME we need to write up logging here
|
||||
Console.WriteLine ($"Could not find document {src_id}");
|
||||
return res;
|
||||
}
|
||||
|
||||
foreach (var m in doc.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
if (Match (sp, start, end))
|
||||
res.Add (new SourceLocation (m, sp));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match (SequencePoint sp, int line, int column)
|
||||
{
|
||||
var bp = (line: line + 1, column: column + 1);
|
||||
|
||||
if (sp.StartLine > bp.line || sp.EndLine < bp.line)
|
||||
return false;
|
||||
|
||||
//Chrome sends a zero column even if getPossibleBreakpoints say something else
|
||||
if (column == 0)
|
||||
return true;
|
||||
|
||||
if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
|
||||
return false;
|
||||
|
||||
if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public SourceLocation FindBestBreakpoint (BreakPointRequest req)
|
||||
{
|
||||
var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase));
|
||||
var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (src == null)
|
||||
return null;
|
||||
|
||||
foreach (var m in src.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
//FIXME handle multi doc methods
|
||||
if (Match (sp, req.Line, req.Column))
|
||||
return new SourceLocation (m, sp);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ToUrl (SourceLocation location)
|
||||
=> location != null ? GetFileById (location.Id).Url : "";
|
||||
|
||||
public SourceFile GetFileByUrlRegex (string urlRegex)
|
||||
{
|
||||
var regex = new Regex (urlRegex);
|
||||
return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString()));
|
||||
}
|
||||
|
||||
public SourceFile GetFileByUrl (string url)
|
||||
=> AllSources ().FirstOrDefault (file => file.Url.ToString() == url);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,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 stitch 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 bubble 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 bubble 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 bubble 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 bubble 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,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ValidationAttribute"/> that compares two properties
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class ComparePropertyAttribute : CompareAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="BlazorCompareAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="otherProperty">The property to compare with the current property.</param>
|
||||
public ComparePropertyAttribute(string otherProperty)
|
||||
: base(otherProperty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
var validationResult = base.IsValid(value, validationContext);
|
||||
if (validationResult == ValidationResult.Success)
|
||||
{
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
return new ValidationResult(validationResult.ErrorMessage, new[] { validationContext.MemberName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Description>Provides experimental support for validation using DataAnnotations.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Forms" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Forms
|
||||
{
|
||||
public class ObjectGraphDataAnnotationsValidator : ComponentBase
|
||||
{
|
||||
private static readonly object ValidationContextValidatorKey = new object();
|
||||
private static readonly object ValidatedObjectsKey = new object();
|
||||
private ValidationMessageStore _validationMessageStore;
|
||||
|
||||
[CascadingParameter]
|
||||
internal EditContext EditContext { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_validationMessageStore = new ValidationMessageStore(EditContext);
|
||||
|
||||
// Perform object-level validation (starting from the root model) on request
|
||||
EditContext.OnValidationRequested += (sender, eventArgs) =>
|
||||
{
|
||||
_validationMessageStore.Clear();
|
||||
ValidateObject(EditContext.Model, new HashSet<object>());
|
||||
EditContext.NotifyValidationStateChanged();
|
||||
};
|
||||
|
||||
// Perform per-field validation on each field edit
|
||||
EditContext.OnFieldChanged += (sender, eventArgs) =>
|
||||
ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier);
|
||||
}
|
||||
|
||||
internal void ValidateObject(object value, HashSet<object> visited)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visited.Add(value))
|
||||
{
|
||||
// Already visited this object.
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is IEnumerable<object> enumerable)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
ValidateObject(item, visited);
|
||||
index++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResults = new List<ValidationResult>();
|
||||
ValidateObject(value, visited, validationResults);
|
||||
|
||||
// Transfer results to the ValidationMessageStore
|
||||
foreach (var validationResult in validationResults)
|
||||
{
|
||||
if (!validationResult.MemberNames.Any())
|
||||
{
|
||||
_validationMessageStore.Add(new FieldIdentifier(value, string.Empty), validationResult.ErrorMessage);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var memberName in validationResult.MemberNames)
|
||||
{
|
||||
var fieldIdentifier = new FieldIdentifier(value, memberName);
|
||||
_validationMessageStore.Add(fieldIdentifier, validationResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults)
|
||||
{
|
||||
var validationContext = new ValidationContext(value);
|
||||
validationContext.Items.Add(ValidationContextValidatorKey, this);
|
||||
validationContext.Items.Add(ValidatedObjectsKey, visited);
|
||||
Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true);
|
||||
}
|
||||
|
||||
internal static bool TryValidateRecursive(object value, ValidationContext validationContext)
|
||||
{
|
||||
if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is ObjectGraphDataAnnotationsValidator validator)
|
||||
{
|
||||
var visited = (HashSet<object>)validationContext.Items[ValidatedObjectsKey];
|
||||
validator.ValidateObject(value, visited);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
|
||||
{
|
||||
// DataAnnotations only validates public properties, so that's all we'll look for
|
||||
var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
|
||||
var validationContext = new ValidationContext(fieldIdentifier.Model)
|
||||
{
|
||||
MemberName = propertyInfo.Name
|
||||
};
|
||||
var results = new List<ValidationResult>();
|
||||
|
||||
Validator.TryValidateProperty(propertyValue, validationContext, results);
|
||||
messages.Clear(fieldIdentifier);
|
||||
messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
|
||||
|
||||
// We have to notify even if there were no messages before and are still no messages now,
|
||||
// because the "state" that changed might be the completion of some async validation task
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ValidationAttribute"/> that indicates that the property is a complex or collection type that further needs to be validated.
|
||||
/// <para>
|
||||
/// By default <see cref="Validator"/> does not recurse in to complex property types during validation.
|
||||
/// When used in conjunction with <see cref="ObjectGraphDataAnnotationsValidator"/>, this property allows the validation system to validate
|
||||
/// complex or collection type properties.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class ValidateComplexTypeAttribute : ValidationAttribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
if (!ObjectGraphDataAnnotationsValidator.TryValidateRecursive(value, validationContext))
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(ObjectGraphDataAnnotationsValidator)}.");
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,540 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public class ObjectGraphDataAnnotationsValidatorTest
|
||||
{
|
||||
public class SimpleModel
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Range(1, 16)]
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_SimpleObject()
|
||||
{
|
||||
var model = new SimpleModel
|
||||
{
|
||||
Age = 23,
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_SimpleObject_AllValid()
|
||||
{
|
||||
var model = new SimpleModel { Name = "Test", Age = 5 };
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Age);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Empty(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
public class ModelWithComplexProperty
|
||||
{
|
||||
[Required]
|
||||
public string Property1 { get; set; }
|
||||
|
||||
[ValidateComplexType]
|
||||
public SimpleModel SimpleModel { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_NullComplexProperty()
|
||||
{
|
||||
var model = new ModelWithComplexProperty();
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Property1);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ModelWithComplexProperties()
|
||||
{
|
||||
var model = new ModelWithComplexProperty { SimpleModel = new SimpleModel() };
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Property1);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(3, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ModelWithComplexProperties_SomeValid()
|
||||
{
|
||||
var model = new ModelWithComplexProperty
|
||||
{
|
||||
Property1 = "Value",
|
||||
SimpleModel = new SimpleModel { Name = "Some Value" },
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Property1);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
public class TestValidatableObject : IValidatableObject
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
yield return new ValidationResult("Custom validation error");
|
||||
}
|
||||
}
|
||||
|
||||
public class ModelWithValidatableComplexProperty
|
||||
{
|
||||
[Required]
|
||||
public string Property1 { get; set; }
|
||||
|
||||
[ValidateComplexType]
|
||||
public TestValidatableObject Property2 { get; set; } = new TestValidatableObject();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ValidatableComplexProperty()
|
||||
{
|
||||
var model = new ModelWithValidatableComplexProperty();
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Property1);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Property2);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Property2.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ValidatableComplexProperty_ValidatesIValidatableProperty()
|
||||
{
|
||||
var model = new ModelWithValidatableComplexProperty
|
||||
{
|
||||
Property2 = new TestValidatableObject { Name = "test" },
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(() => model.Property1);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(new FieldIdentifier(model.Property2, string.Empty));
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Property2.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ModelIsIValidatable_PropertyHasError()
|
||||
{
|
||||
var model = new TestValidatableObject();
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ModelIsIValidatable_ModelHasError()
|
||||
{
|
||||
var model = new TestValidatableObject { Name = "test" };
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_CollectionModel()
|
||||
{
|
||||
var model = new List<SimpleModel>
|
||||
{
|
||||
new SimpleModel(),
|
||||
new SimpleModel { Name = "test", },
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
|
||||
var item = model[0];
|
||||
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, "0"));
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => item.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => item.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
item = model[1];
|
||||
messages = editContext.GetValidationMessages(new FieldIdentifier(model, "1"));
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => item.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => item.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(3, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_CollectionValidatableModel()
|
||||
{
|
||||
var model = new List<TestValidatableObject>
|
||||
{
|
||||
new TestValidatableObject(),
|
||||
new TestValidatableObject { Name = "test", },
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
|
||||
var item = model[0];
|
||||
var messages = editContext.GetValidationMessages(() => item.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
item = model[1];
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => item.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
private class Level1Validation
|
||||
{
|
||||
[ValidateComplexType]
|
||||
public Level2Validation Level2 { get; set; }
|
||||
}
|
||||
|
||||
public class Level2Validation
|
||||
{
|
||||
[ValidateComplexType]
|
||||
public SimpleModel Level3 { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_ManyLevels()
|
||||
{
|
||||
var model = new Level1Validation
|
||||
{
|
||||
Level2 = new Level2Validation
|
||||
{
|
||||
Level3 = new SimpleModel
|
||||
{
|
||||
Age = 47,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var editContext = Validate(model);
|
||||
var level3 = model.Level2.Level3;
|
||||
|
||||
var messages = editContext.GetValidationMessages(() => level3.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => level3.Age);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
[ValidateComplexType]
|
||||
public Person Related { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_RecursiveRelation()
|
||||
{
|
||||
var model = new Person { Related = new Person() };
|
||||
model.Related.Related = model;
|
||||
|
||||
var editContext = Validate(model);
|
||||
|
||||
var messages = editContext.GetValidationMessages(() => model.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => model.Related.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_RecursiveRelation_OverManySteps()
|
||||
{
|
||||
var person1 = new Person();
|
||||
var person2 = new Person { Name = "Valid name" };
|
||||
var person3 = new Person();
|
||||
var person4 = new Person();
|
||||
|
||||
person1.Related = person2;
|
||||
person2.Related = person3;
|
||||
person3.Related = person4;
|
||||
person4.Related = person1;
|
||||
|
||||
var editContext = Validate(person1);
|
||||
|
||||
var messages = editContext.GetValidationMessages(() => person1.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person2.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person3.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person4.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(3, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
private class Node
|
||||
{
|
||||
[Required]
|
||||
public string Id { get; set; }
|
||||
|
||||
[ValidateComplexType]
|
||||
public List<Node> Related { get; set; } = new List<Node>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_RecursiveRelation_ViaCollection()
|
||||
{
|
||||
var node1 = new Node();
|
||||
var node2 = new Node { Id = "Valid Id" };
|
||||
var node3 = new Node();
|
||||
node1.Related.Add(node2);
|
||||
node2.Related.Add(node3);
|
||||
node3.Related.Add(node1);
|
||||
|
||||
var editContext = Validate(node1);
|
||||
|
||||
var messages = editContext.GetValidationMessages(() => node1.Id);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => node2.Id);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => node3.Id);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateObject_RecursiveRelation_InCollection()
|
||||
{
|
||||
var person1 = new Person();
|
||||
var person2 = new Person { Name = "Valid name" };
|
||||
var person3 = new Person();
|
||||
var person4 = new Person();
|
||||
|
||||
person1.Related = person2;
|
||||
person2.Related = person3;
|
||||
person3.Related = person4;
|
||||
person4.Related = person1;
|
||||
|
||||
var editContext = Validate(person1);
|
||||
|
||||
var messages = editContext.GetValidationMessages(() => person1.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person2.Name);
|
||||
Assert.Empty(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person3.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
messages = editContext.GetValidationMessages(() => person4.Name);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(3, editContext.GetValidationMessages().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateField_PropertyValid()
|
||||
{
|
||||
var model = new SimpleModel { Age = 1 };
|
||||
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
|
||||
|
||||
var editContext = ValidateField(model, fieldIdentifier);
|
||||
var messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Empty(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateField_PropertyInvalid()
|
||||
{
|
||||
var model = new SimpleModel { Age = 42 };
|
||||
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
|
||||
|
||||
var editContext = ValidateField(model, fieldIdentifier);
|
||||
var messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateField_AfterSubmitValidation()
|
||||
{
|
||||
var model = new SimpleModel { Age = 42 };
|
||||
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Equal(2, editContext.GetValidationMessages().Count());
|
||||
|
||||
model.Age = 4;
|
||||
|
||||
editContext.NotifyFieldChanged(fieldIdentifier);
|
||||
messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Empty(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateField_ModelWithComplexProperty()
|
||||
{
|
||||
var model = new ModelWithComplexProperty
|
||||
{
|
||||
SimpleModel = new SimpleModel { Age = 1 },
|
||||
};
|
||||
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Name);
|
||||
|
||||
var editContext = ValidateField(model, fieldIdentifier);
|
||||
var messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Single(messages);
|
||||
|
||||
Assert.Single(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateField_ModelWithComplexProperty_AfterSubmitValidation()
|
||||
{
|
||||
var model = new ModelWithComplexProperty
|
||||
{
|
||||
Property1 = "test",
|
||||
SimpleModel = new SimpleModel { Age = 29, Name = "Test" },
|
||||
};
|
||||
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Age);
|
||||
|
||||
var editContext = Validate(model);
|
||||
var messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Single(messages);
|
||||
|
||||
model.SimpleModel.Age = 9;
|
||||
editContext.NotifyFieldChanged(fieldIdentifier);
|
||||
|
||||
messages = editContext.GetValidationMessages(fieldIdentifier);
|
||||
Assert.Empty(messages);
|
||||
Assert.Empty(editContext.GetValidationMessages());
|
||||
}
|
||||
|
||||
private static EditContext Validate(object model)
|
||||
{
|
||||
var editContext = new EditContext(model);
|
||||
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
|
||||
validator.OnInitialized();
|
||||
|
||||
editContext.Validate();
|
||||
|
||||
return editContext;
|
||||
}
|
||||
|
||||
private static EditContext ValidateField(object model, in FieldIdentifier field)
|
||||
{
|
||||
var editContext = new EditContext(model);
|
||||
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
|
||||
validator.OnInitialized();
|
||||
|
||||
editContext.NotifyFieldChanged(field);
|
||||
|
||||
return editContext;
|
||||
}
|
||||
|
||||
private class TestObjectGraphDataAnnotationsValidator : ObjectGraphDataAnnotationsValidator
|
||||
{
|
||||
public new void OnInitialized() => base.OnInitialized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,19 +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.Hosting;
|
||||
|
||||
namespace HostedInAspNet.Client
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
|
||||
BlazorWebAssemblyHost.CreateDefaultBuilder()
|
||||
.UseBlazorStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace HostedInAspNet.Client
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app)
|
||||
{
|
||||
app.AddComponent<Home>("app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Sample Blazor app</title>
|
||||
</head>
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
<script src="customJsFileForTests.js"></script>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,19 +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.Hosting;
|
||||
|
||||
namespace StandaloneApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
|
||||
BlazorWebAssemblyHost.CreateDefaultBuilder()
|
||||
.UseBlazorStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor" />
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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.
|
||||
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace StandaloneApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app)
|
||||
{
|
||||
app.AddComponent<App>("app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,31 +9,31 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Analyzers.Tests", "Analyzers\test\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "{F000C49D-3857-42A4-918D-DA4C08691FE2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor", "Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly", "WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Tests", "Blazor\Blazor\test\Microsoft.AspNetCore.Blazor.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Tests", "WebAssembly\WebAssembly\test\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build", "Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build", "WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build.Tests", "Blazor\Build\test\Microsoft.AspNetCore.Blazor.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.Tests", "WebAssembly\Build\test\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DevServer", "WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server", "WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{FD37F740-A654-4117-BFB6-9112CE4C1D3B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "WebAssembly\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{FD37F740-A654-4117-BFB6-9112CE4C1D3B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "Blazor\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{C1E2C117-BE47-4E29-94B3-753262D97A5C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "WebAssembly\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{C1E2C117-BE47-4E29-94B3-753262D97A5C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "Blazor\testassets\MonoSanity\MonoSanity.csproj", "{F16C1A7C-A2BD-4EB1-8BC8-23B1375F3B9E}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "WebAssembly\testassets\MonoSanity\MonoSanity.csproj", "{F16C1A7C-A2BD-4EB1-8BC8-23B1375F3B9E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "Blazor\testassets\MonoSanityClient\MonoSanityClient.csproj", "{1C4BF2D3-44A8-4A71-B031-15B983663CB0}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "WebAssembly\testassets\MonoSanityClient\MonoSanityClient.csproj", "{1C4BF2D3-44A8-4A71-B031-15B983663CB0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "Blazor\testassets\StandaloneApp\StandaloneApp.csproj", "{C0FFB29E-4696-4875-9039-E5FA1AC5A42A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj", "{C0FFB29E-4696-4875-9039-E5FA1AC5A42A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A27FF193-195B-4474-8E6C-840B2E339373}"
|
||||
EndProject
|
||||
|
|
@ -206,10 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Connections.Client", "..\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj", "{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient", "Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj", "{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient.Tests", "Blazor\Http\test\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", "{E4C01A3F-D3C1-4639-A6A9-930E918843DD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Tests", "Web\test\Microsoft.AspNetCore.Components.Web.Tests.csproj", "{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{08791FEE-761D-40EF-B701-1D31FD1E6E53}"
|
||||
|
|
@ -236,23 +232,41 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop", "Mono.WebAssembly.Interop", "{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{346EC9B8-BF36-4A5E-A1A3-77879931713A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA95650-E56E-470A-82A3-BC6572E4D6CD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server.Tests", "WebAssembly\Server\test\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "{3D0ED658-9DAC-4066-A587-795321FA1C98}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Authentication.WebAssembly.Msal", "WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj", "{4B4E4247-7BBF-444E-9737-407D34821D70}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression", "WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj", "{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DebugProxy", "WebAssembly\DebugProxy\src\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj", "{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevServer", "DevServer", "{C6B58D53-04E2-4D65-B445-B510A3CB7569}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.WebAssembly", "WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj", "{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication", "WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "{5CD61479-5181-4A5B-B90F-9F34316248B3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", "WebAssembly\WebAssembly.Authentication\test\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", "{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{81250121-9B43-40B1-BF11-CE4458F2676C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.HttpHandler", "WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -1344,30 +1358,6 @@ Global
|
|||
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -1476,42 +1466,6 @@ Global
|
|||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -1548,6 +1502,102 @@ Global
|
|||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.Build.0 = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -1555,12 +1605,12 @@ Global
|
|||
GlobalSection(NestedProjects) = preSolution
|
||||
{ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
|
||||
{F000C49D-3857-42A4-918D-DA4C08691FE2} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
|
||||
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
|
||||
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
|
||||
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
|
||||
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
|
||||
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
|
||||
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
|
||||
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
|
||||
{C1E2C117-BE47-4E29-94B3-753262D97A5C} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
|
||||
|
|
@ -1645,8 +1695,6 @@ Global
|
|||
{DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
|
||||
{0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {2FC10057-7A0A-4E34-8302-879925BC0102}
|
||||
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
|
||||
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{E4C01A3F-D3C1-4639-A6A9-930E918843DD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373}
|
||||
{956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
|
||||
{B13CDE69-ED22-4664-AAD7-686ED8CD5E88} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
|
||||
|
|
@ -1656,14 +1704,23 @@ Global
|
|||
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
|
||||
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
|
||||
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
|
||||
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
|
||||
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
|
||||
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
|
||||
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
|
||||
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
|
||||
{346EC9B8-BF36-4A5E-A1A3-77879931713A} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{1FA95650-E56E-470A-82A3-BC6572E4D6CD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{3D0ED658-9DAC-4066-A587-795321FA1C98} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
|
||||
{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{4B4E4247-7BBF-444E-9737-407D34821D70} = {81250121-9B43-40B1-BF11-CE4458F2676C}
|
||||
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
|
||||
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
|
||||
{C6B58D53-04E2-4D65-B445-B510A3CB7569} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{5CD61479-5181-4A5B-B90F-9F34316248B3} = {81250121-9B43-40B1-BF11-CE4458F2676C}
|
||||
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17} = {81250121-9B43-40B1-BF11-CE4458F2676C}
|
||||
{81250121-9B43-40B1-BF11-CE4458F2676C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}
|
||||
|
|
|
|||
|
|
@ -6,22 +6,6 @@
|
|||
"Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj",
|
||||
"Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
|
||||
"Authorization\\test\\Microsoft.AspNetCore.Components.Authorization.Tests.csproj",
|
||||
"Blazor\\Blazor\\src\\Microsoft.AspNetCore.Blazor.csproj",
|
||||
"Blazor\\Blazor\\test\\Microsoft.AspNetCore.Blazor.Tests.csproj",
|
||||
"Blazor\\Build\\src\\Microsoft.AspNetCore.Blazor.Build.csproj",
|
||||
"Blazor\\Build\\test\\Microsoft.AspNetCore.Blazor.Build.Tests.csproj",
|
||||
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
|
||||
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
|
||||
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
|
||||
"Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
|
||||
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
|
||||
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
|
||||
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
|
||||
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
|
||||
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
|
||||
"Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
|
||||
"Blazor\\testassets\\MonoSanity\\MonoSanity.csproj",
|
||||
"Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj",
|
||||
"Components\\perf\\Microsoft.AspNetCore.Components.Performance.csproj",
|
||||
"Components\\src\\Microsoft.AspNetCore.Components.csproj",
|
||||
"Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj",
|
||||
|
|
@ -32,12 +16,32 @@
|
|||
"Samples\\BlazorServerApp\\BlazorServerApp.csproj",
|
||||
"Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
|
||||
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
|
||||
"WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
|
||||
"WebAssembly\\Build\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj",
|
||||
"WebAssembly\\Build\\test\\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj",
|
||||
"WebAssembly\\Compression\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj",
|
||||
"WebAssembly\\DebugProxy\\src\\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj",
|
||||
"WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj",
|
||||
"WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
|
||||
"WebAssembly\\Server\\src\\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj",
|
||||
"WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj",
|
||||
"WebAssembly\\WebAssembly.Authentication\\src\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj",
|
||||
"WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
|
||||
"WebAssembly\\WebAssemblyHttpHandler\\src\\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj",
|
||||
"WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj",
|
||||
"WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj",
|
||||
"WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
|
||||
"WebAssembly\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
|
||||
"WebAssembly\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
|
||||
"WebAssembly\\testassets\\MonoSanity\\MonoSanity.csproj",
|
||||
"WebAssembly\\testassets\\StandaloneApp\\StandaloneApp.csproj",
|
||||
"Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
|
||||
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
|
||||
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
|
||||
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
|
||||
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
|
||||
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
|
||||
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
||||
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
||||
"test\\testassets\\TestServer\\Components.TestServer.csproj"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageTags>aspnetcore;components</PackageTags>
|
||||
<EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- This property points to the latest released Microsoft.AspNetCore.App version it needs to be updated to
|
||||
target the latest patch before a preview release. -->
|
||||
<LatestAspNetCoreReferenceVersion>3.1.0</LatestAspNetCoreReferenceVersion>
|
||||
<PropertyGroup>
|
||||
<PackageTags>aspnetcore;components</PackageTags>
|
||||
|
||||
<ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,26 +3,6 @@
|
|||
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We need to do this because our build config interferes with the FrameworkReference definition.
|
||||
This is a way to add the framework defition to the projects that need it (like Blazor Server and
|
||||
Blazor Dev Server) -->
|
||||
<Target Name="_AddAspNetCoreFrameworkReference" BeforeTargets="ProcessFrameworkReferences" Condition="'$(UseLatestAspNetCoreReference)' == 'true' ">
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Version="$(LatestAspNetCoreReferenceVersion)" />
|
||||
<KnownFrameworkReference Include="Microsoft.AspNetCore.App">
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<RuntimeFrameworkName>Microsoft.AspNetCore.App</RuntimeFrameworkName>
|
||||
<DefaultRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</DefaultRuntimeFrameworkVersion>
|
||||
<LatestRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</LatestRuntimeFrameworkVersion>
|
||||
<TargetingPackName>Microsoft.AspNetCore.App.Ref</TargetingPackName>
|
||||
<TargetingPackVersion>$(LatestAspNetCoreReferenceVersion)</TargetingPackVersion>
|
||||
<RuntimePackNamePatterns>Microsoft.AspNetCore.App.Runtime.**RID**</RuntimePackNamePatterns>
|
||||
<RuntimePackRuntimeIdentifiers>linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86</RuntimePackRuntimeIdentifiers>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
</KnownFrameworkReference>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Add a project dependency without reference output assemblies to enforce build order -->
|
||||
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
|
||||
|
|
@ -35,7 +15,29 @@
|
|||
Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="Blazor\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
|
||||
<Import Project="WebAssembly\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
|
||||
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||
|
||||
<ItemGroup Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'">
|
||||
<ProjectReference
|
||||
Include="$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj"
|
||||
CopyLocal="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="_FixupReferenceToWebAssemblyHttpHandler"
|
||||
Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'"
|
||||
AfterTargets="_ResolveBlazorInputs"
|
||||
BeforeTargets="_ResolveBlazorOutputs">
|
||||
<!--
|
||||
ProjectReference doesn't really play well with IncludeAssets behavior you get when referencing
|
||||
the package with IncludeAssets="compile".
|
||||
-->
|
||||
<ItemGroup>
|
||||
<_HttpHandlerAssembly Include="@(_BlazorUserRuntimeAssembly)"
|
||||
Condition="%(_BlazorUserRuntimeAssembly.ProjectReferenceOriginalItemSpec) == '$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj'" />
|
||||
|
||||
<_BlazorUserRuntimeAssembly Remove="@(_HttpHandlerAssembly)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
||||
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
|
||||
public static readonly string EnableNavigationInterception = Prefix + "enableNavigationInterception";
|
||||
|
||||
public static readonly string GetLocationHref = Prefix + "getLocationHref";
|
||||
public static readonly string GetLocationHref = Prefix + "getUnmarshalledLocationHref";
|
||||
|
||||
public static readonly string GetBaseUri = Prefix + "getBaseURI";
|
||||
public static readonly string GetBaseUri = Prefix + "getUnmarshalledBaseURI";
|
||||
|
||||
public static readonly string NavigateTo = Prefix + "navigateTo";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@
|
|||
Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GetTargetPath" />
|
||||
|
||||
<!-- Workaround strange issues with something calling these targets. -->
|
||||
<Target Name="GetTargetFramework" />
|
||||
<Target Name="GetCopyToPublishDirectoryItems" />
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue