Merge blazor-wasm in to master
This commit is contained in:
commit
4c50b6cb16
|
|
@ -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
|
||||
|
|
@ -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,8 @@
|
|||
$(RepoRoot)src\Installers\**\*.*proj;
|
||||
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
|
||||
$(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
|
||||
|
|
|
|||
|
|
@ -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,14 @@
|
|||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
|
||||
<ProjectReferenceProvider Include="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="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,13 @@
|
|||
-->
|
||||
<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="System.Net.Http.Json" Version="3.2.0">
|
||||
<Uri>https://github.com/dotnet/corefx</Uri>
|
||||
<Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="dotnet-ef" Version="5.0.0-preview.6.20276.2">
|
||||
<Uri>https://github.com/dotnet/efcore</Uri>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
<!-- Servicing builds have different characteristics for the way dependencies, baselines, and versions are handled. -->
|
||||
<IsServicingBuild Condition=" '$(DisableServicingFeatures)' != 'true' AND '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
|
||||
<VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix>
|
||||
<ComponentsWebAssemblyVersionPrefix>$(ComponentsWebAssemblyMajorVersion).$(ComponentsWebAssemblyMinorVersion).$(ComponentsWebAssemblyPatchVersion)</ComponentsWebAssemblyVersionPrefix>
|
||||
<!-- TargetingPackVersionPrefix is used by projects, like .deb and .rpm, which use slightly different version formats. -->
|
||||
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
|
||||
<!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. -->
|
||||
|
|
@ -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.20276.2</dotnetefPackageVersion>
|
||||
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>5.0.0-preview.6.20276.2</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
|
|||
if ($msbuildCmd -ne $null) {
|
||||
# Workaround for https://github.com/dotnet/roslyn/issues/35793
|
||||
# Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+
|
||||
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0])
|
||||
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0])
|
||||
|
||||
if ($msbuildVersion -ge $vsMinVersion) {
|
||||
return $global:_MSBuildExe = $msbuildCmd.Path
|
||||
|
|
|
|||
|
|
@ -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,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor" />
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. -->
|
||||
<Reference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
public class GenerateBlazorBootJson : Task
|
||||
{
|
||||
[Required]
|
||||
public string AssemblyPath { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] References { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool LinkerEnabled { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputPath { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
|
||||
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
|
||||
|
||||
using var fileStream = File.Create(OutputPath);
|
||||
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
|
||||
|
||||
return true;
|
||||
|
||||
static string GetUriPath(ITaskItem item)
|
||||
{
|
||||
var outputPath = item.GetMetadata("RelativeOutputPath");
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
outputPath = Path.GetFileName(item.ItemSpec);
|
||||
}
|
||||
|
||||
return outputPath.Replace('\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
|
||||
{
|
||||
var data = new BootJsonData
|
||||
{
|
||||
entryAssembly = entryAssemblyName,
|
||||
assemblies = assemblies,
|
||||
linkerEnabled = linkerEnabled,
|
||||
};
|
||||
|
||||
var serializer = new DataContractJsonSerializer(typeof(BootJsonData));
|
||||
serializer.WriteObject(stream, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the structure of a Blazor boot JSON file
|
||||
/// </summary>
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public class BootJsonData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the assembly with the application entry point
|
||||
/// </summary>
|
||||
public string entryAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
|
||||
/// </summary>
|
||||
public string[] assemblies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if the linker is enabled.
|
||||
/// </summary>
|
||||
public bool linkerEnabled { get; set; }
|
||||
}
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
|
||||
</Project>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<!-- Require rebuild if the targets change -->
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir>
|
||||
<_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM>
|
||||
<_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM>
|
||||
<BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath>
|
||||
|
||||
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
||||
<!-- By default, enable debugging for debug builds. -->
|
||||
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Blazor.MonoRuntime.targets" />
|
||||
<Import Project="Publish.targets" />
|
||||
<Import Project="StaticWebAssets.targets" />
|
||||
|
||||
<Target Name="GenerateBlazorMetadataFile"
|
||||
BeforeTargets="GetCopyToOutputDirectoryItems">
|
||||
<PropertyGroup>
|
||||
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
|
||||
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorConfigContent Include="$(MSBuildProjectFullPath)" />
|
||||
<_BlazorConfigContent Include="$(TargetPath)" />
|
||||
<_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorMetadataFilePath)"
|
||||
Lines="@(_BlazorConfigContent)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Blazor build outputs">
|
||||
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
|
||||
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
||||
<BaseBlazorDistPath>dist\</BaseBlazorDistPath>
|
||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath>
|
||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath>
|
||||
<BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath>
|
||||
<BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath>
|
||||
<BlazorWebRootName>wwwroot\</BlazorWebRootName>
|
||||
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
|
||||
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention -->
|
||||
<DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath>
|
||||
<DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath>
|
||||
<DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath>
|
||||
<DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''">
|
||||
<!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds -->
|
||||
<DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath>
|
||||
<DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath>
|
||||
<DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath>
|
||||
<DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target
|
||||
Name="_BlazorCopyFilesToOutputDirectory"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
AfterTargets="CopyFilesToOutputDirectory"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
|
||||
<!-- Copy the blazor output files -->
|
||||
<Copy
|
||||
SourceFiles="@(BlazorOutputWithTargetPath)"
|
||||
DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
|
||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
||||
Retries="$(CopyRetryCount)"
|
||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
|
||||
</Copy>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" />
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="PrepareBlazorOutputs"
|
||||
DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson">
|
||||
|
||||
<ItemGroup>
|
||||
<MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" />
|
||||
<BlazorJSFile Include="$(BlazorJSPath)" />
|
||||
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
|
||||
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
|
||||
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Static content supplied by NuGet packages">
|
||||
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
|
||||
<TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
|
||||
</_BlazorPackageContentOutput>
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
|
||||
<PropertyGroup>
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
|
||||
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
|
||||
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
|
||||
|
||||
<_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
|
||||
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
|
||||
<BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
|
||||
|
||||
<_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache>
|
||||
|
||||
<_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_WebAssemblyBCLFolder Include="
|
||||
$(DotNetWebAssemblyBCLPath);
|
||||
$(DotNetWebAssemblyBCLFacadesPath);
|
||||
$(DotNetWebAssemblyFrameworkPath)" />
|
||||
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
|
||||
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
|
||||
-->
|
||||
<ItemGroup>
|
||||
<!-- Assemblies from packages -->
|
||||
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
|
||||
|
||||
<!-- Assemblies from other references -->
|
||||
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
|
||||
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
|
||||
|
||||
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
|
||||
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
|
||||
</ItemGroup>
|
||||
|
||||
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
|
||||
<Error
|
||||
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
|
||||
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
|
||||
|
||||
<ItemGroup>
|
||||
<!--
|
||||
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
|
||||
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
|
||||
-->
|
||||
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
|
||||
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Linker enabled part of the pipeline:
|
||||
|
||||
* If there are no descriptors defined, generate a new linker descriptor.
|
||||
* Invoke the linker and write linked files to a well-known directory.
|
||||
* Collect the outputs of the linker.
|
||||
-->
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenLinked"
|
||||
Condition="'$(BlazorLinkOnBuild)' == 'true'"
|
||||
DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication">
|
||||
|
||||
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
|
||||
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
|
||||
</ReadLinesFromFile>
|
||||
</Target>
|
||||
|
||||
<Target Name="_PrepareBlazorLinkerInputs">
|
||||
<ItemGroup>
|
||||
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
|
||||
|
||||
<!--
|
||||
Any assembly from a package reference that starts with System. file name is allowed to be linked.
|
||||
Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity.
|
||||
-->
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
|
||||
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
|
||||
|
||||
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
|
||||
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
|
||||
|
||||
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
|
||||
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
|
||||
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target Name="_GenerateBlazorLinkerDescriptor"
|
||||
Inputs="@(IntermediateAssembly)"
|
||||
Outputs="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Condition="'@(BlazorLinkerDescriptor)' == ''">
|
||||
|
||||
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
|
||||
|
||||
<BlazorCreateRootDescriptorFile
|
||||
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
|
||||
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target Name="_GenerateTypeGranularLinkerDescriptor"
|
||||
Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
|
||||
Outputs="$(_TypeGranularityLinkerDescriptor)">
|
||||
|
||||
<GenerateTypeGranularityLinkingConfig
|
||||
Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
|
||||
OutputPath="$(_TypeGranularityLinkerDescriptor)" />
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" />
|
||||
<FileWrites Include="$(_TypeGranularityLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_LinkBlazorApplication"
|
||||
Inputs="$(ProjectAssetsFile);
|
||||
@(_BlazorManagedRuntimeAssemby);
|
||||
@(BlazorLinkerDescriptor);
|
||||
$(MSBuildAllProjects)"
|
||||
Outputs="$(_BlazorLinkerOutputCache)">
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
|
||||
</ItemGroup>
|
||||
|
||||
<Delete Files="@(_OldLinkedFile)" />
|
||||
|
||||
<!--
|
||||
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
|
||||
In this case, explicitly specify the path to the dotnet host.
|
||||
-->
|
||||
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
|
||||
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
|
||||
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
|
||||
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<BlazorILLink
|
||||
ILLinkPath="$(MonoLinkerPath)"
|
||||
AssemblyPaths="@(_BlazorAssemblyToLink)"
|
||||
RootAssemblyNames="@(_BlazorLinkerRoot)"
|
||||
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
|
||||
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
|
||||
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
|
||||
ToolExe="$(_DotNetHostFileName)"
|
||||
ToolPath="$(_DotNetHostDirectory)" />
|
||||
|
||||
<ItemGroup>
|
||||
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_ResolveBlazorOutputsWhenNotLinked"
|
||||
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
|
||||
Condition="'$(BlazorLinkOnBuild)' != 'true'">
|
||||
|
||||
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
|
||||
</ReadLinesFromFile>
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorRuntimeDependencies"
|
||||
Inputs="$(ProjectAssetsFile);
|
||||
@(IntermediateAssembly);
|
||||
@(_BlazorManagedRuntimeAssemby)"
|
||||
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
|
||||
|
||||
<!--
|
||||
At this point we have decided not to run the linker and instead to just copy the assemblies
|
||||
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
|
||||
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
|
||||
-->
|
||||
<ResolveBlazorRuntimeDependencies
|
||||
EntryPoint="@(IntermediateAssembly)"
|
||||
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
|
||||
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
|
||||
|
||||
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
|
||||
</ResolveBlazorRuntimeDependencies>
|
||||
|
||||
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" />
|
||||
|
||||
<Target
|
||||
Name="_GenerateBlazorBootJson"
|
||||
Inputs="@(BlazorOutputWithTargetPath)"
|
||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
||||
<ItemGroup>
|
||||
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<GenerateBlazorBootJson
|
||||
AssemblyPath="@(IntermediateAssembly)"
|
||||
References="@(_BlazorRuntimeFile)"
|
||||
LinkerEnabled="$(BlazorLinkOnBuild)"
|
||||
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" />
|
||||
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild>
|
||||
<BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir>
|
||||
|
||||
<!-- Disable unwanted parts of the default publish process -->
|
||||
<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BlazorGetCopyToPublishDirectoryItems"
|
||||
BeforeTargets="GetCopyToPublishDirectoryItems"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
<ItemGroup>
|
||||
<!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. -->
|
||||
<ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" />
|
||||
|
||||
<!-- Move wwwroot files to output root -->
|
||||
<ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))">
|
||||
<TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
||||
<!-- Publish all the 'dist' files -->
|
||||
<_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)">
|
||||
<TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath>
|
||||
</_BlazorGCTPDI>
|
||||
|
||||
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">
|
||||
<TargetPath>%(TargetPath)</TargetPath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Replace the .blazor.config contents with what we need to serve in production -->
|
||||
<PropertyGroup>
|
||||
<_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorPublishConfigContent Include="." />
|
||||
<_BlazorPublishConfigContent Include="$(AssemblyName)/" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(_BlazorConfigPath)"
|
||||
Lines="@(_BlazorPublishConfigContent)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="true" />
|
||||
</Target>
|
||||
|
||||
<!-- The following target runs only for standalone publishing -->
|
||||
<Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory">
|
||||
<!-- Add a suitable web.config file if there isn't one already -->
|
||||
<ItemGroup>
|
||||
<_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/>
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile
|
||||
Condition="!Exists('$(PublishDir)web.config')"
|
||||
File="$(PublishDir)web.config"
|
||||
Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" />
|
||||
|
||||
<!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing -->
|
||||
<Delete Files="$(PublishDir)$(AssemblyName).blazor.config" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<ResolveStaticWebAssetsInputsDependsOn>
|
||||
$(ResolveStaticWebAssetsInputsDependsOn);
|
||||
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
|
||||
</ResolveStaticWebAssetsInputsDependsOn>
|
||||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
||||
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<Target Name="_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="BlazorStaticWebAssetsComputeFilesToPublish"
|
||||
AfterTargets="_StaticWebAssetsComputeFilesToPublish">
|
||||
|
||||
<ItemGroup>
|
||||
<!-- We need to update the external static web assets to follow the blazor publish output convention that puts them inside $(TargetName)/dist instead of wwwroot -->
|
||||
<_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''">
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))'))</RelativePath>
|
||||
</_StandaloneExternalPublishStaticWebAsset>
|
||||
|
||||
<!-- Update doesn't work inside targets so we need to remove the items and re-add them. See https://github.com/microsoft/msbuild/issues/2835 for details -->
|
||||
<ResolvedFileToPublish Remove="@(_StandaloneExternalPublishStaticWebAsset)" />
|
||||
<ResolvedFileToPublish Include="@(_StandaloneExternalPublishStaticWebAsset)" />
|
||||
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class BindRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public BindRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> ValueChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> OnChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElement_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", null, ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementWithSuffix_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindDuplicates_ReportsDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
// Act
|
||||
var result = CompileToCSharp(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(result.Diagnostics);
|
||||
Assert.Equal("RZ9989", diagnostic.Id);
|
||||
Assert.Equal(
|
||||
"The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
|
||||
"Test.BindAttributes" + Environment.NewLine +
|
||||
"Test.BindAttributes",
|
||||
diagnostic.GetMessage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
|
||||
public string Format { get; set; } = ""MM/dd/yyyy"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""checkbox"" @bind=""@Enabled"" />
|
||||
@code {
|
||||
public bool Enabled { get; set; }
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "checkbox", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" />
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
|
||||
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "visible", 2),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 3),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
|
||||
}
|
||||
|
||||
[Fact] // See https://github.com/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
|
|
@ -17,19 +17,19 @@
|
|||
"@microsoft/signalr": "link:../../SignalR/clients/ts/signalr",
|
||||
"@microsoft/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack",
|
||||
"@microsoft/dotnet-js-interop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@microsoft/dotnet-js-interop/-/@microsoft/dotnet-js-interop-5.0.0-alpha1.19572.2.tgz",
|
||||
"@types/emscripten": "0.0.31",
|
||||
"@types/jest": "^24.0.6",
|
||||
"@types/emscripten": "^1.39.3",
|
||||
"@types/jest": "^24.9.1",
|
||||
"@types/jsdom": "11.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^1.5.0",
|
||||
"@typescript-eslint/parser": "^1.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||
"@typescript-eslint/parser": "^1.13.0",
|
||||
"eslint": "^5.16.0",
|
||||
"jest": "^24.8.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"ts-jest": "^24.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"rimraf": "^2.7.1",
|
||||
"ts-jest": "^24.3.0",
|
||||
"ts-loader": "^4.4.1",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.36.1",
|
||||
"webpack-cli": "^3.3.6"
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/set-value": "^2.0.1"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue