Merge pull request #21921 from dotnet/prkrishn/merge-blazor-wasm
Merge blazor-wasm -> 3.1
This commit is contained in:
commit
432dc979b3
|
|
@ -664,6 +664,8 @@ stages:
|
|||
vmImage: 'ubuntu-16.04'
|
||||
variables:
|
||||
DotNetCoreSdkDir: $(Agent.ToolsDirectory)/dotnet
|
||||
# This isn't needed in the path because build does not need to _use_ global tools.
|
||||
DOTNET_CLI_HOME: $(System.DefaultWorkingDirectory)
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: true
|
||||
steps:
|
||||
- script: |
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ jobs:
|
|||
- _BuildConfig: ${{ parameters.configuration }}
|
||||
- BuildConfiguration: ${{ parameters.configuration }}
|
||||
- BuildDirectory: ${{ parameters.buildDirectory }}
|
||||
- DOTNET_CLI_HOME: $(System.DefaultWorkingDirectory)
|
||||
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
- TeamName: AspNetCore
|
||||
- ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
||||
|
|
@ -155,6 +156,12 @@ jobs:
|
|||
- ${{ if and(eq(parameters.installTar, 'true'), eq(parameters.agentOs, 'Windows')) }}:
|
||||
- powershell: ./eng/scripts/InstallTar.ps1
|
||||
displayName: Find or install Tar
|
||||
- ${{ if eq(parameters.agentOs, 'Windows') }}:
|
||||
- powershell: Write-Host "##vso[task.prependpath]$(DOTNET_CLI_HOME)\.dotnet\tools"
|
||||
displayName: Add dotnet tools to path
|
||||
- ${{ if ne(parameters.agentOs, 'Windows') }}:
|
||||
- script: echo "##vso[task.prependpath]$(DOTNET_CLI_HOME)/.dotnet/tools"
|
||||
displayName: Add dotnet tools to path
|
||||
|
||||
- ${{ parameters.beforeBuild }}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-serve": {
|
||||
"version": "1.7.125",
|
||||
"commands": [
|
||||
"dotnet-serve"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,13 @@
|
|||
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
|
||||
<BaselinePackageVersion>3.1.5</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.5</BaselinePackageVersion>
|
||||
|
|
@ -201,6 +208,49 @@
|
|||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.5, )" />
|
||||
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.5, )" />
|
||||
</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.5</BaselinePackageVersion>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Update this list when preparing for a new patch.
|
|||
<Package Id="AspNetCoreRuntime.3.0.x64" Version="3.0.3" />
|
||||
<Package Id="AspNetCoreRuntime.3.0.x86" Version="3.0.3" />
|
||||
<Package Id="dotnet-sql-cache" Version="3.1.5" />
|
||||
<Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.5" />
|
||||
|
|
@ -30,6 +31,12 @@ Update this list when preparing for a new patch.
|
|||
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="3.2.0" />
|
||||
<Package Id="Microsoft.JSInterop.WebAssembly" Version="3.2.0" />
|
||||
<Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.5" />
|
||||
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.5" />
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
$(RepoRoot)src\Installers\**\*.*proj;
|
||||
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
|
||||
$(RepoRoot)src\Components\Blazor\Templates\src\content\**\*.*proj;
|
||||
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
|
||||
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ and are generated based on the last package release.
|
|||
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
|
||||
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
|
||||
<LatestPackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
|
||||
|
|
@ -122,7 +123,7 @@ and are generated based on the last package release.
|
|||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.1" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -55,12 +55,14 @@
|
|||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" />
|
||||
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Build" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" />
|
||||
<ProjectReferenceProvider Include="blazor-brotli" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Abstractions" ProjectPath="$(RepoRoot)src\DataProtection\Abstractions\src\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Abstractions\ref\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Cryptography.Internal" ProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\src\Microsoft.AspNetCore.Cryptography.Internal.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\ref\Microsoft.AspNetCore.Cryptography.Internal.csproj" />
|
||||
|
|
|
|||
|
|
@ -9,9 +9,13 @@
|
|||
-->
|
||||
<Dependencies>
|
||||
<ProductDependencies>
|
||||
<Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.1.0-preview4.19605.1" Pinned="true">
|
||||
<Uri>https://github.com/aspnet/Blazor</Uri>
|
||||
<Sha>7868699de745fd30a654c798a99dc541b77b95c0</Sha>
|
||||
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0">
|
||||
<Uri>https://github.com/dotnet/blazor</Uri>
|
||||
<Sha>cc449601d638ffaab58ae9487f0fd010bb178a12</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="System.Net.Http.Json" Version="3.2.0">
|
||||
<Uri>https://github.com/dotnet/corefx</Uri>
|
||||
<Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.5">
|
||||
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore-tooling</Uri>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@
|
|||
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
|
||||
<AspNetCorePatchVersion>6</AspNetCorePatchVersion>
|
||||
<PreReleasePreviewNumber>0</PreReleasePreviewNumber>
|
||||
|
||||
<ComponentsWebAssemblyMajorVersion>3</ComponentsWebAssemblyMajorVersion>
|
||||
<ComponentsWebAssemblyMinorVersion>2</ComponentsWebAssemblyMinorVersion>
|
||||
<ComponentsWebAssemblyPatchVersion>1</ComponentsWebAssemblyPatchVersion>
|
||||
|
||||
<!--
|
||||
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
|
||||
-->
|
||||
|
|
@ -19,9 +24,6 @@
|
|||
<IncludePreReleaseLabelInPackageVersion Condition=" '$(DotNetFinalVersionKind)' == 'release' ">false</IncludePreReleaseLabelInPackageVersion>
|
||||
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
|
||||
<PreReleaseBrandingLabel>Servicing</PreReleaseBrandingLabel>
|
||||
<!-- Blazor Client packages will not RTM with 3.1 -->
|
||||
<BlazorClientPreReleasePreviewNumber>4</BlazorClientPreReleasePreviewNumber>
|
||||
<BlazorClientPreReleaseVersionLabel>preview$(BlazorClientPreReleasePreviewNumber)</BlazorClientPreReleaseVersionLabel>
|
||||
<AspNetCoreMajorMinorVersion>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)</AspNetCoreMajorMinorVersion>
|
||||
<!-- The following property may need to be updated if ingesting new versions of Extensions.Refs package. The package override version is used to create PackageOverrides.txt in the targeting pack. -->
|
||||
<MicrosoftInternalExtensionsRefsPackageOverrideVersion>3.1.0</MicrosoftInternalExtensionsRefsPackageOverrideVersion>
|
||||
|
|
@ -30,6 +32,7 @@
|
|||
<!-- Servicing builds have different characteristics for the way dependencies, framework references, and versions are handled. -->
|
||||
<IsServicingBuild Condition=" '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
|
||||
<VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix>
|
||||
<ComponentsWebAssemblyVersionPrefix>$(ComponentsWebAssemblyMajorVersion).$(ComponentsWebAssemblyMinorVersion).$(ComponentsWebAssemblyPatchVersion)</ComponentsWebAssemblyVersionPrefix>
|
||||
<!-- TargetingPackVersionPrefix is used by projects, like .deb and .rpm, which use slightly different version formats. -->
|
||||
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
|
||||
<!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. -->
|
||||
|
|
@ -92,12 +95,13 @@
|
|||
<SystemServiceProcessServiceControllerPackageVersion>4.7.0</SystemServiceProcessServiceControllerPackageVersion>
|
||||
<SystemTextEncodingsWebPackageVersion>4.7.1</SystemTextEncodingsWebPackageVersion>
|
||||
<SystemTextJsonPackageVersion>4.7.2</SystemTextJsonPackageVersion>
|
||||
<SystemNetHttpJsonPackageVersion>3.2.0</SystemNetHttpJsonPackageVersion>
|
||||
<SystemThreadingChannelsPackageVersion>4.7.1</SystemThreadingChannelsPackageVersion>
|
||||
<SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion>
|
||||
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
||||
<MicrosoftNETCorePlatformsPackageVersion>3.1.1</MicrosoftNETCorePlatformsPackageVersion>
|
||||
<!-- Packages from aspnet/Blazor -->
|
||||
<MicrosoftAspNetCoreBlazorMonoPackageVersion>3.1.0-preview4.19605.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
|
||||
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
|
||||
<!-- Packages from aspnet/Extensions -->
|
||||
<InternalAspNetCoreAnalyzersPackageVersion>3.1.5-servicing.20270.9</InternalAspNetCoreAnalyzersPackageVersion>
|
||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.5-servicing.20270.9</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ try {
|
|||
Get-ChildItem "$repoRoot/*.sln" -Recurse `
|
||||
| ? {
|
||||
# These .sln files are used by the templating engine.
|
||||
($_.Name -ne "BlazorServerWeb_CSharp.sln")
|
||||
(($_.Name -ne "BlazorServerWeb_CSharp.sln") -and ($_.Name -ne 'ComponentsWebAssembly-CSharp.sln'))
|
||||
} `
|
||||
| % {
|
||||
Write-Host " Checking $(Split-Path -Leaf $_)"
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IComponentsApplicationBuilder"/>.
|
||||
/// </summary>
|
||||
public static class ComponentsApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Associates the component type with the application,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
/// </summary>
|
||||
/// <param name="app">The <see cref="IComponentsApplicationBuilder"/>.</param>
|
||||
/// <typeparam name="TComponent">The type of the component.</typeparam>
|
||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
||||
public static void AddComponent<TComponent>(this IComponentsApplicationBuilder app, string domElementSelector)
|
||||
where TComponent : IComponent
|
||||
{
|
||||
app.AddComponent(typeof(TComponent), domElementSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder for adding components to an application.
|
||||
/// </summary>
|
||||
public interface IComponentsApplicationBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the application services.
|
||||
/// </summary>
|
||||
IServiceProvider Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Associates the <see cref="IComponent"/> with the application,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The type of the component.</param>
|
||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
||||
void AddComponent(Type componentType, string domElementSelector);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to create an instance of Blazor host builder for a Browser application.
|
||||
/// </summary>
|
||||
public static class BlazorWebAssemblyHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="IWebAssemblyHostBuilder"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
||||
public static IWebAssemblyHostBuilder CreateDefaultBuilder()
|
||||
{
|
||||
return new WebAssemblyHostBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
// Keeping this simple for now to focus on predictable and reasonable behaviors.
|
||||
// Startup in WebHost supports lots of things we don't yet support, and some we
|
||||
// may never support.
|
||||
//
|
||||
// Possible additions:
|
||||
// - environments
|
||||
// - case-insensitivity (makes sense with environments)
|
||||
//
|
||||
// Likely never:
|
||||
// - statics
|
||||
// - DI into constructor
|
||||
internal class ConventionBasedStartup : IBlazorStartup
|
||||
{
|
||||
public ConventionBasedStartup(object instance)
|
||||
{
|
||||
Instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
public object Instance { get; }
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app, IServiceProvider services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = GetConfigureMethod();
|
||||
Debug.Assert(method != null);
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
var arguments = new object[parameters.Length];
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
arguments[i] = parameter.ParameterType == typeof(IComponentsApplicationBuilder)
|
||||
? app
|
||||
: services.GetRequiredService(parameter.ParameterType);
|
||||
}
|
||||
|
||||
method.Invoke(Instance, arguments);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is TargetInvocationException)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal MethodInfo GetConfigureMethod()
|
||||
{
|
||||
var methods = Instance.GetType()
|
||||
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(m => string.Equals(m.Name, "Configure", StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
if (methods.Length == 1)
|
||||
{
|
||||
return methods[0];
|
||||
}
|
||||
else if (methods.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The startup class must define a 'Configure' method.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Overloading the 'Configure' method is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = GetConfigureServicesMethod();
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(Instance, new object[] { services });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is TargetInvocationException)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal MethodInfo GetConfigureServicesMethod()
|
||||
{
|
||||
return Instance.GetType()
|
||||
.GetMethod(
|
||||
"ConfigureServices",
|
||||
BindingFlags.Public | BindingFlags.Instance,
|
||||
null,
|
||||
new Type[] { typeof(IServiceCollection), },
|
||||
Array.Empty<ParameterModifier>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
internal interface IBlazorStartup
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
|
||||
void Configure(IComponentsApplicationBuilder app, IServiceProvider services);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// A program abstraction.
|
||||
/// </summary>
|
||||
public interface IWebAssemblyHost : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The programs configured services.
|
||||
/// </summary>
|
||||
IServiceProvider Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start the program.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Used to abort program start.</param>
|
||||
/// <returns></returns>
|
||||
Task StartAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to gracefully stop the program.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
|
||||
/// <returns></returns>
|
||||
Task StopAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for configuring a Blazor browser-based application.
|
||||
/// </summary>
|
||||
public interface IWebAssemblyHostBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
|
||||
|
||||
/// <summary>
|
||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate">The delegate for configuring the <see cref="IServiceCollection"/> that will be used
|
||||
/// to construct the <see cref="IServiceProvider"/>.</param>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
IWebAssemblyHostBuilder ConfigureServices(Action<WebAssemblyHostBuilderContext, IServiceCollection> configureDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Run the given actions to initialize the host. This can only be called once.
|
||||
/// </summary>
|
||||
/// <returns>An initialized <see cref="IWebAssemblyHost"/></returns>
|
||||
IWebAssemblyHost Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs
|
||||
|
||||
internal interface IWebAssemblyServiceFactoryAdapter
|
||||
{
|
||||
object CreateBuilder(IServiceCollection services);
|
||||
|
||||
IServiceProvider CreateServiceProvider(object containerBuilder);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
internal class WebAssemblyBlazorApplicationBuilder : IComponentsApplicationBuilder
|
||||
{
|
||||
public WebAssemblyBlazorApplicationBuilder(IServiceProvider services)
|
||||
{
|
||||
Entries = new List<(Type componentType, string domElementSelector)>();
|
||||
Services = services;
|
||||
}
|
||||
|
||||
public List<(Type componentType, string domElementSelector)> Entries { get; }
|
||||
|
||||
public IServiceProvider Services { get; }
|
||||
|
||||
public void AddComponent(Type componentType, string domElementSelector)
|
||||
{
|
||||
if (componentType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentType));
|
||||
}
|
||||
|
||||
if (domElementSelector == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(domElementSelector));
|
||||
}
|
||||
|
||||
Entries.Add((componentType, domElementSelector));
|
||||
}
|
||||
|
||||
public async Task<WebAssemblyRenderer> CreateRendererAsync()
|
||||
{
|
||||
var loggerFactory = (ILoggerFactory)Services.GetService(typeof(ILoggerFactory));
|
||||
var renderer = new WebAssemblyRenderer(Services, loggerFactory);
|
||||
for (var i = 0; i < Entries.Count; i++)
|
||||
{
|
||||
var (componentType, domElementSelector) = Entries[i];
|
||||
await renderer.AddComponentAsync(componentType, domElementSelector);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor.Rendering;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
internal class WebAssemblyHost : IWebAssemblyHost
|
||||
{
|
||||
private readonly IJSRuntime _runtime;
|
||||
|
||||
private IServiceScope _scope;
|
||||
private WebAssemblyRenderer _renderer;
|
||||
|
||||
public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime)
|
||||
{
|
||||
Services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
}
|
||||
|
||||
public IServiceProvider Services { get; }
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return StartAsyncAwaited();
|
||||
}
|
||||
|
||||
private async Task StartAsyncAwaited()
|
||||
{
|
||||
var scopeFactory = Services.GetRequiredService<IServiceScopeFactory>();
|
||||
_scope = scopeFactory.CreateScope();
|
||||
|
||||
try
|
||||
{
|
||||
var startup = _scope.ServiceProvider.GetService<IBlazorStartup>();
|
||||
if (startup == null)
|
||||
{
|
||||
var message =
|
||||
$"Could not find a registered Blazor Startup class. " +
|
||||
$"Using {nameof(IWebAssemblyHost)} requires a call to {nameof(IWebAssemblyHostBuilder)}.UseBlazorStartup.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
// Note that we differ from the WebHost startup path here by using a 'scope' for the app builder
|
||||
// as well as the Configure method.
|
||||
var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider);
|
||||
startup.Configure(builder, _scope.ServiceProvider);
|
||||
|
||||
_renderer = await builder.CreateRendererAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_scope.Dispose();
|
||||
_scope = null;
|
||||
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.Dispose();
|
||||
_renderer = null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_scope != null)
|
||||
{
|
||||
_scope.Dispose();
|
||||
_scope = null;
|
||||
}
|
||||
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.Dispose();
|
||||
_renderer = null;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(Services as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Blazor.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
//
|
||||
// This code was taken virtually as-is from the Microsoft.Extensions.Hosting project in aspnet/Hosting and then
|
||||
// lots of things were removed.
|
||||
//
|
||||
internal class WebAssemblyHostBuilder : IWebAssemblyHostBuilder
|
||||
{
|
||||
private List<Action<WebAssemblyHostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<WebAssemblyHostBuilderContext, IServiceCollection>>();
|
||||
private bool _hostBuilt;
|
||||
private WebAssemblyHostBuilderContext _BrowserHostBuilderContext;
|
||||
private IWebAssemblyServiceFactoryAdapter _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
|
||||
private IServiceProvider _appServices;
|
||||
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
public IWebAssemblyHostBuilder ConfigureServices(Action<WebAssemblyHostBuilderContext, IServiceCollection> configureDelegate)
|
||||
{
|
||||
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
|
||||
{
|
||||
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
|
||||
{
|
||||
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(() => _BrowserHostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the given actions to initialize the host. This can only be called once.
|
||||
/// </summary>
|
||||
/// <returns>An initialized <see cref="IWebAssemblyHost"/></returns>
|
||||
public IWebAssemblyHost Build()
|
||||
{
|
||||
if (_hostBuilt)
|
||||
{
|
||||
throw new InvalidOperationException("Build can only be called once.");
|
||||
}
|
||||
_hostBuilt = true;
|
||||
|
||||
CreateBrowserHostBuilderContext();
|
||||
CreateServiceProvider();
|
||||
|
||||
return _appServices.GetRequiredService<IWebAssemblyHost>();
|
||||
}
|
||||
|
||||
private void CreateBrowserHostBuilderContext()
|
||||
{
|
||||
_BrowserHostBuilderContext = new WebAssemblyHostBuilderContext(Properties);
|
||||
}
|
||||
|
||||
private void CreateServiceProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(_BrowserHostBuilderContext);
|
||||
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
||||
services.AddSingleton<HttpClient>(s =>
|
||||
{
|
||||
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
|
||||
var navigationManager = s.GetRequiredService<NavigationManager>();
|
||||
return new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(navigationManager.BaseUri)
|
||||
};
|
||||
});
|
||||
|
||||
// Needed for authorization
|
||||
// However, since authorization isn't on by default, we could consider removing these and
|
||||
// having a separate services.AddBlazorAuthorization() call that brings in the required services.
|
||||
services.AddOptions();
|
||||
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>)));
|
||||
|
||||
foreach (var configureServicesAction in _configureServicesActions)
|
||||
{
|
||||
configureServicesAction(_BrowserHostBuilderContext, services);
|
||||
}
|
||||
|
||||
var builder = _serviceProviderFactory.CreateBuilder(services);
|
||||
_appServices = _serviceProviderFactory.CreateServiceProvider(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Context containing the common services on the <see cref="IWebAssemblyHost" />. Some properties may be null until set by the <see cref="IWebAssemblyHost" />.
|
||||
/// </summary>
|
||||
public sealed class WebAssemblyHostBuilderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="WebAssemblyHostBuilderContext" />.
|
||||
/// </summary>
|
||||
/// <param name="properties">The property collection.</param>
|
||||
public WebAssemblyHostBuilderContext(IDictionary<object, object> properties)
|
||||
{
|
||||
Properties = properties ?? throw new System.ArgumentNullException(nameof(properties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Properties { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides Blazor-specific support for <see cref="IWebAssemblyHost"/>.
|
||||
/// </summary>
|
||||
public static class WebAssemblyHostBuilderExtensions
|
||||
{
|
||||
private const string BlazorStartupKey = "Blazor.Startup";
|
||||
|
||||
/// <summary>
|
||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IWebAssemblyHostBuilder" /> to configure.</param>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
|
||||
public static IWebAssemblyHostBuilder ConfigureServices(this IWebAssemblyHostBuilder hostBuilder, Action<IServiceCollection> configureDelegate)
|
||||
{
|
||||
return hostBuilder.ConfigureServices((context, collection) => configureDelegate(collection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the <see cref="IWebAssemblyHostBuilder"/> to use the provided startup class.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IWebAssemblyHostBuilder"/>.</param>
|
||||
/// <param name="startupType">A type that configures a Blazor application.</param>
|
||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
||||
public static IWebAssemblyHostBuilder UseBlazorStartup(this IWebAssemblyHostBuilder builder, Type startupType)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (builder.Properties.ContainsKey(BlazorStartupKey))
|
||||
{
|
||||
throw new InvalidOperationException("A startup class has already been registered.");
|
||||
}
|
||||
|
||||
// It would complicate the implementation to allow multiple startup classes, and we don't
|
||||
// really have a need for it.
|
||||
builder.Properties.Add(BlazorStartupKey, bool.TrueString);
|
||||
|
||||
var startup = new ConventionBasedStartup(Activator.CreateInstance(startupType));
|
||||
|
||||
builder.ConfigureServices(startup.ConfigureServices);
|
||||
builder.ConfigureServices(s => s.AddSingleton<IBlazorStartup>(startup));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the <see cref="IWebAssemblyHostBuilder"/> to use the provided startup class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TStartup">A type that configures a Blazor application.</typeparam>
|
||||
/// <param name="builder">The <see cref="IWebAssemblyHostBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
|
||||
public static IWebAssemblyHostBuilder UseBlazorStartup<TStartup>(this IWebAssemblyHostBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
return UseBlazorStartup(builder, typeof(TStartup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IWebAssemblyHost"/>.
|
||||
/// </summary>
|
||||
public static class WebAssemblyHostExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the application.
|
||||
/// </summary>
|
||||
/// <param name="host">The <see cref="IWebAssemblyHost"/> to run.</param>
|
||||
/// <remarks>
|
||||
/// Currently, Blazor applications running in the browser don't have a lifecycle - the application does not
|
||||
/// get a chance to gracefully shut down. For now, <see cref="Run(IWebAssemblyHost)"/> simply starts the host
|
||||
/// and allows execution to continue.
|
||||
/// </remarks>
|
||||
public static void Run(this IWebAssemblyHost host)
|
||||
{
|
||||
// Behave like async void, because we don't yet support async-main properly on WebAssembly.
|
||||
// However, don't actualy make this method async, because we rely on startup being synchronous
|
||||
// for things like attaching navigation event handlers.
|
||||
host.StartAsync().ContinueWith(task =>
|
||||
{
|
||||
if (task.Exception != null)
|
||||
{
|
||||
Console.WriteLine(task.Exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs
|
||||
|
||||
internal class WebAssemblyServiceFactoryAdapter<TContainerBuilder> : IWebAssemblyServiceFactoryAdapter
|
||||
{
|
||||
private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
|
||||
private readonly Func<WebAssemblyHostBuilderContext> _contextResolver;
|
||||
private Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;
|
||||
|
||||
public WebAssemblyServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
|
||||
{
|
||||
_serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory));
|
||||
}
|
||||
|
||||
public WebAssemblyServiceFactoryAdapter(Func<WebAssemblyHostBuilderContext> contextResolver, Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
|
||||
{
|
||||
_contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver));
|
||||
_factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver));
|
||||
}
|
||||
|
||||
public object CreateBuilder(IServiceCollection services)
|
||||
{
|
||||
if (_serviceProviderFactory == null)
|
||||
{
|
||||
_serviceProviderFactory = _factoryResolver(_contextResolver());
|
||||
|
||||
if (_serviceProviderFactory == null)
|
||||
{
|
||||
throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
|
||||
}
|
||||
}
|
||||
return _serviceProviderFactory.CreateBuilder(services);
|
||||
}
|
||||
|
||||
public IServiceProvider CreateServiceProvider(object containerBuilder)
|
||||
{
|
||||
if (_serviceProviderFactory == null)
|
||||
{
|
||||
throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
|
||||
}
|
||||
|
||||
return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures options for the WebAssembly HTTP message handler.
|
||||
/// </summary>
|
||||
public static class WebAssemblyHttpMessageHandlerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the default value of the 'credentials' option on outbound HTTP requests.
|
||||
/// Defaults to <see cref="FetchCredentialsOption.SameOrigin"/>.
|
||||
/// </summary>
|
||||
public static FetchCredentialsOption DefaultCredentials
|
||||
{
|
||||
get
|
||||
{
|
||||
var valueString = MonoDefaultCredentialsGetter.Value();
|
||||
var result = default(FetchCredentialsOption);
|
||||
if (valueString != null)
|
||||
{
|
||||
Enum.TryParse(valueString, out result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
MonoDefaultCredentialsSetter.Value(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
static Func<Type> MonoWasmHttpMessageHandlerType = ()
|
||||
=> Assembly.Load("WebAssembly.Net.Http")
|
||||
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
|
||||
|
||||
static Func<Type> MonoFetchCredentialsOptionType = ()
|
||||
=> Assembly.Load("WebAssembly.Net.Http")
|
||||
.GetType("WebAssembly.Net.Http.HttpClient.FetchCredentialsOption");
|
||||
|
||||
static Lazy<PropertyInfo> MonoDefaultCredentialsProperty = new Lazy<PropertyInfo>(
|
||||
() => MonoWasmHttpMessageHandlerType()?.GetProperty("DefaultCredentials", BindingFlags.Public | BindingFlags.Static));
|
||||
|
||||
static Lazy<Func<string>> MonoDefaultCredentialsGetter = new Lazy<Func<string>>(() =>
|
||||
{
|
||||
return () => MonoDefaultCredentialsProperty.Value?.GetValue(null).ToString();
|
||||
});
|
||||
|
||||
static Lazy<Action<string>> MonoDefaultCredentialsSetter = new Lazy<Action<string>>(() =>
|
||||
{
|
||||
var fetchCredentialsOptionsType = MonoFetchCredentialsOptionType();
|
||||
return value => MonoDefaultCredentialsProperty.Value?.SetValue(null, Enum.Parse(fetchCredentialsOptionsType, value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.AspNetCore.Blazor.Services;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods called by interop. Intended for framework use only, not supported for use in application
|
||||
/// code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class JSInteropMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string uri, bool isInterceptedLink)
|
||||
{
|
||||
WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.WebAssembly.Interop" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Web" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" />
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" />
|
||||
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger
|
||||
{
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return NoOpDisposable.Instance;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return logLevel >= LogLevel.Warning;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
var formattedMessage = formatter(state, exception);
|
||||
Console.WriteLine($"[{logLevel}] {formattedMessage}");
|
||||
}
|
||||
|
||||
private class NoOpDisposable : IDisposable
|
||||
{
|
||||
public static NoOpDisposable Instance = new NoOpDisposable();
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Mono.WebAssembly.Interop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
internal sealed class WebAssemblyJSRuntime : MonoWebAssemblyJSRuntime
|
||||
{
|
||||
private static readonly WebAssemblyJSRuntime _instance = new WebAssemblyJSRuntime();
|
||||
private static bool _initialized;
|
||||
|
||||
public WebAssemblyJSRuntime()
|
||||
{
|
||||
JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter());
|
||||
}
|
||||
|
||||
public static WebAssemblyJSRuntime Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
// This is executing in MonoWASM. Consequently we do not to have concern ourselves with thread safety.
|
||||
_initialized = true;
|
||||
Initialize(_instance);
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
internal class WebAssemblyLoggerFactory : ILoggerFactory
|
||||
{
|
||||
public void AddProvider(ILoggerProvider provider)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
=> new WebAssemblyConsoleLogger<object>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Blazor.Hosting;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Hosting
|
||||
{
|
||||
public class ConventionBasedStartupTest
|
||||
{
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_GetConfigureServicesMethod_FindsConfigureServices()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup1());
|
||||
|
||||
// Act
|
||||
var method = startup.GetConfigureServicesMethod();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(IServiceCollection), method.GetParameters()[0].ParameterType);
|
||||
}
|
||||
|
||||
private class MyStartup1
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// Ignored
|
||||
public void ConfigureServices(DateTime x)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Ignored
|
||||
private void ConfigureServices(int x)
|
||||
{
|
||||
}
|
||||
|
||||
// Ignored
|
||||
public static void ConfigureServices(string x)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_GetConfigureServicesMethod_NoMethodFound()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup2());
|
||||
|
||||
// Act
|
||||
var method = startup.GetConfigureServicesMethod();
|
||||
|
||||
// Assert
|
||||
Assert.Null(method);
|
||||
}
|
||||
|
||||
private class MyStartup2
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_ConfigureServices_CallsMethod()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup3());
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Act
|
||||
startup.ConfigureServices(services);
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(services);
|
||||
}
|
||||
|
||||
private class MyStartup3
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton("foo");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_ConfigureServices_NoMethodFound()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup4());
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Act
|
||||
startup.ConfigureServices(services);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(services);
|
||||
}
|
||||
|
||||
private class MyStartup4
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_GetConfigureMethod_FindsConfigure()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup5());
|
||||
|
||||
// Act
|
||||
var method = startup.GetConfigureMethod();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(method.GetParameters());
|
||||
}
|
||||
|
||||
private class MyStartup5
|
||||
{
|
||||
public void Configure()
|
||||
{
|
||||
}
|
||||
|
||||
// Ignored
|
||||
private void Configure(int x)
|
||||
{
|
||||
}
|
||||
|
||||
// Ignored
|
||||
public static void Configure(string x)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_GetConfigureMethod_NoMethodFoundThrows()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup6());
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => startup.GetConfigureMethod());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The startup class must define a 'Configure' method.", ex.Message);
|
||||
}
|
||||
|
||||
private class MyStartup6
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_GetConfigureMethod_OverloadedThrows()
|
||||
{
|
||||
// Arrange
|
||||
var startup = new ConventionBasedStartup(new MyStartup7());
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => startup.GetConfigureMethod());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Overloading the 'Configure' method is not supported.", ex.Message);
|
||||
}
|
||||
|
||||
private class MyStartup7
|
||||
{
|
||||
public void Configure()
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(string x)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConventionBasedStartup_Configure()
|
||||
{
|
||||
// Arrange
|
||||
var instance = new MyStartup8();
|
||||
var startup = new ConventionBasedStartup(instance);
|
||||
|
||||
var services = new ServiceCollection().AddSingleton("foo").BuildServiceProvider();
|
||||
var builder = new WebAssemblyBlazorApplicationBuilder(services);
|
||||
|
||||
// Act
|
||||
startup.Configure(builder, services);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
instance.Arguments,
|
||||
a => Assert.Same(builder, a),
|
||||
a => Assert.Equal("foo", a));
|
||||
}
|
||||
|
||||
private class MyStartup8
|
||||
{
|
||||
public List<object> Arguments { get; } = new List<object>();
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app, string foo)
|
||||
{
|
||||
Arguments.Add(app);
|
||||
Arguments.Add(foo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting.Test
|
||||
{
|
||||
public class WebAssemblyHostBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void HostBuilder_CanCallBuild_BuildsServices()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
|
||||
// Act
|
||||
var host = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(host.Services.GetService(typeof(IWebAssemblyHost)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostBuilder_CanConfigureAdditionalServices()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
builder.ConfigureServices((c, s) => s.AddSingleton<string>("foo"));
|
||||
builder.ConfigureServices((c, s) => s.AddSingleton<StringBuilder>(new StringBuilder("bar")));
|
||||
|
||||
// Act
|
||||
var host = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("foo", host.Services.GetService(typeof(string)));
|
||||
Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostBuilder_UseBlazorStartup_CanConfigureAdditionalServices()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
builder.UseBlazorStartup<MyStartup>();
|
||||
builder.ConfigureServices((c, s) => s.AddSingleton<StringBuilder>(new StringBuilder("bar")));
|
||||
|
||||
// Act
|
||||
var host = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("foo", host.Services.GetService(typeof(string)));
|
||||
Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostBuilder_UseBlazorStartup_DoesNotAllowMultiple()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
builder.UseBlazorStartup<MyStartup>();
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseBlazorStartup<MyStartup>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A startup class has already been registered.", ex.Message);
|
||||
}
|
||||
|
||||
private class MyStartup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<string>("foo");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostBuilder_CanCustomizeServiceFactory()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
builder.UseServiceProviderFactory(new TestServiceProviderFactory());
|
||||
|
||||
// Act
|
||||
var host = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.IsType<TestServiceProvider>(host.Services);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostBuilder_CanCustomizeServiceFactoryWithContext()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
builder.UseServiceProviderFactory(context =>
|
||||
{
|
||||
Assert.NotNull(context.Properties);
|
||||
Assert.Same(builder.Properties, context.Properties);
|
||||
return new TestServiceProviderFactory();
|
||||
});
|
||||
|
||||
// Act
|
||||
var host = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.IsType<TestServiceProvider>(host.Services);
|
||||
}
|
||||
|
||||
private class TestServiceProvider : IServiceProvider
|
||||
{
|
||||
private readonly IServiceProvider _underlyingProvider;
|
||||
|
||||
public TestServiceProvider(IServiceProvider underlyingProvider)
|
||||
{
|
||||
_underlyingProvider = underlyingProvider;
|
||||
}
|
||||
|
||||
public object GetService(Type serviceType)
|
||||
{
|
||||
if (serviceType == typeof(IWebAssemblyHost))
|
||||
{
|
||||
// Since the test will make assertions about the resulting IWebAssemblyHost,
|
||||
// show that custom DI containers have the power to substitute themselves
|
||||
// as the IServiceProvider
|
||||
return new WebAssemblyHost(
|
||||
this, _underlyingProvider.GetRequiredService<IJSRuntime>());
|
||||
}
|
||||
else
|
||||
{
|
||||
return _underlyingProvider.GetService(serviceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
|
||||
{
|
||||
public IServiceCollection CreateBuilder(IServiceCollection services)
|
||||
{
|
||||
return new TestServiceCollection(services);
|
||||
}
|
||||
|
||||
public IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
|
||||
{
|
||||
Assert.IsType<TestServiceCollection>(serviceCollection);
|
||||
return new TestServiceProvider(serviceCollection.BuildServiceProvider());
|
||||
}
|
||||
|
||||
class TestServiceCollection : List<ServiceDescriptor>, IServiceCollection
|
||||
{
|
||||
public TestServiceCollection(IEnumerable<ServiceDescriptor> collection)
|
||||
: base(collection)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.AspNetCore.Components.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
using Mono.WebAssembly.Interop;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting.Test
|
||||
{
|
||||
public class WebAssemblyHostTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task BrowserHost_StartAsync_ThrowsWithoutStartup()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
var host = builder.Build();
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await host.StartAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Could not find a registered Blazor Startup class. " +
|
||||
"Using IWebAssemblyHost requires a call to IWebAssemblyHostBuilder.UseBlazorStartup.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BrowserHost_StartAsync_RunsConfigureMethod()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new WebAssemblyHostBuilder();
|
||||
|
||||
var startup = new MockStartup();
|
||||
builder.ConfigureServices((c, s) => { s.AddSingleton<IBlazorStartup>(startup); });
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
// Act
|
||||
await host.StartAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(startup.ConfigureCalled);
|
||||
}
|
||||
|
||||
private class MockStartup : IBlazorStartup
|
||||
{
|
||||
public bool ConfigureCalled { get; set; }
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app, IServiceProvider services)
|
||||
{
|
||||
ConfigureCalled = true;
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor" />
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. -->
|
||||
<Reference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
||||
{
|
||||
class ResolveRuntimeDependenciesCommand
|
||||
{
|
||||
public static void Command(CommandLineApplication command)
|
||||
{
|
||||
var referencesFile = command.Option("--references",
|
||||
"The path to a file that lists the paths to given referenced dll files",
|
||||
CommandOptionType.SingleValue);
|
||||
|
||||
var baseClassLibrary = command.Option("--base-class-library",
|
||||
"Full path to a directory in which BCL assemblies can be found",
|
||||
CommandOptionType.MultipleValue);
|
||||
|
||||
var outputPath = command.Option("--output",
|
||||
"Path to the output file that will contain the list with the full paths of the resolved assemblies",
|
||||
CommandOptionType.SingleValue);
|
||||
|
||||
var mainAssemblyPath = command.Argument("assembly",
|
||||
"Path to the assembly containing the entry point of the application.");
|
||||
|
||||
command.OnExecute(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(mainAssemblyPath.Value) ||
|
||||
!baseClassLibrary.HasValue() || !outputPath.HasValue())
|
||||
{
|
||||
command.ShowHelp(command.Name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var referencesSources = referencesFile.HasValue()
|
||||
? File.ReadAllLines(referencesFile.Value())
|
||||
: Array.Empty<string>();
|
||||
|
||||
RuntimeDependenciesResolver.ResolveRuntimeDependencies(
|
||||
mainAssemblyPath.Value,
|
||||
referencesSources,
|
||||
baseClassLibrary.Values.ToArray(),
|
||||
outputPath.Value());
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
|
||||
{
|
||||
internal class WriteBootJsonCommand
|
||||
{
|
||||
public static void Command(CommandLineApplication command)
|
||||
{
|
||||
var referencesFile = command.Option("--references",
|
||||
"The path to a file that lists the paths to given referenced dll files",
|
||||
CommandOptionType.SingleValue);
|
||||
|
||||
var embeddedResourcesFile = command.Option("--embedded-resources",
|
||||
"The path to a file that lists the paths of .NET assemblies that may contain embedded resources (typically, referenced assemblies in their pre-linked states)",
|
||||
CommandOptionType.SingleValue);
|
||||
|
||||
var outputPath = command.Option("--output",
|
||||
"Path to the output file",
|
||||
CommandOptionType.SingleValue);
|
||||
|
||||
var mainAssemblyPath = command.Argument("assembly",
|
||||
"Path to the assembly containing the entry point of the application.");
|
||||
|
||||
var linkerEnabledFlag = command.Option("--linker-enabled",
|
||||
"If set, specifies that the application is being built with linking enabled.",
|
||||
CommandOptionType.NoValue);
|
||||
|
||||
command.OnExecute(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(mainAssemblyPath.Value) || !outputPath.HasValue())
|
||||
{
|
||||
command.ShowHelp(command.Name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var referencesSources = referencesFile.HasValue()
|
||||
? File.ReadAllLines(referencesFile.Value())
|
||||
: Array.Empty<string>();
|
||||
|
||||
var embeddedResourcesSources = embeddedResourcesFile.HasValue()
|
||||
? File.ReadAllLines(embeddedResourcesFile.Value())
|
||||
: Array.Empty<string>();
|
||||
|
||||
BootJsonWriter.WriteFile(
|
||||
mainAssemblyPath.Value,
|
||||
referencesSources,
|
||||
embeddedResourcesSources,
|
||||
linkerEnabledFlag.HasValue(),
|
||||
outputPath.Value());
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Build.DevServer.Commands;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandLineApplication
|
||||
{
|
||||
Name = "Microsoft.AspNetCore.Blazor.Build"
|
||||
};
|
||||
app.HelpOption("-?|-h|--help");
|
||||
|
||||
app.Command("resolve-dependencies", ResolveRuntimeDependenciesCommand.Command);
|
||||
app.Command("write-boot-json", WriteBootJsonCommand.Command);
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
return app.Execute(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.ShowHelp();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
internal class BootJsonWriter
|
||||
{
|
||||
public static void WriteFile(
|
||||
string assemblyPath,
|
||||
string[] assemblyReferences,
|
||||
string[] embeddedResourcesSources,
|
||||
bool linkerEnabled,
|
||||
string outputPath)
|
||||
{
|
||||
var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources(
|
||||
embeddedResourcesSources, Path.GetDirectoryName(outputPath));
|
||||
var bootJsonText = GetBootJsonContent(
|
||||
Path.GetFileName(assemblyPath),
|
||||
GetAssemblyEntryPoint(assemblyPath),
|
||||
assemblyReferences,
|
||||
embeddedContent,
|
||||
linkerEnabled);
|
||||
var normalizedOutputPath = Path.GetFullPath(outputPath);
|
||||
Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
|
||||
File.WriteAllText(normalizedOutputPath, bootJsonText);
|
||||
}
|
||||
|
||||
public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable<EmbeddedResourceInfo> embeddedContent, bool linkerEnabled)
|
||||
{
|
||||
var data = new BootJsonData(
|
||||
assemblyFileName,
|
||||
entryPoint,
|
||||
assemblyReferences,
|
||||
embeddedContent,
|
||||
linkerEnabled);
|
||||
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
|
||||
private static string GetAssemblyEntryPoint(string assemblyPath)
|
||||
{
|
||||
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
|
||||
{
|
||||
var entryPoint = assemblyDefinition.EntryPoint;
|
||||
if (entryPoint == null)
|
||||
{
|
||||
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
|
||||
}
|
||||
|
||||
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the structure of a Blazor boot JSON file
|
||||
/// </summary>
|
||||
class BootJsonData
|
||||
{
|
||||
public string Main { get; }
|
||||
public string EntryPoint { get; }
|
||||
public IEnumerable<string> AssemblyReferences { get; }
|
||||
public IEnumerable<string> CssReferences { get; }
|
||||
public IEnumerable<string> JsReferences { get; }
|
||||
public bool LinkerEnabled { get; }
|
||||
|
||||
public BootJsonData(
|
||||
string entrypointAssemblyWithExtension,
|
||||
string entryPoint,
|
||||
IEnumerable<string> assemblyReferences,
|
||||
IEnumerable<EmbeddedResourceInfo> embeddedContent,
|
||||
bool linkerEnabled)
|
||||
{
|
||||
Main = entrypointAssemblyWithExtension;
|
||||
EntryPoint = entryPoint;
|
||||
AssemblyReferences = assemblyReferences;
|
||||
LinkerEnabled = linkerEnabled;
|
||||
|
||||
CssReferences = embeddedContent
|
||||
.Where(c => c.Kind == EmbeddedResourceKind.Css)
|
||||
.Select(c => c.RelativePath);
|
||||
|
||||
JsReferences = embeddedContent
|
||||
.Where(c => c.Kind == EmbeddedResourceKind.JavaScript)
|
||||
.Select(c => c.RelativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
internal class EmbeddedResourceInfo
|
||||
{
|
||||
public EmbeddedResourceKind Kind { get; }
|
||||
public string RelativePath { get; }
|
||||
|
||||
public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath)
|
||||
{
|
||||
Kind = kind;
|
||||
RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
internal enum EmbeddedResourceKind
|
||||
{
|
||||
JavaScript,
|
||||
Css,
|
||||
Static
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Mono.Cecil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
internal class EmbeddedResourcesProcessor
|
||||
{
|
||||
const string ContentSubdirName = "_content";
|
||||
|
||||
private readonly static Dictionary<string, EmbeddedResourceKind> _knownResourceKindsByNamePrefix = new Dictionary<string, EmbeddedResourceKind>
|
||||
{
|
||||
{ "blazor:js:", EmbeddedResourceKind.JavaScript },
|
||||
{ "blazor:css:", EmbeddedResourceKind.Css },
|
||||
{ "blazor:file:", EmbeddedResourceKind.Static },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Finds Blazor-specific embedded resources in the specified assemblies, writes them
|
||||
/// to disk, and returns a description of those resources in dependency order.
|
||||
/// </summary>
|
||||
/// <param name="referencedAssemblyPaths">The paths to assemblies that may contain embedded resources.</param>
|
||||
/// <param name="outputDir">The path to the directory where output is being written.</param>
|
||||
/// <returns>A description of the embedded resources that were written to disk.</returns>
|
||||
public static IReadOnlyList<EmbeddedResourceInfo> ExtractEmbeddedResources(
|
||||
IEnumerable<string> referencedAssemblyPaths, string outputDir)
|
||||
{
|
||||
// Clean away any earlier state
|
||||
var contentDir = Path.Combine(outputDir, ContentSubdirName);
|
||||
if (Directory.Exists(contentDir))
|
||||
{
|
||||
Directory.Delete(contentDir, recursive: true);
|
||||
}
|
||||
|
||||
// First, get an ordered list of AssemblyDefinition instances
|
||||
var referencedAssemblyDefinitions = referencedAssemblyPaths
|
||||
.Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want
|
||||
.Select(path => AssemblyDefinition.ReadAssembly(path))
|
||||
.ToList();
|
||||
referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst);
|
||||
|
||||
// Now process them in turn
|
||||
return referencedAssemblyDefinitions
|
||||
.SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir))
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
}
|
||||
|
||||
private static IEnumerable<EmbeddedResourceInfo> ExtractEmbeddedResourcesFromSingleAssembly(
|
||||
AssemblyDefinition assemblyDefinition, string outputDirPath)
|
||||
{
|
||||
var assemblyName = assemblyDefinition.Name.Name;
|
||||
foreach (var res in assemblyDefinition.MainModule.Resources)
|
||||
{
|
||||
if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo))
|
||||
{
|
||||
yield return extractedResourceInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo)
|
||||
{
|
||||
if (resource is EmbeddedResource embeddedResource)
|
||||
{
|
||||
if (TryInterpretLogicalName(resource.Name, out var kind, out var name))
|
||||
{
|
||||
// Prefix the output path with the assembly name to ensure no clashes
|
||||
// Also be invariant to the OS on which the package was built
|
||||
name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar));
|
||||
|
||||
// Write the file content to disk, ensuring we don't try to write outside the output root
|
||||
var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name));
|
||||
if (!outputPath.StartsWith(outputDirPath))
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}");
|
||||
}
|
||||
WriteResourceFile(embeddedResource, outputPath);
|
||||
|
||||
// The URLs we write into the boot json file need to use web-style directory separators
|
||||
extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
extractedResourceInfo = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void WriteResourceFile(EmbeddedResource resource, string outputPath)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
using (var outputStream = File.OpenWrite(outputPath))
|
||||
{
|
||||
resource.GetResourceStream().CopyTo(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name
|
||||
.Replace('\\', desiredSeparatorChar)
|
||||
.Replace('/', desiredSeparatorChar);
|
||||
|
||||
private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName)
|
||||
{
|
||||
foreach (var kvp in _knownResourceKindsByNamePrefix)
|
||||
{
|
||||
if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal))
|
||||
{
|
||||
kind = kvp.Value;
|
||||
resolvedName = logicalName.Substring(kvp.Key.Length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
kind = default;
|
||||
resolvedName = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// For each assembly B that references A, we want the resources from A to be loaded before
|
||||
// the references for B (because B's resources might depend on A's resources)
|
||||
private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b)
|
||||
=> AssemblyHasReference(a, b) ? 1
|
||||
: AssemblyHasReference(b, a) ? -1
|
||||
: 0;
|
||||
|
||||
private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to)
|
||||
=> from.MainModule.AssemblyReferences
|
||||
.Select(reference => reference.Name)
|
||||
.Contains(to.Name.Name, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build
|
||||
{
|
||||
internal class RuntimeDependenciesResolver
|
||||
{
|
||||
public static void ResolveRuntimeDependencies(
|
||||
string entryPoint,
|
||||
string[] applicationDependencies,
|
||||
string[] monoBclDirectories,
|
||||
string outputFile)
|
||||
{
|
||||
var paths = ResolveRuntimeDependenciesCore(entryPoint, applicationDependencies, monoBclDirectories);
|
||||
File.WriteAllLines(outputFile, paths);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ResolveRuntimeDependenciesCore(
|
||||
string entryPoint,
|
||||
string[] applicationDependencies,
|
||||
string[] monoBclDirectories)
|
||||
{
|
||||
var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint));
|
||||
|
||||
var dependencies = applicationDependencies
|
||||
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
|
||||
.ToArray();
|
||||
|
||||
var bcl = monoBclDirectories
|
||||
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f)))
|
||||
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
|
||||
.ToArray();
|
||||
|
||||
var assemblyResolutionContext = new AssemblyResolutionContext(
|
||||
assembly,
|
||||
dependencies,
|
||||
bcl);
|
||||
|
||||
assemblyResolutionContext.ResolveAssemblies();
|
||||
|
||||
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
|
||||
return paths.Concat(FindPdbs(paths));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
|
||||
{
|
||||
return dllPaths
|
||||
.Select(path => Path.ChangeExtension(path, "pdb"))
|
||||
.Where(path => File.Exists(path));
|
||||
}
|
||||
|
||||
public class AssemblyResolutionContext
|
||||
{
|
||||
public AssemblyResolutionContext(
|
||||
AssemblyEntry assembly,
|
||||
AssemblyEntry[] dependencies,
|
||||
AssemblyEntry[] bcl)
|
||||
{
|
||||
Assembly = assembly;
|
||||
Dependencies = dependencies;
|
||||
Bcl = bcl;
|
||||
}
|
||||
|
||||
public AssemblyEntry Assembly { get; }
|
||||
public AssemblyEntry[] Dependencies { get; }
|
||||
public AssemblyEntry[] Bcl { get; }
|
||||
|
||||
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
|
||||
|
||||
internal void ResolveAssemblies()
|
||||
{
|
||||
var visitedAssemblies = new HashSet<string>();
|
||||
var pendingAssemblies = new Stack<AssemblyNameReference>();
|
||||
pendingAssemblies.Push(Assembly.Definition.Name);
|
||||
ResolveAssembliesCore();
|
||||
|
||||
void ResolveAssembliesCore()
|
||||
{
|
||||
while (pendingAssemblies.TryPop(out var current))
|
||||
{
|
||||
if (!visitedAssemblies.Contains(current.Name))
|
||||
{
|
||||
visitedAssemblies.Add(current.Name);
|
||||
|
||||
// Not all references will be resolvable within the Mono BCL, particularly
|
||||
// when building for server-side Blazor as you will be running on CoreCLR
|
||||
// and therefore may depend on System.* BCL assemblies that aren't present
|
||||
// in Mono WebAssembly. Skipping unresolved assemblies here is equivalent
|
||||
// to passing "--skip-unresolved true" to the Mono linker.
|
||||
var resolved = Resolve(current);
|
||||
if (resolved != null)
|
||||
{
|
||||
Results.Add(resolved);
|
||||
var references = GetAssemblyReferences(resolved);
|
||||
foreach (var reference in references)
|
||||
{
|
||||
pendingAssemblies.Push(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<AssemblyNameReference> GetAssemblyReferences(AssemblyEntry current) =>
|
||||
current.Definition.Modules.SelectMany(m => m.AssemblyReferences);
|
||||
|
||||
AssemblyEntry Resolve(AssemblyNameReference current)
|
||||
{
|
||||
if (Assembly.Definition.Name.Name == current.Name)
|
||||
{
|
||||
return Assembly;
|
||||
}
|
||||
|
||||
var referencedAssemblyCandidate = FindCandidate(current, Dependencies);
|
||||
var bclAssemblyCandidate = FindCandidate(current, Bcl);
|
||||
|
||||
// Resolution logic. For right now, we will prefer the mono BCL version of a given
|
||||
// assembly if there is a candidate assembly and an equivalent mono assembly.
|
||||
if (bclAssemblyCandidate != null)
|
||||
{
|
||||
return bclAssemblyCandidate;
|
||||
}
|
||||
|
||||
return referencedAssemblyCandidate;
|
||||
}
|
||||
|
||||
AssemblyEntry FindCandidate(AssemblyNameReference current, AssemblyEntry[] candidates)
|
||||
{
|
||||
// Do simple name match. Assume no duplicates.
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (current.Name == candidate.Definition.Name.Name)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToString(),nq}")]
|
||||
public class AssemblyEntry
|
||||
{
|
||||
public AssemblyEntry(string path, AssemblyDefinition definition)
|
||||
{
|
||||
Path = path;
|
||||
Definition = definition;
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public AssemblyDefinition Definition { get; set; }
|
||||
|
||||
public override string ToString() => Definition.FullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<Description>Build mechanism for ASP.NET Core Blazor applications.</Description>
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Pack settings -->
|
||||
<PropertyGroup>
|
||||
<!-- Producing this package requires building with NodeJS enabled. -->
|
||||
<IsPackable Condition="'$(BuildNodeJS)' == 'false'">false</IsPackable>
|
||||
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);Publish</GenerateNuspecDependsOn>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<NuspecFile>Microsoft.AspNetCore.Blazor.Build.nuspec</NuspecFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<NuspecProperty Include="configuration=$(Configuration)" />
|
||||
<NuspecProperty Include="publishDir=$(PublishDir)" />
|
||||
<NuspecProperty Include="componentsversion=$(ComponentsPackageVersion)" />
|
||||
<NuspecProperty Include="razorversion=$(MicrosoftAspNetCoreRazorDesignPackageVersion)" />
|
||||
<NuspecProperty Include="blazormonoversion=$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
||||
<NuspecProperty Include="PackageThirdPartyNoticesFile=$(PackageThirdPartyNoticesFile)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Composite" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
<Reference Include="Mono.Cecil" />
|
||||
<Reference Include="System.CodeDom" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
$CommonMetadataElements$
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.AspNetCore.Blazor.Mono" version="$blazormonoversion$" include="all" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
$CommonFileElements$
|
||||
<file src="$PackageThirdPartyNoticesFile$" target=".\THIRD-PARTY-NOTICES.txt" />
|
||||
<file src="build\**" target="build" />
|
||||
<file src="targets\**" target="targets" />
|
||||
<file src="$publishdir$**\*" target="tools/" />
|
||||
<file src="..\..\..\Web.JS\dist\$configuration$\blazor.*.js" target="tools/blazor" />
|
||||
</files>
|
||||
</package>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
|
||||
</Project>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="Blazor.MonoRuntime.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>
|
||||
|
||||
<!-- By default, enable auto rebuilds for debug builds. Note that the server will not enable it in production environments regardless. -->
|
||||
<BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug' AND '$(BlazorRebuildOnFileChange)' == ''">true</BlazorRebuildOnFileChange>
|
||||
|
||||
<!-- By default, enable debugging for debug builds. -->
|
||||
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
|
||||
|
||||
<!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. -->
|
||||
<NoRestartServerOnBuild>true</NoRestartServerOnBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<!-- Require rebuild if the targets change -->
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)../tools/</BlazorToolsDir>
|
||||
<BlazorBuildExe>dotnet "$(BlazorToolsDir)Microsoft.AspNetCore.Blazor.Build.dll"</BlazorBuildExe>
|
||||
|
||||
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Blazor.MonoRuntime.targets" />
|
||||
<Import Project="Publish.targets" />
|
||||
|
||||
<Target Name="GenerateBlazorMetadataFile" BeforeTargets="GetCopyToOutputDirectoryItems">
|
||||
<PropertyGroup>
|
||||
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
|
||||
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorEnableDebugging)'=='true'" Lines="debug:true" Overwrite="false" Encoding="Unicode"/>
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
||||
_ClearCurrentStaticWebAssetsForReferenceDiscovery
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_ClearCurrentStaticWebAssetsForReferenceDiscovery">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup Condition="'$(BlazorBuildReferenceFromSource)'==''">
|
||||
<BlazorJsPath>$(MSBuildThisFileDirectory)../tools/blazor/blazor.*.js</BlazorJsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Blazor build outputs">
|
||||
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
|
||||
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
||||
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
|
||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
|
||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
|
||||
<BaseBlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin/</BaseBlazorRuntimeBinOutputPath>
|
||||
<BaseBlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm/</BaseBlazorRuntimeWasmOutputPath>
|
||||
<BaseBlazorJsOutputPath>$(BaseBlazorRuntimeOutputPath)</BaseBlazorJsOutputPath>
|
||||
<BaseBlazorIntermediateOutputPath>blazor/</BaseBlazorIntermediateOutputPath>
|
||||
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
|
||||
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
|
||||
<BlazorBootJsonOutputPath>$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)</BlazorBootJsonOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,653 +0,0 @@
|
|||
<Project>
|
||||
|
||||
<Target
|
||||
Name="_BlazorCopyFilesToOutputDirectory"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
Inputs="@(BlazorItemOutput)"
|
||||
Outputs="@(BlazorItemOutput->'%(TargetOutputPath)')"
|
||||
AfterTargets="CopyFilesToOutputDirectory"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
|
||||
<!-- Copy the blazor output files -->
|
||||
<Copy
|
||||
SourceFiles="@(BlazorItemOutput)"
|
||||
DestinationFiles="@(BlazorItemOutput->'%(TargetOutputPath)')"
|
||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
||||
Retries="$(CopyRetryCount)"
|
||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
Condition="'@(BlazorItemOutput)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
|
||||
</Copy>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_BlazorTrackResolveReferencesDidRun" AfterTargets="ResolveReferences">
|
||||
<PropertyGroup>
|
||||
<!-- So we know we can trust @(ReferenceCopyLocalPaths) later -->
|
||||
<_BlazorResolveReferencesDidRun>true</_BlazorResolveReferencesDidRun>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_BlazorBuildReport"
|
||||
AfterTargets="_BlazorCopyFilesToOutputDirectory">
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorStatisticsOutput Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' == ''">normal</_BlazorStatisticsReportImportance>
|
||||
<_BlazorStatisticsReportImportance Condition="'$(BlazorOutputStatistics)' != ''">high</_BlazorStatisticsReportImportance>
|
||||
</PropertyGroup>
|
||||
|
||||
<Message Importance="high" Text="Blazor Build result -> @(_BlazorStatisticsOutput->Distinct()->Count()) files in $(TargetDir)dist" />
|
||||
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
|
||||
</Target>
|
||||
|
||||
<!-- Preparing blazor files for output:
|
||||
PrepareBlazorOutputs
|
||||
_PrepareBlazorOutputConfiguration
|
||||
_DefineBlazorCommonInputs
|
||||
_BlazorResolveOutputBinaries
|
||||
When link on build:
|
||||
_GenerateLinkerDescriptor
|
||||
_CollectBlazorLinkerDescriptors
|
||||
_LinkBlazorApplication
|
||||
_CollectLinkerOutputs
|
||||
When don't link on build:
|
||||
_CollectResolvedAssemblies
|
||||
_ResolveBlazorApplicationAssemblies
|
||||
_ReadResolvedBlazorApplicationAssemblies
|
||||
_IntermediateCopyBlazorApplicationAssemblies
|
||||
_TouchBlazorApplicationAssemblies
|
||||
_GenerateBlazorBootJson
|
||||
_BlazorCopyFilesToOutputDirectory
|
||||
|
||||
The process for doing builds goes as follows:
|
||||
Produce a hash file with the Hash SDK task and write that hash to a marker file.
|
||||
Produce a marker file that saves whether we are linking or not in this build so that we can take that as
|
||||
input in future builds and do the correct thing for incremental builds.
|
||||
We only produce marker files when the input changes, if the input doesn't change the marker stays the
|
||||
same.
|
||||
|
||||
If we are linking on this build the process is as follows:
|
||||
1) We determine if there are linker descriptors available, if not generate one.
|
||||
2) Collect the list of linker descriptors and create a marker for the linker if it doesn't exist or changed
|
||||
from a previous build.
|
||||
3) Run the linker in case the linker inputs marker is newer than the linker result file.
|
||||
4) Collect the outputs from the linker.
|
||||
|
||||
If we are not linking in this build the process is as follows:
|
||||
1) Resolve the assemblies for the application only if the inputs marker is newer than the resolved assemblies
|
||||
result file.
|
||||
2) Read the result file with the resolved assemblies.
|
||||
3) Copy the resolved assemblies to an intermediate folder.
|
||||
4) In case we are switching from linking to not linking, touch the files in the intermediate folder to ensure
|
||||
that updated versions of the files get copied to the output folder.
|
||||
|
||||
Once the binary outputs are resolved:
|
||||
1) Create a marker file with the resolved assemblies and the boot json data as inputs.
|
||||
2) If the marker file is newer than the boot json in the output folder, regenerate the
|
||||
boot json
|
||||
|
||||
Once all the outputs are resolved (static content + binary outputs + boot json)
|
||||
Copy all the files to the output folder.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<PrepareBlazorOutputs>
|
||||
_PrepareBlazorOutputConfiguration;
|
||||
_DefineBlazorCommonInputs;
|
||||
_BlazorResolveOutputBinaries;
|
||||
_GenerateBlazorBootJson;
|
||||
</PrepareBlazorOutputs>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PrepareBlazorOutputs" DependsOnTargets="$(PrepareBlazorOutputs)" />
|
||||
|
||||
<!--
|
||||
Prepare blazor outputs preamble:
|
||||
* Creates updated marker files (if necessary) for incremental builds.
|
||||
* Computes intermediate and final output paths.
|
||||
* Computes the list of static items to copy to the output folder.
|
||||
-->
|
||||
|
||||
<Target Name="_PrepareBlazorOutputConfiguration">
|
||||
<!--
|
||||
This task produces all the "final" paths for all the files we need to produce the final output.
|
||||
|
||||
The final folder is something like bin/<<Configuration>>/<<TargetFramework>>/dist
|
||||
/_framework/_bin <- This will contain either the BCL + app assemblies or the result of linking the app.
|
||||
/_framework/wasm <- This will contain the wsm runtime copied from the nuget package.
|
||||
/_framework/blazor.js <- This is the blazor.js file copied from the nuget package.
|
||||
/_framework/blazor.boot.json <- This is the boot json file
|
||||
|
||||
This task also defines some intermediate paths that we will use:
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker <- This will be used to create the output from the linker.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt <- This will be used to save the output files from
|
||||
the linker and use that as marker to identify whether or not we need to run the linker.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker.descriptor.xml <- This will be used to generate an XML descriptor
|
||||
for the mono linker.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache <- This is the marker file to track the inputs common
|
||||
inputs to the output generation process.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt <- Paths to all the copy-local referenced assemblies found
|
||||
during the build process (i.e., the @(ReferenceCopyLocalPaths) values). We need this because when publishing, the build doesn't
|
||||
necessarily also run so this is the only way we know which assemblies to include in linking/resolveassemblies.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache <- This is the marker file to track the
|
||||
switch from linking to not linking and viceversa.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache <- This is the marker file to track the inputs
|
||||
to the linker.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ <- This will be used to store the resolved assemblies
|
||||
before copying them to the output when linking is not enabled.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt <- This keeps track of all the resolved assemblies.
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json <- The generated boot json file
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache <- The marker file that track whether boot json needs to
|
||||
be regenerated.
|
||||
-->
|
||||
|
||||
<PropertyGroup Label="Build properties">
|
||||
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'false'"></_BlazorShouldLinkApplicationAssemblies>
|
||||
<_BlazorShouldLinkApplicationAssemblies Condition="$(BlazorLinkOnBuild) == 'true'">true</_BlazorShouldLinkApplicationAssemblies>
|
||||
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Static content to copy to the output folder">
|
||||
<MonoWasmFile Include="$(MonoWasmRuntimePath)**/*.*" />
|
||||
<BlazorJsFile Include="$(BlazorJsPath)" />
|
||||
<BlazorItemOutput Include="@(MonoWasmFile)">
|
||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>WebAssembly</Type>
|
||||
<IsStatic>true</IsStatic>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(BlazorJsFile)">
|
||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorJsOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>BlazorRuntime</Type>
|
||||
<IsStatic>true</IsStatic>
|
||||
</BlazorItemOutput>
|
||||
</ItemGroup>
|
||||
|
||||
<Error Condition="'@(BlazorJsFile->Count())' == '0'" Text="No JS files found in '$(BlazorJsPath)'" />
|
||||
|
||||
<ItemGroup Label="Static content supplied by NuGet packages">
|
||||
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
|
||||
<TargetOutputPath>$(TargetDir)$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</_BlazorPackageContentOutput>
|
||||
<BlazorItemOutput Include="@(_BlazorPackageContentOutput)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Intermediate output paths">
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
|
||||
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)$(BaseBlazorIntermediateOutputPath)</BlazorIntermediateOutputPath>
|
||||
<BlazorIntermediateOutputPath Condition="! $([System.IO.Path]::IsPathRooted($(BlazorIntermediateOutputPath)))">$([MSBuild]::Escape($([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(BlazorIntermediateOutputPath)'))'))))</BlazorIntermediateOutputPath>
|
||||
|
||||
<!-- Common marker files paths -->
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.basic.cache -->
|
||||
<BlazorBuildCommonInputsCache>$(BlazorIntermediateOutputPath)inputs.basic.cache</BlazorBuildCommonInputsCache>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.copylocal.txt -->
|
||||
<BlazorLocalReferencesOutputPath>$(BlazorIntermediateOutputPath)inputs.copylocal.txt</BlazorLocalReferencesOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linkerswitch.cache -->
|
||||
<BlazorBuildLinkerSwitchInputsCache>$(BlazorIntermediateOutputPath)inputs.linkerswitch.cache</BlazorBuildLinkerSwitchInputsCache>
|
||||
|
||||
<!-- Linker paths and marker files -->
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.linker.cache -->
|
||||
<BlazorBuildLinkerInputsCache>$(BlazorIntermediateOutputPath)inputs.linker.cache</BlazorBuildLinkerInputsCache>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
|
||||
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
|
||||
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linked.assemblies.txt -->
|
||||
<BlazorIntermediateLinkerResultFilePath>$(BlazorIntermediateOutputPath)linked.assemblies.txt</BlazorIntermediateLinkerResultFilePath>
|
||||
|
||||
<!-- Resolved assemblies paths and marker files -->
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolvedassemblies/ -->
|
||||
<BlazorIntermediateResolvedApplicationAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolvedassemblies/</BlazorIntermediateResolvedApplicationAssembliesOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolved.assemblies.txt -->
|
||||
<BlazorResolvedAssembliesOutputPath>$(BlazorIntermediateOutputPath)resolved.assemblies.txt</BlazorResolvedAssembliesOutputPath>
|
||||
|
||||
<!-- boot json related paths and markers -->
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/ -->
|
||||
<BlazorBootJsonIntermediateOutputDir>$(BlazorIntermediateOutputPath)</BlazorBootJsonIntermediateOutputDir>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
|
||||
<BlazorBootJsonIntermediateOutputPath>$(BlazorBootJsonIntermediateOutputDir)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.bootjson.cache -->
|
||||
<BlazorBuildBootJsonInputsCache>$(BlazorIntermediateOutputPath)inputs.bootjson.cache</BlazorBuildBootJsonInputsCache>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/resolve-dependencies.txt -->
|
||||
<BlazorResolveDependenciesFilePath>$(BlazorIntermediateOutputPath)resolve-dependencies.txt</BlazorResolveDependenciesFilePath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/bootjson-references.txt -->
|
||||
<BlazorBootJsonReferencesFilePath>$(BlazorIntermediateOutputPath)bootjson-references.txt</BlazorBootJsonReferencesFilePath>
|
||||
|
||||
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/embedded.resources.txt -->
|
||||
<BlazorEmbeddedResourcesConfigFilePath>$(BlazorIntermediateOutputPath)embedded.resources.txt</BlazorEmbeddedResourcesConfigFilePath>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Final output paths">
|
||||
<BlazorRuntimeBinOutputPath>$(TargetDir)$(BaseBlazorRuntimeBinOutputPath)</BlazorRuntimeBinOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_DefineBlazorCommonInputs">
|
||||
<!-- If ResolveReferences hasn't yet run, we must be inside a VS publish process
|
||||
that doesn't also do a build, so use the stored information. -->
|
||||
<ReadLinesFromFile
|
||||
Condition="'$(_BlazorResolveReferencesDidRun)'!='true'"
|
||||
File="$(BlazorLocalReferencesOutputPath)">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorDependencyInput"/>
|
||||
</ReadLinesFromFile>
|
||||
<ItemGroup Condition="'$(_BlazorResolveReferencesDidRun)'=='true'">
|
||||
<!-- ... otherwise we can get the fresh info from @(ReferenceCopyLocalPaths) -->
|
||||
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorCommonInput Include="@(IntermediateAssembly)" />
|
||||
<_BlazorCommonInput Include="@(_BlazorDependencyInput)" />
|
||||
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
|
||||
<_BlazorCommonInput Include="$(BlazorEnableDebugging)" />
|
||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" />
|
||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<Hash ItemsToHash="@(_BlazorCommonInput)">
|
||||
<Output TaskParameter="HashResult" PropertyName="_BlazorBuildBasicInputHash" />
|
||||
</Hash>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="$(_BlazorBuildBasicInputHash)"
|
||||
File="$(BlazorBuildCommonInputsCache)"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="@(_BlazorDependencyInput)"
|
||||
File="$(BlazorLocalReferencesOutputPath)"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<!-- Switch to detect when we switch from linking to not linking and viceversa -->
|
||||
<WriteLinesToFile
|
||||
Lines="@(_BlazorLinkingOption)"
|
||||
File="$(BlazorBuildLinkerSwitchInputsCache)"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorBuildLinkerSwitchInputsCache)" />
|
||||
<FileWrites Include="$(BlazorBuildCommonInputsCache)" />
|
||||
<FileWrites Include="$(BlazorLocalReferencesOutputPath)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_BlazorResolveOutputBinaries" DependsOnTargets="_CollectLinkerOutputs;_CollectResolvedAssemblies" />
|
||||
|
||||
<!--
|
||||
Linker enabled part of the pipeline:
|
||||
|
||||
* If there are no descriptors defined, generate a new linker descriptor.
|
||||
* Collect the list of descriptors and produce a marker file to determine when the
|
||||
inputs to the linker change in future builds.
|
||||
* Invoke the linker if the linker inputs marker file is newer than the linker outputs.
|
||||
* Read the outputs from the linker and add them to the list of blazor outputs.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<_CollectLinkerOutputsDependsOn>
|
||||
_GenerateLinkerDescriptor;
|
||||
_CollectBlazorLinkerDescriptors;
|
||||
_LinkBlazorApplication
|
||||
</_CollectLinkerOutputsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target
|
||||
Name="_CollectLinkerOutputs"
|
||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''"
|
||||
DependsOnTargets="$(_CollectLinkerOutputsDependsOn)">
|
||||
<!--
|
||||
Read the outputs from the linker (from this run or a previous run) and set them in an item group for
|
||||
later use.
|
||||
-->
|
||||
<ReadLinesFromFile File="$(BlazorIntermediateLinkerResultFilePath)">
|
||||
<Output TaskParameter="Lines" ItemName="_OptimizedFiles"/>
|
||||
</ReadLinesFromFile>
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.dll'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Assembly</Type>
|
||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.pdb'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Pdb</Type>
|
||||
</BlazorItemOutput>
|
||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_GenerateLinkerDescriptor"
|
||||
Inputs="$(BlazorBuildCommonInputsCache)"
|
||||
Outputs="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Condition="$(_BlazorShouldLinkApplicationAssemblies) != '' and '@(BlazorLinkerDescriptor)' == ''">
|
||||
|
||||
<ItemGroup>
|
||||
<_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" />
|
||||
<_GeneratedLinkerDescriptorLine Include="<linker>" />
|
||||
<_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" />
|
||||
<_GeneratedLinkerDescriptorLine Include="</linker>" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="@(_GeneratedLinkerDescriptorLine)"
|
||||
File="$(GeneratedBlazorLinkerDescriptor)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_CollectBlazorLinkerDescriptors">
|
||||
|
||||
<ItemGroup Condition="@(BlazorLinkerDescriptor) == ''">
|
||||
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
|
||||
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorLinkerInput Include="@(IntermediateAssembly)" />
|
||||
<_BlazorLinkerInput Include="@(_BlazorDependencyInput)" />
|
||||
<_BlazorLinkerInput Include="@(BlazorLinkerDescriptor)" />
|
||||
<_BlazorLinkerInput Include="$(AdditionalMonoLinkerOptions)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Hash ItemsToHash="@(_BlazorLinkerInput)">
|
||||
<Output TaskParameter="HashResult" PropertyName="_BlazorLinkerInputHash" />
|
||||
</Hash>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="$(_BlazorLinkerInputHash)"
|
||||
File="$(BlazorBuildLinkerInputsCache)"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorBuildLinkerInputsCache)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_LinkBlazorApplication"
|
||||
Condition="$(_BlazorShouldLinkApplicationAssemblies) != ''"
|
||||
Inputs="$(BlazorBuildLinkerInputsCache);
|
||||
@(IntermediateAssembly);
|
||||
@(_BlazorDependencyInput);
|
||||
@(BlazorLinkerDescriptor)"
|
||||
Outputs="$(BlazorIntermediateLinkerResultFilePath)"
|
||||
>
|
||||
<!--
|
||||
At this point we have decided to run the mono linker on the Blazor assembly and its dependencies.
|
||||
The steps to run the mono linker are the following:
|
||||
1) Clear the linker output directory if not clean before hand, as we don't know what the outputs of
|
||||
the linker will be.
|
||||
2) Run the linker on the main assembly, its dependencies and pass in the BCL folders to do the lookup
|
||||
for framework assemblies.
|
||||
3) Once we've run the linker we need to capture the produced output and generate a marker file containing
|
||||
the list of produced files. This file will act as a marker to skip running the linker if none of the inputs
|
||||
has changed.
|
||||
4) Add the file we just created to the list of file writes, to support incremental builds.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath);$(MonoWasmFrameworkPath)" />
|
||||
<_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a "%(Identity)"')" />
|
||||
<_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a "%(FullPath)"')" />
|
||||
<_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d "%(Identity)"')" />
|
||||
<_BlazorAssemblyDescriptorFiles
|
||||
Include="@(BlazorLinkerDescriptor->'-x "%(FullPath)"')" Condition="'@(BlazorLinkerDescriptor)' != ''" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Clear the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
|
||||
<Delete Files="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
|
||||
<!-- Run the linker and put the results in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker -->
|
||||
<Exec Command="dotnet "$(MonoLinkerPath)" $(_BlazorLinkerAdditionalOptions) @(_BlazorFolderLookupPaths, ' ') -o "$(BlazorIntermediateLinkerOutputPath)" @(_BlazorAssemblyDescriptorFiles, ' ') @(_BlazorAssembliesToLink, ' ')" />
|
||||
|
||||
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
|
||||
<ItemGroup>
|
||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Write the list of files in /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ into
|
||||
/obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt
|
||||
-->
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorIntermediateLinkerResultFilePath)"
|
||||
Lines="@(_BlazorLinkerOutput)"
|
||||
Overwrite="true" />
|
||||
|
||||
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linked.assemblies.txt to the list of written files. -->
|
||||
<!-- Add /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/*.dll to the list of written files. -->
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorIntermediateLinkerResultFilePath)" />
|
||||
<FileWrites Include="@(_BlazorLinkerOutput)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Linker disabled part of the pipeline:
|
||||
|
||||
* Run a CLI tool to produce the transitive closure of application references using the main application
|
||||
as entry point.
|
||||
* Read the list of resolved application references from the file produced by the previous step.
|
||||
* Copy the resolved application references into an intermediate folder.
|
||||
* If we are switching from linking to not linking
|
||||
Touch the files in the intermediate folder to ensure they are copied to the output and replace
|
||||
the linked versions with the same name.
|
||||
* Collect the list of resolved assemblies in the intermediate output folder and prepare them to be
|
||||
copied to their final destination in the output folder.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<_CollectResolvedAssembliesDependsOn>
|
||||
_ResolveBlazorApplicationAssemblies;
|
||||
_ReadResolvedBlazorApplicationAssemblies;
|
||||
_IntermediateCopyBlazorApplicationAssemblies;
|
||||
_TouchBlazorApplicationAssemblies
|
||||
</_CollectResolvedAssembliesDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target
|
||||
Name="_CollectResolvedAssemblies"
|
||||
DependsOnTargets="$(_CollectResolvedAssembliesDependsOn)"
|
||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''">
|
||||
|
||||
<!--
|
||||
At this point we have decided not to run the linker and instead to just copy the assemblies
|
||||
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
|
||||
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.dll'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Assembly</Type>
|
||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.pdb'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Pdb</Type>
|
||||
</BlazorItemOutput>
|
||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_ResolveBlazorApplicationAssemblies"
|
||||
Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''"
|
||||
Inputs="$(BlazorBuildCommonInputsCache);
|
||||
@(IntermediateAssembly);
|
||||
@(_BlazorDependencyInput)"
|
||||
Outputs="$(BlazorResolvedAssembliesOutputPath)"
|
||||
>
|
||||
|
||||
<PropertyGroup>
|
||||
<_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references "$(BlazorResolveDependenciesFilePath)"</_ReferencesArg>
|
||||
<_BclParameter>--base-class-library "$(MonoBaseClassLibraryPath)" --base-class-library "$(MonoBaseClassLibraryFacadesPath)" --base-class-library "$(MonoWasmFrameworkPath)"</_BclParameter>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorResolveDependenciesFilePath)"
|
||||
Lines="@(_BlazorDependencyInput)"
|
||||
Overwrite="true" />
|
||||
|
||||
<Exec Command="$(BlazorBuildExe) resolve-dependencies "@(IntermediateAssembly->'%(FullPath)')" $(_ReferencesArg) $(_BclParameter) --output "$(BlazorResolvedAssembliesOutputPath)"" />
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_ReadResolvedBlazorApplicationAssemblies">
|
||||
|
||||
<ReadLinesFromFile File="$(BlazorResolvedAssembliesOutputPath)">
|
||||
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
|
||||
</ReadLinesFromFile>
|
||||
|
||||
<ItemGroup>
|
||||
<_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorResolvedAssembliesOutputPath)" />
|
||||
<FileWrites Include="@(_IntermediateResolvedRuntimeDependencies)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_IntermediateCopyBlazorApplicationAssemblies"
|
||||
Inputs="@(_BlazorResolvedRuntimeDependencies)"
|
||||
Outputs="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')">
|
||||
|
||||
<Copy
|
||||
SourceFiles="@(_BlazorResolvedRuntimeDependencies)"
|
||||
DestinationFiles="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')"
|
||||
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
|
||||
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
|
||||
Retries="$(CopyRetryCount)"
|
||||
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
|
||||
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
|
||||
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" />
|
||||
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_TouchBlazorApplicationAssemblies"
|
||||
Inputs="$(BlazorBuildLinkerSwitchInputsCache)"
|
||||
Outputs="@(_IntermediateResolvedRuntimeDependencies)">
|
||||
|
||||
<Touch Files="@(_IntermediateResolvedRuntimeDependencies)" ForceTouch="true" />
|
||||
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Final part of the build pipeline:
|
||||
|
||||
* Collect the blazor application assemblies to be copied to the output and create a marker file.
|
||||
* Call our CLI tool to generate the boot json if the list of assemblies has changed.
|
||||
-->
|
||||
|
||||
<Target Name="_ResolveBlazorBootJsonInputs">
|
||||
<ItemGroup>
|
||||
<BlazorBootJsonInput Include="$(Configuration)" />
|
||||
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
|
||||
<BlazorBootJsonInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FullPath)')" />
|
||||
<BlazorBootJsonInput Include="@(_BlazorLinkingOption)" />
|
||||
<BlazorBootJsonInput Include="$(BlazorEnableDebugging)" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorBuildBootJsonInputsCache)"
|
||||
Lines="@(BlazorBootJsonInput)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(BlazorBuildBootJsonInputsCache)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="_GenerateBlazorBootJson"
|
||||
DependsOnTargets="_ResolveBlazorBootJsonInputs"
|
||||
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
|
||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
||||
<ItemGroup>
|
||||
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
|
||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
|
||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
|
||||
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)"</_ReferencesArg>
|
||||
<_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources "$(BlazorEmbeddedResourcesConfigFilePath)"</_EmbeddedResourcesArg>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
File="$(BlazorBootJsonReferencesFilePath)"
|
||||
Lines="@(_AppReferences)"
|
||||
Overwrite="true" />
|
||||
|
||||
<WriteLinesToFile
|
||||
Condition="'@(_UnlinkedAppReferencesPaths)' != ''"
|
||||
File="$(BlazorEmbeddedResourcesConfigFilePath)"
|
||||
Lines="@(_UnlinkedAppReferencesPaths)"
|
||||
Overwrite="true" />
|
||||
|
||||
<Exec Command="$(BlazorBuildExe) write-boot-json "@(IntermediateAssembly)" $(_ReferencesArg) $(_EmbeddedResourcesArg) $(_LinkerEnabledFlag) --output "$(BlazorBootJsonIntermediateOutputPath)"" />
|
||||
|
||||
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
|
||||
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
<_BlazorBootJsonEmbeddedContentFile Include="$(BlazorBootJsonIntermediateOutputDir)_content\**\*.*" />
|
||||
<BlazorItemOutput Include="@(_BlazorBootJson)">
|
||||
<TargetOutputPath>$(TargetDir)$(BlazorBootJsonOutputPath)</TargetOutputPath>
|
||||
<Type>BootJson</Type>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(_BlazorBootJsonEmbeddedContentFile)">
|
||||
<TargetOutputPath>$(TargetDir)dist/_content/%(RecursiveDir)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorItemOutput>
|
||||
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
|
||||
<FileWrites Include="@(_BlazorBootJsonEmbeddedContentFile)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild>
|
||||
<BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir>
|
||||
|
||||
<!-- Disable unwanted parts of the default publish process -->
|
||||
<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BlazorGetCopyToPublishDirectoryItems"
|
||||
BeforeTargets="GetCopyToPublishDirectoryItems"
|
||||
DependsOnTargets="PrepareBlazorOutputs"
|
||||
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
|
||||
<ItemGroup>
|
||||
<!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. -->
|
||||
<ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" />
|
||||
|
||||
<!-- Move wwwroot files to output root -->
|
||||
<ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))">
|
||||
<TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
||||
<!-- Publish all the 'dist' files -->
|
||||
<_BlazorGCTPDIDistFiles Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
|
||||
<_BlazorGCTPDI Include="@(_BlazorGCTPDIDistFiles)">
|
||||
<TargetPath>$(BlazorPublishDistDir)$([MSBuild]::MakeRelative('$(TargetDir)dist\', %(Identity)))</TargetPath>
|
||||
</_BlazorGCTPDI>
|
||||
|
||||
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">
|
||||
<TargetPath>%(TargetPath)</TargetPath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Replace the .blazor.config contents with what we need to serve in production -->
|
||||
<PropertyGroup>
|
||||
<_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile File="$(_BlazorConfigPath)" Lines="." Overwrite="true" />
|
||||
<WriteLinesToFile File="$(_BlazorConfigPath)" Lines="$(AssemblyName)/" Overwrite="false" />
|
||||
</Target>
|
||||
|
||||
<!-- The following target runs only for standalone publishing -->
|
||||
<Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory">
|
||||
<!-- Add a suitable web.config file if there isn't one already -->
|
||||
<ItemGroup>
|
||||
<_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/>
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile
|
||||
Condition="!Exists('$(PublishDir)web.config')"
|
||||
File="$(PublishDir)web.config"
|
||||
Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" />
|
||||
|
||||
<!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing -->
|
||||
<Delete Files="$(PublishDir)$(AssemblyName).blazor.config" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class BindRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public BindRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> ValueChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<int> OnChanged { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
|
||||
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElement_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", null, ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementWithSuffix_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
|
||||
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindDuplicates_ReportsDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
|
||||
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
}"));
|
||||
|
||||
// Act
|
||||
var result = CompileToCSharp(@"
|
||||
<div @bind-value=""@ParentValue"" />
|
||||
@code {
|
||||
public string ParentValue { get; set; } = ""hi"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(result.Diagnostics);
|
||||
Assert.Equal("RZ9989", diagnostic.Id);
|
||||
Assert.Equal(
|
||||
"The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
|
||||
"Test.BindAttributes" + Environment.NewLine +
|
||||
"Test.BindAttributes",
|
||||
diagnostic.GetMessage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/>
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
|
||||
public string Format { get; set; } = ""MM/dd/yyyy"";
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputText_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""text"" @bind=""@ParentValue"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input type=""checkbox"" @bind=""@Enabled"" />
|
||||
@code {
|
||||
public bool Enabled { get; set; }
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "checkbox", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindToElementFallback_WithFormat_WritesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" />
|
||||
@code {
|
||||
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
|
||||
}
|
||||
|
||||
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
|
||||
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "visible", 2),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 3),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
|
||||
}
|
||||
|
||||
[Fact] // See https://github.com/aspnet/Blazor/issues/703
|
||||
public void Workaround_703()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<input @bind-value=""@ParentValue"" @bind-value:event=""onchange"" type=""text"" visible />
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The workaround for 703 is that the value attribute MUST be after the type
|
||||
// attribute.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "input", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "type", "text", 1),
|
||||
frame => AssertFrame.Attribute(frame, "visible", 2),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 3),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
|
||||
}
|
||||
|
||||
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
|
||||
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_BodyContent()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<div @bind-value=""@ParentValue"" @bind-value:event=""onchange"">
|
||||
<span>@(42.ToString())</span>
|
||||
</div>
|
||||
@code {
|
||||
public int ParentValue { get; set; } = 42;
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 7, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "42", 1),
|
||||
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "span", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "42", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindFallback_InvalidSyntax_TooManyParts()
|
||||
{
|
||||
// Arrange & Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<input type=""text"" @bind-first-second-third=""Text"" />
|
||||
@code {
|
||||
public string Text { get; set; } = ""text"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("RZ9991", diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BindFallback_InvalidSyntax_TrailingDash()
|
||||
{
|
||||
// Arrange & Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<input type=""text"" @bind-first-=""Text"" />
|
||||
@code {
|
||||
public string Text { get; set; } = ""text"";
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("RZ9991", diagnostic.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class BootJsonWriterTest
|
||||
{
|
||||
[Fact]
|
||||
public void ProducesJsonReferencingAssemblyAndDependencies()
|
||||
{
|
||||
// Arrange/Act
|
||||
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
|
||||
var content = BootJsonWriter.GetBootJsonContent(
|
||||
"MyApp.Entrypoint.dll",
|
||||
"MyNamespace.MyType::MyMethod",
|
||||
assemblyReferences,
|
||||
Enumerable.Empty<EmbeddedResourceInfo>(),
|
||||
linkerEnabled: true);
|
||||
|
||||
// Assert
|
||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
||||
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value<string>());
|
||||
Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value<string>());
|
||||
Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncludesReferencesToEmbeddedContent()
|
||||
{
|
||||
// Arrange/Act
|
||||
var embeddedContent = new[]
|
||||
{
|
||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"),
|
||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"),
|
||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"),
|
||||
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"),
|
||||
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"),
|
||||
};
|
||||
var content = BootJsonWriter.GetBootJsonContent(
|
||||
"MyApp.Entrypoint",
|
||||
"MyNamespace.MyType::MyMethod",
|
||||
assemblyReferences: new[] { "Something.dll" },
|
||||
embeddedContent: embeddedContent,
|
||||
linkerEnabled: true);
|
||||
|
||||
// Assert
|
||||
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
|
||||
Assert.Equal(
|
||||
new[] { "css/first.css", "css/second.css" },
|
||||
parsedContent["cssReferences"].Values<string>());
|
||||
Assert.Equal(
|
||||
new[] { "javascript/first.js", "javascript/second.js" },
|
||||
parsedContent["jsReferences"].Values<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class ChildContentRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
private readonly CSharpSyntaxTree RenderChildContentComponent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderChildContent : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, ChildContent);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
private readonly CSharpSyntaxTree RenderChildContentStringComponent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderChildContentString : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, ChildContent, Value);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
private readonly CSharpSyntaxTree RenderMultipleChildContent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class RenderMultipleChildContent : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, Header, Name);
|
||||
builder.AddContent(1, ChildContent, Value);
|
||||
builder.AddContent(2, Footer);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> Header { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<string> ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Footer { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
public ChildContentRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContent>
|
||||
<div></div>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent_Generic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContentString Value=""HI"">
|
||||
<div>@context.ToLowerInvariant()</div>
|
||||
</RenderChildContentString>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 1),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 2),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "hi", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ExplicitChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderChildContent>
|
||||
<ChildContent>
|
||||
<div></div>
|
||||
</ChildContent>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_BodyChildContent_Recursive()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
|
||||
<RenderChildContent>
|
||||
<RenderChildContent>
|
||||
<div></div>
|
||||
</RenderChildContent>
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 2),
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 4),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_RenderFragmentOfString()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContentString ChildContent=""@template"" Value=""HI"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 4),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_NoArgTemplate()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment template = @<div>@(""HI"".ToLowerInvariant())</div>; }
|
||||
<RenderChildContent ChildContent=""@template"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_IgnoresEmptyBody()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")""></RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_AttributeChildContent_IgnoresWhitespaceBody()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderChildContent ChildContent=""@template(""HI"")"">
|
||||
|
||||
</RenderChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hi", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> header = context => @<div>@context.ToLowerInvariant()</div>; }
|
||||
<RenderMultipleChildContent Name=""billg"" Header=@header Value=""HI"">
|
||||
<ChildContent>Some @context.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 2),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 3),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 4),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 5),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "billg", 1),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""Context"">Some @Context.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
// Verifies that our check for reuse of parameter names isn't too aggressive.
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent_SetsSameName()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""item"">Some @item.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,616 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_Simple()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent/>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithParameters()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class SomeType
|
||||
{
|
||||
}
|
||||
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter] public int IntProperty { get; set; }
|
||||
[Parameter] public bool BoolProperty { get; set; }
|
||||
[Parameter] public string StringProperty { get; set; }
|
||||
[Parameter] public SomeType ObjectProperty { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent
|
||||
IntProperty=""123""
|
||||
BoolProperty=""true""
|
||||
StringProperty=""My string""
|
||||
ObjectProperty=""new SomeType()"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
||||
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
||||
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
||||
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_TriesToSetNonParamter()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
public int IntProperty { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent IntProperty=""123"" />");
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => GetRenderTree(component));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Object of type 'Test.MyComponent' has a property matching the name 'IntProperty', " +
|
||||
"but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithExplicitStringParameter()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string StringProperty { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent StringProperty=""@(42.ToString())"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithNonPropertyAttributes()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase, IComponent
|
||||
{
|
||||
Task IComponent.SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent some-attribute=""foo"" another-attribute=""@(42.ToString())"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "some-attribute", "foo", 1),
|
||||
frame => AssertFrame.Attribute(frame, "another-attribute", "42", 2));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("e => Increment(e)")]
|
||||
[InlineData("(e) => Increment(e)")]
|
||||
[InlineData("@(e => Increment(e))")]
|
||||
[InlineData("@(e => { Increment(e); })")]
|
||||
[InlineData("Increment")]
|
||||
[InlineData("@Increment")]
|
||||
[InlineData("@(Increment)")]
|
||||
public void Render_ChildComponent_WithEventHandler(string expression)
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public Action<MouseEventArgs> OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent($@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<MyComponent OnClick=""{expression}""/>
|
||||
|
||||
@code {{
|
||||
private int counter;
|
||||
private void Increment(MouseEventArgs e) {{
|
||||
counter++;
|
||||
}}
|
||||
}}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "OnClick", 1);
|
||||
|
||||
// The handler will have been assigned to a lambda
|
||||
var handler = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithExplicitEventHandler()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public Action<EventArgs> OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent OnClick=""@Increment""/>
|
||||
|
||||
@code {
|
||||
private int counter;
|
||||
private void Increment(EventArgs e) {
|
||||
counter++;
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "OnClick", 1);
|
||||
|
||||
// The handler will have been assigned to a lambda
|
||||
var handler = Assert.IsType<Action<EventArgs>>(frame.AttributeValue);
|
||||
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
||||
Assert.Equal("Increment", handler.Method.Name);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithMinimizedBoolAttribute()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public bool BoolProperty { get; set; }
|
||||
}
|
||||
}"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent BoolProperty />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_WithChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string MyAttr { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text @(""Hello"")</some-child></MyComponent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert: component frames are correct
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 2));
|
||||
|
||||
// Assert: Captured ChildContent frames are correct
|
||||
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
|
||||
Assert.Collection(
|
||||
childFrames.AsEnumerable(),
|
||||
frame => AssertFrame.Text(frame, "Some text", 3),
|
||||
frame => AssertFrame.Element(frame, "some-child", 4, 4),
|
||||
frame => AssertFrame.Attribute(frame, "a", "1", 5),
|
||||
frame => AssertFrame.Text(frame, "Nested text ", 6),
|
||||
frame => AssertFrame.Text(frame, "Hello", 7));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildComponent_Nested()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MyComponent><MyComponent>Some text</MyComponent></MyComponent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert: outer component frames are correct
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 1));
|
||||
|
||||
// Assert: first level of ChildContent is correct
|
||||
// Note that we don't really need the sequence numbers to continue on from the
|
||||
// sequence numbers at the parent level. All that really matters is that they are
|
||||
// correct relative to each other (i.e., incrementing) within the nesting level.
|
||||
// As an implementation detail, it happens that they do follow on from the parent
|
||||
// level, but we could change that part of the implementation if we wanted.
|
||||
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).AsEnumerable().ToArray();
|
||||
Assert.Collection(
|
||||
innerFrames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 3));
|
||||
|
||||
// Assert: second level of ChildContent is correct
|
||||
Assert.Collection(
|
||||
GetFrames((RenderFragment)innerFrames[1].AttributeValue).AsEnumerable(),
|
||||
frame => AssertFrame.Text(frame, "Some text", 4));
|
||||
}
|
||||
|
||||
[Fact] // https://github.com/aspnet/Blazor/issues/773
|
||||
public void Regression_773()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class SurveyPrompt : ComponentBase
|
||||
{
|
||||
[Parameter] public string Title { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@page ""/""
|
||||
|
||||
<SurveyPrompt Title=""<div>Test!</div>"" />
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.SurveyPrompt", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Title", "<div>Test!</div>", 1));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Regression_784()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<p @onmouseover=""OnComponentHover"" style=""background: @ParentBgColor;"" />
|
||||
@code {
|
||||
public string ParentBgColor { get; set; } = ""#FFFFFF"";
|
||||
|
||||
public void OnComponentHover(MouseEventArgs e)
|
||||
{
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "p", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "onmouseover", 1),
|
||||
frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2));
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6185")]
|
||||
public void Render_Component_HtmlEncoded()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"<span>Hi</span>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Text(frame, "<span>Hi</span>"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_Component_HtmlBlockEncoded()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"<div><span>Hi</span></div>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Markup(frame, "<div><span>Hi</span></div>"));
|
||||
}
|
||||
|
||||
// Integration test for HTML block rewriting
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6183")]
|
||||
public void Render_HtmlBlock_Integration()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<html>
|
||||
<head><meta><meta></head>
|
||||
<body>
|
||||
<MyComponent>
|
||||
<div><span></span><span></span></div>
|
||||
<div>@(""hi"")</div>
|
||||
<div><span></span><span></span></div>
|
||||
<div></div>
|
||||
<div>@(""hi"")</div>
|
||||
<div></div>
|
||||
</MyComponent>
|
||||
</body>
|
||||
</html>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert: component frames are correct
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "html", 9, 0),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 1),
|
||||
frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2),
|
||||
frame => AssertFrame.Element(frame, "body", 5, 3),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 4),
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 6),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 16),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 17));
|
||||
|
||||
// Assert: Captured ChildContent frames are correct
|
||||
var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue);
|
||||
Assert.Collection(
|
||||
childFrames.AsEnumerable(),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 7),
|
||||
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 9),
|
||||
frame => AssertFrame.Text(frame, "hi", 10),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 11),
|
||||
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 13),
|
||||
frame => AssertFrame.Text(frame, "hi", 14),
|
||||
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_CanBeUsedFromComponent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class Repeater : ComponentBase
|
||||
{
|
||||
[Parameter] public int Count { get; set; }
|
||||
[Parameter] public RenderFragment<string> Template { get; set; }
|
||||
[Parameter] public string Value { get; set; }
|
||||
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
builder.AddContent(i, Template, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
|
||||
<Repeater Count=3 Value=""Hello, World!"" Template=""template"" />
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.Repeater", 4, 2),
|
||||
frame => AssertFrame.Attribute(frame, "Count", typeof(int), 3),
|
||||
frame => AssertFrame.Attribute(frame, "Value", typeof(string), 4),
|
||||
frame => AssertFrame.Attribute(frame, "Template", typeof(RenderFragment<string>), 5),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
// Integration tests for Blazor's directives
|
||||
public class DirectiveRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public DirectiveRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComponentsDoNotHaveLayoutAttributeByDefault()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent($"Hello");
|
||||
|
||||
// Assert
|
||||
Assert.Null(component.GetType().GetCustomAttribute<LayoutAttribute>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsLayoutDeclarations()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestLayout>();
|
||||
var component = CompileToComponent(
|
||||
$"@layout {testComponentTypeName}\n" +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var layoutAttribute = component.GetType().GetCustomAttribute<LayoutAttribute>();
|
||||
Assert.NotNull(layoutAttribute);
|
||||
Assert.Equal(typeof(TestLayout), layoutAttribute.LayoutType);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsImplementsDeclarations()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testInterfaceTypeName = FullTypeName<ITestInterface>();
|
||||
var component = CompileToComponent(
|
||||
$"@implements {testInterfaceTypeName}\n" +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.IsAssignableFrom<ITestInterface>(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsMultipleImplementsDeclarations()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testInterfaceTypeName = FullTypeName<ITestInterface>();
|
||||
var testInterfaceTypeName2 = FullTypeName<ITestInterface2>();
|
||||
var component = CompileToComponent(
|
||||
$"@implements {testInterfaceTypeName}\n" +
|
||||
$"@implements {testInterfaceTypeName2}\n" +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.IsAssignableFrom<ITestInterface>(component);
|
||||
Assert.IsAssignableFrom<ITestInterface2>(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsInheritsDirective()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testBaseClassTypeName = FullTypeName<TestBaseClass>();
|
||||
var component = CompileToComponent(
|
||||
$"@inherits {testBaseClassTypeName}" + Environment.NewLine +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.IsAssignableFrom<TestBaseClass>(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsInjectDirective()
|
||||
{
|
||||
// Arrange/Act 1: Compilation
|
||||
var componentType = CompileToComponent(
|
||||
$"@inject {FullTypeName<IMyService1>()} MyService1\n" +
|
||||
$"@inject {FullTypeName<IMyService2>()} MyService2\n" +
|
||||
$"Hello from @MyService1 and @MyService2").GetType();
|
||||
|
||||
// Assert 1: Compiled type has correct properties
|
||||
var propertyFlags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
var injectableProperties = componentType.GetProperties(propertyFlags)
|
||||
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null);
|
||||
Assert.Collection(injectableProperties.OrderBy(p => p.Name),
|
||||
property =>
|
||||
{
|
||||
Assert.Equal("MyService1", property.Name);
|
||||
Assert.Equal(typeof(IMyService1), property.PropertyType);
|
||||
Assert.False(property.GetMethod.IsPublic);
|
||||
Assert.False(property.SetMethod.IsPublic);
|
||||
},
|
||||
property =>
|
||||
{
|
||||
Assert.Equal("MyService2", property.Name);
|
||||
Assert.Equal(typeof(IMyService2), property.PropertyType);
|
||||
Assert.False(property.GetMethod.IsPublic);
|
||||
Assert.False(property.SetMethod.IsPublic);
|
||||
});
|
||||
|
||||
// Arrange/Act 2: DI-supplied component has correct behavior
|
||||
var serviceProvider = new TestServiceProvider();
|
||||
serviceProvider.AddService<IMyService1>(new MyService1Impl());
|
||||
serviceProvider.AddService<IMyService2>(new MyService2Impl());
|
||||
var componentFactory = new ComponentFactory();
|
||||
var component = componentFactory.InstantiateComponent(serviceProvider, componentType);
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert 2: Rendered component behaves correctly
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello from "),
|
||||
frame => AssertFrame.Text(frame, typeof(MyService1Impl).FullName),
|
||||
frame => AssertFrame.Text(frame, " and "),
|
||||
frame => AssertFrame.Text(frame, typeof(MyService2Impl).FullName));
|
||||
}
|
||||
|
||||
public class TestLayout : IComponent
|
||||
{
|
||||
[Parameter]
|
||||
public RenderFragment Body { get; set; }
|
||||
|
||||
public void Attach(RenderHandle renderHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITestInterface { }
|
||||
|
||||
public interface ITestInterface2 { }
|
||||
|
||||
public class TestBaseClass : ComponentBase { }
|
||||
|
||||
public interface IMyService1 { }
|
||||
public interface IMyService2 { }
|
||||
public class MyService1Impl : IMyService1 { }
|
||||
public class MyService2Impl : IMyService2 { }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class GenericComponentRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
private readonly CSharpSyntaxTree GenericContextComponent = Parse(@"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class GenericContext<TItem> : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
var items = (IReadOnlyList<TItem>)Items ?? Array.Empty<TItem>();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (ChildContent == null)
|
||||
{
|
||||
builder.AddContent(i, Items[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public List<TItem> Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<Context> ChildContent { get; set; }
|
||||
|
||||
public class Context
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public TItem Item { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
namespace Test
|
||||
{
|
||||
public class MultipleGenericParameter<TItem1, TItem2, TItem3> : ComponentBase
|
||||
{
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, Item1);
|
||||
builder.AddContent(1, Item2);
|
||||
builder.AddContent(2, Item3);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public TItem1 Item1 { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TItem2 Item2 { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TItem3 Item3 { get; set; }
|
||||
}
|
||||
}
|
||||
");
|
||||
|
||||
public GenericComponentRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_WithoutChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
|
||||
frame => AssertFrame.Text(frame, "1", 0),
|
||||
frame => AssertFrame.Text(frame, "2", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_WithRef()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" />
|
||||
|
||||
@code {
|
||||
GenericContext<int> _my;
|
||||
void Foo() { GC.KeepAlive(_my); }
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
|
||||
frame => AssertFrame.Text(frame, "1", 0),
|
||||
frame => AssertFrame.Text(frame, "2", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_WithChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"">
|
||||
<div>@(context.Item * context.Index)</div>
|
||||
</GenericContext>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
|
||||
frame => AssertFrame.Attribute(frame, "ChildContent", 2),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "0", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "2", 5),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_TypeInference_WithRef()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<GenericContext Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" />
|
||||
|
||||
@code {
|
||||
GenericContext<int> _my;
|
||||
void Foo() { GC.KeepAlive(_my); }
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
|
||||
frame => AssertFrame.Text(frame, "1", 0),
|
||||
frame => AssertFrame.Text(frame, "2", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_TypeInference_WithRef_Recursive()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var assembly = CompileToAssembly("Test.cshtml", @"
|
||||
@typeparam TItem
|
||||
<GenericContext Items=""@MyItems"" @ref=""_my"" />
|
||||
|
||||
@code {
|
||||
[Parameter] public List<TItem> MyItems { get; set; }
|
||||
GenericContext<TItem> _my;
|
||||
void Foo() { GC.KeepAlive(_my); }
|
||||
}");
|
||||
|
||||
var componentType = assembly.Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "Test`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
var component = (IComponent)Activator.CreateInstance(componentType);
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = assembly.Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", 1),
|
||||
frame => AssertFrame.ComponentReferenceCapture(frame, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_TypeInference_WithoutChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(GenericContextComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<GenericContext Items=""@(new List<int>() { 1, 2, })"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "GenericContext`1")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
|
||||
frame => AssertFrame.Text(frame, "1", 0),
|
||||
frame => AssertFrame.Text(frame, "2", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_GenericComponent_MultipleParameters_WithChildContent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
<MultipleGenericParameter
|
||||
TItem1=""int""
|
||||
TItem2=""string""
|
||||
TItem3=long
|
||||
Item1=3
|
||||
Item2=""@(""FOO"")""
|
||||
Item3=39L/>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
var genericComponentType = component.GetType().Assembly.DefinedTypes
|
||||
.Where(t => t.Name == "MultipleGenericParameter`3")
|
||||
.Single()
|
||||
.MakeGenericType(typeof(int), typeof(string), typeof(long));
|
||||
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, genericComponentType.FullName, 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Item1", 3, 1),
|
||||
frame => AssertFrame.Attribute(frame, "Item2", "FOO", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Item3", 39L, 3),
|
||||
frame => AssertFrame.Text(frame, "3", 0),
|
||||
frame => AssertFrame.Text(frame, "FOO", 1),
|
||||
frame => AssertFrame.Text(frame, "39", 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
||||
<!-- Exclude the TestFiles directory from default wildcards -->
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**\*</DefaultItemExcludes>
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Embed test files so they can be referenced in tests -->
|
||||
<EmbeddedResource Include="TestFiles\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GenerateBaselines)'=='true'">
|
||||
<DefineConstants>GENERATE_BASELINES;$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.Build" />
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.Mono" />
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" />
|
||||
<Reference Include="Microsoft.AspNetCore.Razor.Language" />
|
||||
<Reference Include="Microsoft.CodeAnalysis.Razor" />
|
||||
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
|
||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\testassets\StandaloneApp\StandaloneApp.csproj" />
|
||||
<Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
|
||||
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)test\**\*.cs" LinkBase="Helpers" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- A bit of msbuild magic to support reference resolver tests -->
|
||||
<Target Name="CreateReferenceHintPathsList" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<_BclDirectory Include="$(MonoBaseClassLibraryPath)" />
|
||||
<_BclDirectory Include="$(MonoBaseClassLibraryFacadesPath)" />
|
||||
<_BclDirectory Include="$(MonoWasmFrameworkPath)" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile Lines="@(ReferencePath)" File="$(TargetDir)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
|
||||
<WriteLinesToFile Lines="@(_BclDirectory)" File="$(TargetDir)bclLocations.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RazorProjectItem"/> that does not exist.
|
||||
/// </summary>
|
||||
internal class NotFoundProjectItem : RazorProjectItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="NotFoundProjectItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
public NotFoundProjectItem(string basePath, string path)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string BasePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FilePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Exists => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string PhysicalPath => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream Read() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class TestFile
|
||||
{
|
||||
private TestFile(string resourceName, Assembly assembly)
|
||||
{
|
||||
Assembly = assembly;
|
||||
ResourceName = Assembly.GetName().Name + "." + resourceName.Replace('/', '.').Replace('\\', '.');
|
||||
}
|
||||
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
public string ResourceName { get; }
|
||||
|
||||
public static TestFile Create(string resourceName, Type type)
|
||||
{
|
||||
return new TestFile(resourceName, type.GetTypeInfo().Assembly);
|
||||
}
|
||||
|
||||
public static TestFile Create(string resourceName, Assembly assembly)
|
||||
{
|
||||
return new TestFile(resourceName, assembly);
|
||||
}
|
||||
|
||||
public Stream OpenRead()
|
||||
{
|
||||
var stream = Assembly.GetManifestResourceStream(ResourceName);
|
||||
if (stream == null)
|
||||
{
|
||||
Assert.True(false, string.Format("Manifest resource: {0} not found", ResourceName));
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
var resourceNames = Assembly.GetManifestResourceNames();
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
// Resource names are case-sensitive.
|
||||
if (string.Equals(ResourceName, resourceName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ReadAllText()
|
||||
{
|
||||
using (var reader = new StreamReader(OpenRead()))
|
||||
{
|
||||
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
|
||||
// since all the unit tests rely on the assumption that the files will have \r\n endings.
|
||||
return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the specified path.
|
||||
/// </summary>
|
||||
public void Save(string filePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
using (var outStream = File.Create(filePath))
|
||||
{
|
||||
using (var inStream = OpenRead())
|
||||
{
|
||||
inStream.CopyTo(outStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public static class TestProject
|
||||
{
|
||||
public static string GetProjectDirectory(Type type)
|
||||
{
|
||||
var solutionDir = GetSolutionRootDirectory("Components");
|
||||
|
||||
var assemblyName = type.Assembly.GetName().Name;
|
||||
|
||||
var projectDirectory = Path.Combine(solutionDir, "test", assemblyName);
|
||||
if (!Directory.Exists(projectDirectory))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$@"Could not locate project directory for type {type.FullName}.
|
||||
Directory probe path: {projectDirectory}.");
|
||||
}
|
||||
|
||||
return projectDirectory;
|
||||
}
|
||||
|
||||
public static string GetSolutionRootDirectory(string solution)
|
||||
{
|
||||
var applicationBasePath = AppContext.BaseDirectory;
|
||||
var directoryInfo = new DirectoryInfo(applicationBasePath);
|
||||
|
||||
do
|
||||
{
|
||||
var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln"));
|
||||
if (projectFileInfo.Exists)
|
||||
{
|
||||
return projectFileInfo.DirectoryName;
|
||||
}
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
}
|
||||
while (directoryInfo.Parent != null);
|
||||
|
||||
throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class VirtualRazorProjectFileSystem : RazorProjectFileSystem
|
||||
{
|
||||
private readonly DirectoryNode _root = new DirectoryNode("/");
|
||||
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
|
||||
{
|
||||
basePath = NormalizeAndEnsureValidPath(basePath);
|
||||
var directory = _root.GetDirectory(basePath);
|
||||
return directory?.EnumerateItems() ?? Enumerable.Empty<RazorProjectItem>();
|
||||
}
|
||||
|
||||
[Obsolete("Use GetItem(string path, string fileKind)] instead")]
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
return GetItem(path, fileKind: null);
|
||||
}
|
||||
|
||||
public override RazorProjectItem GetItem(string path, string fileKind)
|
||||
{
|
||||
// We ignore fileKind here because the _root is pre-filled with project items that already have fileKinds defined. This is
|
||||
// a unique circumstance where the RazorProjectFileSystem is actually pre-filled with all of its project items on construction.
|
||||
|
||||
path = NormalizeAndEnsureValidPath(path);
|
||||
return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path);
|
||||
}
|
||||
|
||||
public void Add(RazorProjectItem projectItem)
|
||||
{
|
||||
if (projectItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath);
|
||||
_root.AddFile(new FileNode(filePath, projectItem));
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal class DirectoryNode
|
||||
{
|
||||
public DirectoryNode(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public List<DirectoryNode> Directories { get; } = new List<DirectoryNode>();
|
||||
|
||||
public List<FileNode> Files { get; } = new List<FileNode>();
|
||||
|
||||
public void AddFile(FileNode fileNode)
|
||||
{
|
||||
var filePath = fileNode.Path;
|
||||
if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = "Error";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
// Look for the first / that appears in the path after the current directory path.
|
||||
var directoryPath = GetDirectoryPath(filePath);
|
||||
var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true);
|
||||
Debug.Assert(directory != null);
|
||||
directory.Files.Add(fileNode);
|
||||
}
|
||||
|
||||
public DirectoryNode GetDirectory(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = "Error";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return GetOrAddDirectory(this, path);
|
||||
}
|
||||
|
||||
public IEnumerable<RazorProjectItem> EnumerateItems()
|
||||
{
|
||||
foreach (var file in Files)
|
||||
{
|
||||
yield return file.ProjectItem;
|
||||
}
|
||||
|
||||
foreach (var directory in Directories)
|
||||
{
|
||||
foreach (var file in directory.EnumerateItems())
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RazorProjectItem GetItem(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Error");
|
||||
}
|
||||
|
||||
var directoryPath = GetDirectoryPath(path);
|
||||
var directory = GetOrAddDirectory(this, directoryPath);
|
||||
if (directory == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in directory.Files)
|
||||
{
|
||||
var filePath = file.Path;
|
||||
var directoryLength = directory.Path.Length;
|
||||
|
||||
// path, filePath -> /Views/Home/Index.cshtml
|
||||
// directory.Path -> /Views/Home/
|
||||
// We only need to match the file name portion since we've already matched the directory segment.
|
||||
if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return file.ProjectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetDirectoryPath(string path)
|
||||
{
|
||||
// /dir1/dir2/file.cshtml -> /dir1/dir2/
|
||||
var fileNameIndex = path.LastIndexOf('/');
|
||||
if (fileNameIndex == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.Substring(0, fileNameIndex + 1);
|
||||
}
|
||||
|
||||
private static DirectoryNode GetOrAddDirectory(
|
||||
DirectoryNode directory,
|
||||
string path,
|
||||
bool createIfNotExists = false)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||
if (path[path.Length - 1] != '/')
|
||||
{
|
||||
path += '/';
|
||||
}
|
||||
|
||||
int index;
|
||||
while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length)
|
||||
{
|
||||
var subDirectory = FindSubDirectory(directory, path);
|
||||
|
||||
if (subDirectory == null)
|
||||
{
|
||||
if (createIfNotExists)
|
||||
{
|
||||
var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash
|
||||
subDirectory = new DirectoryNode(directoryPath);
|
||||
directory.Directories.Add(subDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
directory = subDirectory;
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path)
|
||||
{
|
||||
for (var i = 0; i < parentDirectory.Directories.Count; i++)
|
||||
{
|
||||
// ParentDirectory.Path -> /Views/Home/
|
||||
// CurrentDirectory.Path -> /Views/Home/SubDir/
|
||||
// Path -> /Views/Home/SubDir/MorePath/File.cshtml
|
||||
// Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file.
|
||||
|
||||
var currentDirectory = parentDirectory.Directories[i];
|
||||
var directoryPath = currentDirectory.Path;
|
||||
var startIndex = parentDirectory.Path.Length;
|
||||
var directoryNameLength = directoryPath.Length - startIndex;
|
||||
|
||||
if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return currentDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal struct FileNode
|
||||
{
|
||||
public FileNode(string path, RazorProjectItem projectItem)
|
||||
{
|
||||
Path = path;
|
||||
ProjectItem = projectItem;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public RazorProjectItem ProjectItem { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class VirtualProjectItem : RazorProjectItem
|
||||
{
|
||||
private readonly byte[] _content;
|
||||
|
||||
public VirtualProjectItem(
|
||||
string basePath,
|
||||
string filePath,
|
||||
string physicalPath,
|
||||
string relativePhysicalPath,
|
||||
string fileKind,
|
||||
byte[] content)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = filePath;
|
||||
PhysicalPath = physicalPath;
|
||||
RelativePhysicalPath = relativePhysicalPath;
|
||||
_content = content;
|
||||
|
||||
// Base class will detect based on file-extension.
|
||||
FileKind = fileKind ?? base.FileKind;
|
||||
}
|
||||
|
||||
public override string BasePath { get; }
|
||||
|
||||
public override string RelativePhysicalPath { get; }
|
||||
|
||||
public override string FileKind { get; }
|
||||
|
||||
public override string FilePath { get; }
|
||||
|
||||
public override string PhysicalPath { get; }
|
||||
|
||||
public override bool Exists => true;
|
||||
|
||||
public override Stream Read()
|
||||
{
|
||||
return new MemoryStream(_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,542 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class RazorIntegrationTestBase
|
||||
{
|
||||
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
|
||||
|
||||
internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test";
|
||||
internal const string ArbitraryMacLinuxPath = "/dir/subdir/Test";
|
||||
|
||||
// Creating the initial compilation + reading references is on the order of 250ms without caching
|
||||
// so making sure it doesn't happen for each test.
|
||||
private static readonly CSharpCompilation BaseCompilation;
|
||||
|
||||
private static CSharpParseOptions CSharpParseOptions { get; }
|
||||
|
||||
static RazorIntegrationTestBase()
|
||||
{
|
||||
var referenceAssemblyRoots = new[]
|
||||
{
|
||||
typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime
|
||||
typeof(ComponentBase).Assembly,
|
||||
typeof(RazorIntegrationTestBase).Assembly, // Reference this assembly, so that we can refer to test component types
|
||||
};
|
||||
|
||||
var referenceAssemblies = referenceAssemblyRoots
|
||||
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() }))
|
||||
.Distinct()
|
||||
.Select(Assembly.Load)
|
||||
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
|
||||
.ToList();
|
||||
BaseCompilation = CSharpCompilation.Create(
|
||||
"TestAssembly",
|
||||
Array.Empty<SyntaxTree>(),
|
||||
referenceAssemblies,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.Preview);
|
||||
}
|
||||
|
||||
public RazorIntegrationTestBase(ITestOutputHelper output)
|
||||
{
|
||||
_output.Value = output;
|
||||
|
||||
AdditionalSyntaxTrees = new List<SyntaxTree>();
|
||||
AdditionalRazorItems = new List<RazorProjectItem>();
|
||||
|
||||
Configuration = RazorConfiguration.Create(RazorLanguageVersion.Latest, "MVC-3.0", Array.Empty<RazorExtension>());
|
||||
FileKind = FileKinds.Component; // Treat input files as components by default.
|
||||
FileSystem = new VirtualRazorProjectFileSystem();
|
||||
PathSeparator = Path.DirectorySeparatorChar.ToString();
|
||||
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
|
||||
|
||||
// Many of the rendering tests include line endings in the output.
|
||||
LineEnding = "\n";
|
||||
NormalizeSourceLineEndings = true;
|
||||
|
||||
DefaultRootNamespace = "Test"; // Matches the default working directory
|
||||
DefaultFileName = "TestComponent.cshtml";
|
||||
}
|
||||
|
||||
internal List<RazorProjectItem> AdditionalRazorItems { get; }
|
||||
|
||||
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
|
||||
|
||||
internal virtual RazorConfiguration Configuration { get; }
|
||||
|
||||
internal virtual string DefaultRootNamespace { get; }
|
||||
|
||||
internal virtual string DefaultFileName { get; }
|
||||
|
||||
internal virtual bool DesignTime { get; }
|
||||
|
||||
internal virtual string FileKind { get; }
|
||||
|
||||
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
|
||||
|
||||
// Used to force a specific style of line-endings for testing. This matters
|
||||
// for the baseline tests that exercise line mappings. Even though we normalize
|
||||
// newlines for testing, the difference between platforms affects the data through
|
||||
// the *count* of characters written.
|
||||
internal virtual string LineEnding { get; }
|
||||
|
||||
internal virtual string PathSeparator { get; }
|
||||
|
||||
internal virtual bool NormalizeSourceLineEndings { get; }
|
||||
|
||||
internal virtual bool UseTwoPhaseCompilation { get; }
|
||||
|
||||
internal virtual string WorkingDirectory { get; }
|
||||
|
||||
// Intentionally private, we don't want tests messing with this because it's fragile.
|
||||
private RazorProjectEngine CreateProjectEngine(MetadataReference[] references)
|
||||
{
|
||||
return RazorProjectEngine.Create(Configuration, FileSystem, b =>
|
||||
{
|
||||
b.SetRootNamespace(DefaultRootNamespace);
|
||||
|
||||
// Turn off checksums, we're testing code generation.
|
||||
b.Features.Add(new SuppressChecksum());
|
||||
|
||||
if (LineEnding != null)
|
||||
{
|
||||
b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding));
|
||||
}
|
||||
|
||||
// Including MVC here so that we can find any issues that arise from mixed MVC + Components.
|
||||
Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions.Register(b);
|
||||
|
||||
// Features that use Roslyn are mandatory for components
|
||||
Microsoft.CodeAnalysis.Razor.CompilerFeatures.Register(b);
|
||||
|
||||
b.Features.Add(new CompilationTagHelperFeature());
|
||||
b.Features.Add(new DefaultMetadataReferenceFeature()
|
||||
{
|
||||
References = references,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
var fullPath = WorkingDirectory + PathSeparator + cshtmlRelativePath;
|
||||
|
||||
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
|
||||
var filePath = cshtmlRelativePath.Replace('\\', '/');
|
||||
if (!filePath.StartsWith('/'))
|
||||
{
|
||||
filePath = '/' + filePath;
|
||||
}
|
||||
|
||||
if (NormalizeSourceLineEndings)
|
||||
{
|
||||
cshtmlContent = cshtmlContent.Replace("\r", "").Replace("\n", LineEnding);
|
||||
}
|
||||
|
||||
return new VirtualProjectItem(
|
||||
WorkingDirectory,
|
||||
filePath,
|
||||
fullPath,
|
||||
cshtmlRelativePath,
|
||||
FileKind,
|
||||
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
|
||||
{
|
||||
return CompileToCSharp(DefaultFileName, cshtmlContent);
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
if (UseTwoPhaseCompilation)
|
||||
{
|
||||
// The first phase won't include any metadata references for component discovery. This mirrors
|
||||
// what the build does.
|
||||
var projectEngine = CreateProjectEngine(Array.Empty<MetadataReference>());
|
||||
|
||||
RazorCodeDocument codeDocument;
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
// Result of generating declarations
|
||||
codeDocument = projectEngine.ProcessDeclarationOnly(item);
|
||||
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||
|
||||
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||
}
|
||||
|
||||
// Result of generating declarations
|
||||
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||
codeDocument = projectEngine.ProcessDeclarationOnly(projectItem);
|
||||
var declaration = new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
|
||||
// Result of doing 'temp' compilation
|
||||
var tempAssembly = CompileToAssembly(declaration);
|
||||
|
||||
// Add the 'temp' compilation as a metadata reference
|
||||
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
|
||||
projectEngine = CreateProjectEngine(references);
|
||||
|
||||
// Now update the any additional files
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
// Result of generating declarations
|
||||
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(item) : projectEngine.Process(item);
|
||||
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||
|
||||
// Replace the 'declaration' syntax tree
|
||||
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||
AdditionalSyntaxTrees.RemoveAll(st => st.FilePath == item.FilePath);
|
||||
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||
}
|
||||
|
||||
// Result of real code generation for the document under test
|
||||
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||
|
||||
_output.Value.WriteLine("Use this output when opening an issue");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(projectItem));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
foreach (var item in AdditionalRazorItems)
|
||||
{
|
||||
_output.Value.WriteLine($"### Additional source file ({item.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(item));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
_output.Value.WriteLine("## Generated C#:");
|
||||
_output.Value.WriteLine("```C#");
|
||||
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
|
||||
_output.Value.WriteLine("```");
|
||||
|
||||
return new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// For single phase compilation tests just use the base compilation's references.
|
||||
// This will include the built-in Blazor components.
|
||||
var projectEngine = CreateProjectEngine(BaseCompilation.References.ToArray());
|
||||
|
||||
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||
var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||
|
||||
// Log the generated code for test results.
|
||||
_output.Value.WriteLine("Use this output when opening an issue");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(ReadProjectItem(projectItem));
|
||||
_output.Value.WriteLine("```");
|
||||
_output.Value.WriteLine(string.Empty);
|
||||
|
||||
_output.Value.WriteLine("## Generated C#:");
|
||||
_output.Value.WriteLine("```C#");
|
||||
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
|
||||
_output.Value.WriteLine("```");
|
||||
|
||||
return new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent);
|
||||
return CompileToAssembly(cSharpResult);
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, bool throwOnFailure = true)
|
||||
{
|
||||
if (cSharpResult.Diagnostics.Any())
|
||||
{
|
||||
var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray());
|
||||
throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}");
|
||||
}
|
||||
|
||||
var syntaxTrees = new[]
|
||||
{
|
||||
Parse(cSharpResult.Code),
|
||||
};
|
||||
|
||||
var compilation = cSharpResult.BaseCompilation.AddSyntaxTrees(syntaxTrees);
|
||||
|
||||
var diagnostics = compilation
|
||||
.GetDiagnostics()
|
||||
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
|
||||
|
||||
if (diagnostics.Any() && throwOnFailure)
|
||||
{
|
||||
throw new CompilationFailedException(compilation);
|
||||
}
|
||||
else if (diagnostics.Any())
|
||||
{
|
||||
return new CompileToAssemblyResult
|
||||
{
|
||||
Compilation = compilation,
|
||||
Diagnostics = diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
using (var peStream = new MemoryStream())
|
||||
{
|
||||
compilation.Emit(peStream);
|
||||
|
||||
return new CompileToAssemblyResult
|
||||
{
|
||||
Compilation = compilation,
|
||||
Diagnostics = diagnostics,
|
||||
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(string cshtmlSource)
|
||||
{
|
||||
var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource);
|
||||
|
||||
var componentFullTypeName = $"{DefaultRootNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
|
||||
return CompileToComponent(assemblyResult, componentFullTypeName);
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(CompileToCSharpResult cSharpResult, string fullTypeName)
|
||||
{
|
||||
return CompileToComponent(CompileToAssembly(cSharpResult), fullTypeName);
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
|
||||
{
|
||||
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
|
||||
if (componentType == null)
|
||||
{
|
||||
throw new XunitException(
|
||||
$"Failed to find component type '{fullTypeName}'. Found types:" + Environment.NewLine +
|
||||
string.Join(Environment.NewLine, assemblyResult.Assembly.ExportedTypes.Select(t => t.FullName)));
|
||||
}
|
||||
|
||||
return (IComponent)Activator.CreateInstance(componentType);
|
||||
}
|
||||
|
||||
protected static CSharpSyntaxTree Parse(string text, string path = null)
|
||||
{
|
||||
return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions, path: path);
|
||||
}
|
||||
|
||||
protected static string FullTypeName<T>() => typeof(T).FullName.Replace('+', '.');
|
||||
|
||||
protected RenderTreeFrame[] GetRenderTree(IComponent component)
|
||||
{
|
||||
var renderer = new TestRenderer();
|
||||
return GetRenderTree(renderer, component);
|
||||
}
|
||||
|
||||
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
|
||||
{
|
||||
renderer.AttachComponent(component);
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterView.Empty));
|
||||
// we will have to change this method if we add a test that does actual async work.
|
||||
Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted));
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
|
||||
}
|
||||
return renderer.LatestBatchReferenceFrames;
|
||||
}
|
||||
|
||||
protected ArrayRange<RenderTreeFrame> GetFrames(RenderFragment fragment)
|
||||
{
|
||||
var builder = new RenderTreeBuilder();
|
||||
fragment(builder);
|
||||
return builder.GetFrames();
|
||||
}
|
||||
|
||||
protected static void AssertSourceEquals(string expected, CompileToCSharpResult generated)
|
||||
{
|
||||
// Normalize the paths inside the expected result to match the OS paths
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var windowsPath = Path.Combine(ArbitraryWindowsPath, generated.CodeDocument.Source.RelativePath).Replace('/', '\\');
|
||||
expected = expected.Replace(windowsPath, generated.CodeDocument.Source.FilePath);
|
||||
}
|
||||
|
||||
expected = expected.Trim();
|
||||
Assert.Equal(expected, generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
private static string ReadProjectItem(RazorProjectItem item)
|
||||
{
|
||||
using (var reader = new StreamReader(item.Read()))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
protected class CompileToCSharpResult
|
||||
{
|
||||
// A compilation that can be used *with* this code to compile an assembly
|
||||
public Compilation BaseCompilation { get; set; }
|
||||
public RazorCodeDocument CodeDocument { get; set; }
|
||||
public string Code { get; set; }
|
||||
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
protected class CompileToAssemblyResult
|
||||
{
|
||||
public Assembly Assembly { get; set; }
|
||||
public Compilation Compilation { get; set; }
|
||||
public string VerboseLog { get; set; }
|
||||
public IEnumerable<Diagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
protected class TestRenderer : Renderer
|
||||
{
|
||||
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
|
||||
|
||||
public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; }
|
||||
|
||||
public void AttachComponent(IComponent component)
|
||||
=> AssignRootComponentId(component);
|
||||
|
||||
protected override void HandleException(Exception exception)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
}
|
||||
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
{
|
||||
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.AsEnumerable().ToArray();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationFailedException : XunitException
|
||||
{
|
||||
public CompilationFailedException(Compilation compilation)
|
||||
{
|
||||
Compilation = compilation;
|
||||
}
|
||||
|
||||
public Compilation Compilation { get; }
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("Compilation failed: ");
|
||||
|
||||
var diagnostics = Compilation.GetDiagnostics();
|
||||
var syntaxTreesWithErrors = new HashSet<SyntaxTree>();
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
builder.AppendLine(diagnostic.ToString());
|
||||
|
||||
if (diagnostic.Location.IsInSource)
|
||||
{
|
||||
syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree);
|
||||
}
|
||||
}
|
||||
|
||||
if (syntaxTreesWithErrors.Any())
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
|
||||
foreach (var syntaxTree in syntaxTreesWithErrors)
|
||||
{
|
||||
builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:");
|
||||
builder.AppendLine(syntaxTree.GetText().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SuppressChecksum : IConfigureRazorCodeGenerationOptionsFeature
|
||||
{
|
||||
public int Order => 0;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Configure(RazorCodeGenerationOptionsBuilder options)
|
||||
{
|
||||
options.SuppressChecksum = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ForceLineEndingPhase : RazorEnginePhaseBase
|
||||
{
|
||||
public ForceLineEndingPhase(string lineEnding)
|
||||
{
|
||||
LineEnding = lineEnding;
|
||||
}
|
||||
|
||||
public string LineEnding { get; }
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var field = typeof(CodeRenderingContext).GetField("NewLineString", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var key = field.GetValue(null);
|
||||
codeDocument.Items[key] = LineEnding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,744 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
// Integration tests for the end-to-end of successful Razor compilation of component definitions
|
||||
// Includes running the component code to verify the output.
|
||||
public class RenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
public RenderingRazorIntegrationTest(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsPlainText()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some plain text");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Some plain text", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsCSharpExpressions()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@(""Hello"")
|
||||
@((object)null)
|
||||
@(123)
|
||||
@(new object())
|
||||
");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "Hello", 0),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 1),
|
||||
frame => AssertFrame.TextWhitespace(frame, 2), // @((object)null)
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 3),
|
||||
frame => AssertFrame.Text(frame, "123", 4),
|
||||
frame => AssertFrame.MarkupWhitespace(frame, 5),
|
||||
frame => AssertFrame.Text(frame, new object().ToString(), 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsCSharpFunctionsBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@foreach(var item in items) {
|
||||
@item
|
||||
}
|
||||
@code {
|
||||
string[] items = new[] { ""First"", ""Second"", ""Third"" };
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "First", 0),
|
||||
frame => AssertFrame.Text(frame, "Second", 0),
|
||||
frame => AssertFrame.Text(frame, "Third", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsElementsWithDynamicContent()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<myelem>Hello @(\"there\")</myelem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "myelem", 3, 0),
|
||||
frame => AssertFrame.Text(frame, "Hello ", 1),
|
||||
frame => AssertFrame.Text(frame, "there", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsElementsAsStaticBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<myelem>Hello</myelem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "<myelem>Hello</myelem>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatesSeparateMarkupFrameForEachTopLevelStaticElement()
|
||||
{
|
||||
// The JavaScript-side rendering code does not rely on this behavior. It supports
|
||||
// inserting markup frames with arbitrary markup (e.g., multiple top-level elements
|
||||
// or none). This test exists only as an observation of the current behavior rather
|
||||
// than a promise that we never want to change it.
|
||||
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"<root>@(\"Hi\") <child1>a</child1> <child2><another>b</another></child2> </root>");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "root", 5, 0),
|
||||
frame => AssertFrame.Text(frame, "Hi", 1),
|
||||
frame => AssertFrame.Text(frame, " ", 2),
|
||||
frame => AssertFrame.Markup(frame, "<child1>a</child1> ", 3),
|
||||
frame => AssertFrame.Markup(frame, "<child2><another>b</another></child2> ", 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendersMarkupStringAsMarkupFrame()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var someMarkup = new MarkupString(\"<div>Hello</div>\"); }"
|
||||
+ "<p>@someMarkup</p>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "p", 2, 0),
|
||||
frame => AssertFrame.Markup(frame, "<div>Hello</div>", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsSelfClosingElementsWithDynamicContent()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <myelem myattr=@(\"val\") />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0),
|
||||
frame => AssertFrame.Element(frame, "myelem", 2, 1),
|
||||
frame => AssertFrame.Attribute(frame, "myattr", "val", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsSelfClosingElementsAsStaticBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <input attr='123' />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <input attr=\"123\">", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsVoidHtmlElements()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some text so elem isn't at position 0 <img>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <img>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsComments()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Start<!-- My comment -->End");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Markup(frame, "StartEnd", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithLiteralValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("<elem attrib-one=\"Value 1\" a2='v2'>@(\"Hello\")</elem>");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 4, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attrib-one", "Value 1", 1),
|
||||
frame => AssertFrame.Attribute(frame, "a2", "v2", 2),
|
||||
frame => AssertFrame.Text(frame, "Hello", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"My string\"; }"
|
||||
+ "<elem attr=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "My string", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithNonStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = 123; }"
|
||||
+ "<elem attr=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "123", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithInterpolatedStringExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"world\"; var myNum=123; }"
|
||||
+ "<elem attr=\"Hello, @myValue.ToUpperInvariant() with number @(myNum*2)!\" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithInterpolatedTernaryExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"world\"; }"
|
||||
+ "<elem attr=\"Hello, @(true ? myValue : \"nothing\")!\" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "attr", "Hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsHyphenedAttributesWithCSharpExpressionValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
"@{ var myValue = \"My string\"; }"
|
||||
+ "<elem abc-def=@myValue />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, "abc-def", "My string", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsDataDashAttributes()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@{
|
||||
var myValue = ""Expression value"";
|
||||
}
|
||||
<elem data-abc=""Literal value"" data-def=""@myValue"" />");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "data-abc", "Literal value", 1),
|
||||
frame => AssertFrame.Attribute(frame, "data-def", "Expression value", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsUsingStatements()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
@"@using System.Collections.Generic
|
||||
@(typeof(List<string>).FullName)");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, typeof(List<string>).FullName, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForTextboxes()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {
|
||||
public string MyValue { get; set; } = ""Initial value"";
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForTextareas()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<textarea @bind=""MyValue"" ></textarea>
|
||||
@code {
|
||||
public string MyValue { get; set; } = ""Initial value"";
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForDateValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyDate"" />
|
||||
@code {
|
||||
public DateTime MyDate { get; set; } = new DateTime(2018, 3, 4, 1, 2, 3);
|
||||
}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = newDateValue.ToString(), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(newDateValue, myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testDateFormat = "ddd yyyy-MM-dd";
|
||||
var component = CompileToComponent($@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""@MyDate"" @bind:format=""{testDateFormat}"" />
|
||||
@code {{
|
||||
public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4);
|
||||
}}");
|
||||
var myDateProperty = component.GetType().GetProperty("MyDate");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact] // In this case, onclick is just a normal HTML attribute
|
||||
public void SupportsEventHandlerWithString()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
<button onclick=""function(){console.log('hello');};"" />");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Markup(frame, "<button onclick=\"function(){console.log('hello');};\"></button>", 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsEventHandlerWithLambda()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<button @onclick=""x => Clicked = true"" />
|
||||
@code {
|
||||
public bool Clicked { get; set; }
|
||||
}");
|
||||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "button", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onclick", 1);
|
||||
|
||||
var func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.False((bool)clicked.GetValue(component));
|
||||
|
||||
func(new MouseEventArgs());
|
||||
Assert.True((bool)clicked.GetValue(component));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsEventHandlerWithMethodGroup()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<button @onclick=""OnClick"" />
|
||||
@code {
|
||||
public void OnClick(MouseEventArgs e) { Clicked = true; }
|
||||
public bool Clicked { get; set; }
|
||||
}");
|
||||
|
||||
var clicked = component.GetType().GetProperty("Clicked");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
|
||||
// Assert
|
||||
Action<MouseEventArgs> func = default; // Since this is a method group, we don't need to create an EventCallback
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "button", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onclick", 1);
|
||||
|
||||
func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.False((bool)clicked.GetValue(component));
|
||||
|
||||
|
||||
});
|
||||
|
||||
func.Invoke(new MouseEventArgs());
|
||||
Assert.True((bool)clicked.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForBoolValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {
|
||||
public bool MyValue { get; set; } = true;
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", true, 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs() { Value = false, }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.False((bool)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsTwoWayBindingForEnumValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var myEnumType = FullTypeName<MyEnum>();
|
||||
var component = CompileToComponent($@"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
<input @bind=""MyValue"" />
|
||||
@code {{
|
||||
public {myEnumType} MyValue {{ get; set; }} = {myEnumType}.{nameof(MyEnum.FirstValue)};
|
||||
}}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Assert
|
||||
EventCallback setter = default;
|
||||
var frames = GetRenderTree(renderer, component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "input", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
setter = Assert.IsType<EventCallback>(frame.AttributeValue);
|
||||
});
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
//
|
||||
// This should always complete synchronously.
|
||||
var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = MyEnum.SecondValue.ToString(), }));
|
||||
Assert.Equal(TaskStatus.RanToCompletion, task.Status);
|
||||
await task;
|
||||
|
||||
Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component));
|
||||
}
|
||||
|
||||
public enum MyEnum { FirstValue, SecondValue }
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_NonGeneric_CanBeUsedFromRazorCode()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment template = @<div>@(""Hello, World!"".ToLower())</div>; }
|
||||
@for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@template;
|
||||
}
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_Generic_CanBeUsedFromRazorCode()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
|
||||
@for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@template(""Hello, World!"");
|
||||
}
|
||||
");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 0),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_NonGeneric_CanBeUsedFromMethod()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@(Repeat(@<div>@(""Hello, World!"".ToLower())</div>, 3))
|
||||
|
||||
@code {
|
||||
RenderFragment Repeat(RenderFragment template, int count)
|
||||
{
|
||||
return (b) =>
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
b.AddContent(i, template);
|
||||
}
|
||||
};
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
|
||||
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
|
||||
// from the same logical sequence as the surrounding code.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorTemplate_Generic_CanBeUsedFromMethod()
|
||||
{
|
||||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@(Repeat((context) => @<div>@context.ToLower()</div>, ""Hello, World!"", 3))
|
||||
|
||||
@code {
|
||||
RenderFragment Repeat<T>(RenderFragment<T> template, T value, int count)
|
||||
{
|
||||
return (b) =>
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
b.AddContent(i, template, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call
|
||||
// that precedes the definition of the lambda. Sequence numbers for the lambda are allocated
|
||||
// from the same logical sequence as the surrounding code.
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 1),
|
||||
frame => AssertFrame.Text(frame, "hello, world!", 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Override prerelease label and use preview 4, even in the final build -->
|
||||
<PreReleaseVersionLabel>$(BlazorClientPreReleaseVersionLabel)</PreReleaseVersionLabel>
|
||||
<DotNetFinalVersionKind></DotNetFinalVersionKind>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for working with JSON APIs.
|
||||
/// </summary>
|
||||
public static class HttpClientJsonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a GET request to the specified URI, and parses the JSON response body
|
||||
/// to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
|
||||
{
|
||||
var stringContent = await httpClient.GetStringAsync(requestUri);
|
||||
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task PostJsonAsync(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync(HttpMethod.Post, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task<T> PostJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<T>(HttpMethod.Post, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
public static Task PutJsonAsync(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync(HttpMethod.Put, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static Task<T> PutJsonAsync<T>(this HttpClient httpClient, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<T>(HttpMethod.Put, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
|
||||
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
|
||||
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="requestUri">The URI that the request will be sent to.</param>
|
||||
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
|
||||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
|
||||
{
|
||||
var requestJson = JsonSerializer.Serialize(content, JsonSerializerOptionsProvider.Options);
|
||||
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
|
||||
{
|
||||
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
|
||||
});
|
||||
|
||||
// Make sure the call was successful before we
|
||||
// attempt to process the response content
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (typeof(T) == typeof(IgnoreResponse))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
var stringContent = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoreResponse { }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Test
|
||||
{
|
||||
public class HttpClientJsonExtensionsTest
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
const string TestUri = "http://example.com/some/uri";
|
||||
|
||||
[Fact]
|
||||
public async Task GetJson_Success()
|
||||
{
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
return Task.FromResult(CreateJsonResponse(HttpStatusCode.OK, new Person
|
||||
{
|
||||
Name = "Abc",
|
||||
Age = 123
|
||||
}));
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Abc", result.Name);
|
||||
Assert.Equal(123, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJson_Failure()
|
||||
{
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||
}));
|
||||
|
||||
// Act/Assert
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(
|
||||
() => httpClient.GetJsonAsync<Person>(TestUri));
|
||||
Assert.Contains("404 (Not Found)", ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("Post")]
|
||||
[InlineData("Patch")]
|
||||
[InlineData("Delete")]
|
||||
[InlineData("MyArtificialMethod")]
|
||||
public async Task SendJson_Success(string httpMethodString)
|
||||
{
|
||||
var httpMethod = new HttpMethod(httpMethodString);
|
||||
var requestContent = new { MyProp = true, OtherProp = "Hello" };
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
|
||||
{
|
||||
Assert.Equal(httpMethod, req.Method);
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
|
||||
return CreateJsonResponse(HttpStatusCode.OK, new Person
|
||||
{
|
||||
Name = "Abc",
|
||||
Age = 123
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await Send(httpClient, httpMethodString, requestContent);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Abc", result.Name);
|
||||
Assert.Equal(123, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsJsonAsync_ReadsCamelCasedJson()
|
||||
{
|
||||
var input = "{\"name\": \"TestPerson\", \"age\": 23 }";
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(input)
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestPerson", result.Name);
|
||||
Assert.Equal(23, result.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsJsonAsync_ReadsPascalCasedJson()
|
||||
{
|
||||
var input = "{\"Name\": \"TestPerson\", \"Age\": 23 }";
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(req =>
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(input)
|
||||
});
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await httpClient.GetJsonAsync<Person>(TestUri);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestPerson", result.Name);
|
||||
Assert.Equal(23, result.Age);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("Post")]
|
||||
[InlineData("Patch")]
|
||||
[InlineData("Delete")]
|
||||
[InlineData("MyArtificialMethod")]
|
||||
public async Task SendJson_Failure(string httpMethodString)
|
||||
{
|
||||
var httpMethod = new HttpMethod(httpMethodString);
|
||||
var requestContent = new { MyProp = true, OtherProp = "Hello" };
|
||||
|
||||
// Arrange
|
||||
var httpClient = new HttpClient(new TestHttpMessageHandler(async req =>
|
||||
{
|
||||
Assert.Equal(httpMethod, req.Method);
|
||||
Assert.Equal(TestUri, req.RequestUri.AbsoluteUri);
|
||||
Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync());
|
||||
return new HttpResponseMessage(HttpStatusCode.BadGateway);
|
||||
}));
|
||||
|
||||
// Act/Assert
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(
|
||||
() => Send(httpClient, httpMethodString, requestContent));
|
||||
Assert.Contains("502 (Bad Gateway)", ex.Message);
|
||||
}
|
||||
|
||||
HttpResponseMessage CreateJsonResponse(HttpStatusCode statusCode, object content)
|
||||
{
|
||||
return new HttpResponseMessage(statusCode)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(content, _jsonSerializerOptions))
|
||||
};
|
||||
}
|
||||
|
||||
Task<Person> Send(HttpClient httpClient, string httpMethodString, object requestContent)
|
||||
{
|
||||
// For methods with convenience overloads, show those overloads work
|
||||
switch (httpMethodString)
|
||||
{
|
||||
case "post":
|
||||
return httpClient.PostJsonAsync<Person>(TestUri, requestContent);
|
||||
case "put":
|
||||
return httpClient.PutJsonAsync<Person>(TestUri, requestContent);
|
||||
default:
|
||||
return httpClient.SendJsonAsync<Person>(new HttpMethod(httpMethodString), TestUri, requestContent);
|
||||
}
|
||||
}
|
||||
|
||||
class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
class TestHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>> _sendDelegate;
|
||||
|
||||
public TestHttpMessageHandler(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendDelegate)
|
||||
{
|
||||
_sendDelegate = sendDelegate;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
=> base.Dispose(disposing);
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
=> _sendDelegate(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.Blazor.Server.AutoRebuild;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
internal static class AutoRebuildExtensions
|
||||
{
|
||||
// Note that we don't need to watch typical static-file extensions (.css, .js, etc.)
|
||||
// because anything in wwwroot is just served directly from disk on each reload.
|
||||
// TODO: Make the set of extensions and exclusions configurable in csproj
|
||||
private static string[] _includedSuffixes = new[] { ".cs", ".cshtml" };
|
||||
private static string[] _excludedDirectories = new[] { "obj", "bin" };
|
||||
|
||||
// To ensure the FileSystemWatchers aren't collected, reference them
|
||||
// in this static list. They never need to be removed because there's no
|
||||
// way to remove middleware once it's registered.
|
||||
private static List<object> _uncollectableWatchers = new List<object>();
|
||||
|
||||
public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath)
|
||||
{
|
||||
var isFirstFileWrite = true;
|
||||
WatchFileSystem(config, () =>
|
||||
{
|
||||
if (isFirstFileWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Touch any .cs file to force the host project to rebuild
|
||||
// (which in turn rebuilds the client, since it's referenced)
|
||||
var fileToTouch = Directory.EnumerateFiles(
|
||||
hostAppContentRootPath,
|
||||
"*.cs",
|
||||
SearchOption.AllDirectories).FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(fileToTouch))
|
||||
{
|
||||
File.SetLastWriteTime(fileToTouch, DateTime.Now);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If we don't have permission to write these files, autorebuild will not be enabled
|
||||
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions));
|
||||
logger?.LogWarning(ex,
|
||||
"Cannot autorebuild because there was an error when writing to a file in '{0}'.",
|
||||
hostAppContentRootPath);
|
||||
}
|
||||
|
||||
isFirstFileWrite = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config)
|
||||
{
|
||||
// Currently this only supports VS for Windows. Later on we can add
|
||||
// an IRebuildService implementation for VS for Mac, etc.
|
||||
if (!VSForWindowsRebuildService.TryCreate(out var rebuildService))
|
||||
{
|
||||
return; // You're not on Windows, or you didn't launch this process from VS
|
||||
}
|
||||
|
||||
// Assume we're up to date when the app starts.
|
||||
var buildToken = new RebuildToken(new DateTime(1970, 1, 1)) { BuildTask = Task.CompletedTask, };
|
||||
|
||||
WatchFileSystem(config, () =>
|
||||
{
|
||||
// Don't start the recompilation immediately. We only start it when the next
|
||||
// HTTP request arrives, because it's annoying if the IDE is constantly rebuilding
|
||||
// when you're making changes to multiple files and aren't ready to reload
|
||||
// in the browser yet.
|
||||
//
|
||||
// Replacing the token means that new requests that come in will trigger a rebuild,
|
||||
// and will all 'join' that build until a new file change occurs.
|
||||
buildToken = new RebuildToken(DateTime.Now);
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = buildToken;
|
||||
if (token.BuildTask == null)
|
||||
{
|
||||
// The build is out of date, but a new build is not yet started.
|
||||
//
|
||||
// We can count on VS to only allow one build at a time, this is a safe race
|
||||
// because if we request a second concurrent build, it will 'join' the current one.
|
||||
var task = rebuildService.PerformRebuildAsync(
|
||||
config.SourceMSBuildPath,
|
||||
token.LastChange);
|
||||
token.BuildTask = task;
|
||||
}
|
||||
|
||||
// In the general case it's safe to await this task, it will be a completed task
|
||||
// if everything is up to date.
|
||||
await token.BuildTask;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If there's no listener on the other end of the pipe, or if anything
|
||||
// else goes wrong, we just let the incoming request continue.
|
||||
// There's nowhere useful to log this information so if people report
|
||||
// problems we'll just have to get a repro and debug it.
|
||||
// If it was an error on the VS side, it logs to the output window.
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
private static void WatchFileSystem(BlazorConfig config, Action onWrite)
|
||||
{
|
||||
var clientAppRootDir = Path.GetDirectoryName(config.SourceMSBuildPath);
|
||||
var excludePathPrefixes = _excludedDirectories.Select(subdir
|
||||
=> Path.Combine(clientAppRootDir, subdir) + Path.DirectorySeparatorChar);
|
||||
|
||||
var fsw = new FileSystemWatcher(clientAppRootDir);
|
||||
fsw.Created += OnEvent;
|
||||
fsw.Changed += OnEvent;
|
||||
fsw.Deleted += OnEvent;
|
||||
fsw.Renamed += OnEvent;
|
||||
fsw.IncludeSubdirectories = true;
|
||||
fsw.EnableRaisingEvents = true;
|
||||
|
||||
// Ensure the watcher is not GCed for as long as the app lives
|
||||
lock (_uncollectableWatchers)
|
||||
{
|
||||
_uncollectableWatchers.Add(fsw);
|
||||
}
|
||||
|
||||
void OnEvent(object sender, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
if (!File.Exists(eventArgs.FullPath))
|
||||
{
|
||||
// It's probably a directory rather than a file
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_includedSuffixes.Any(ext => eventArgs.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Not a candidate file type
|
||||
return;
|
||||
}
|
||||
|
||||
if (excludePathPrefixes.Any(prefix => eventArgs.FullPath.StartsWith(prefix, StringComparison.Ordinal)))
|
||||
{
|
||||
// In an excluded subdirectory
|
||||
return;
|
||||
}
|
||||
|
||||
onWrite();
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a three-state value for the state of the build
|
||||
//
|
||||
// BuildTask == null means the build is out of date, but no build has started
|
||||
// BuildTask.IsCompleted == false means the build has been started, but has not completed
|
||||
// BuildTask.IsCompleted == true means the build has completed
|
||||
private class RebuildToken
|
||||
{
|
||||
public RebuildToken(DateTime lastChange)
|
||||
{
|
||||
LastChange = lastChange;
|
||||
}
|
||||
|
||||
public DateTime LastChange { get; }
|
||||
|
||||
public Task BuildTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mechanism for rebuilding a .NET project. For example, it
|
||||
/// could be a way of signalling to a VS process to perform a build.
|
||||
/// </summary>
|
||||
internal interface IRebuildService
|
||||
{
|
||||
Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
internal static class ProcessUtils
|
||||
{
|
||||
// Based on https://stackoverflow.com/a/3346055
|
||||
|
||||
public static Process GetParent(Process process)
|
||||
{
|
||||
var result = new ProcessBasicInformation();
|
||||
var handle = process.Handle;
|
||||
var status = NtQueryInformationProcess(handle, 0, ref result, Marshal.SizeOf(result), out var returnLength);
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Win32Exception(status);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parentProcessId = result.InheritedFromUniqueProcessId.ToInt32();
|
||||
return parentProcessId > 0 ? Process.GetProcessById(parentProcessId) : null;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null; // Process not found
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct ProcessBasicInformation
|
||||
{
|
||||
// These members must match PROCESS_BASIC_INFORMATION
|
||||
public IntPtr Reserved1;
|
||||
public IntPtr PebBaseAddress;
|
||||
public IntPtr Reserved2_0;
|
||||
public IntPtr Reserved2_1;
|
||||
public IntPtr UniqueProcessId;
|
||||
public IntPtr InheritedFromUniqueProcessId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
internal static class StreamProtocolExtensions
|
||||
{
|
||||
public static async Task WriteStringAsync(this Stream stream, string str)
|
||||
{
|
||||
var utf8Bytes = Encoding.UTF8.GetBytes(str);
|
||||
await stream.WriteAsync(BitConverter.GetBytes(utf8Bytes.Length), 0, 4);
|
||||
await stream.WriteAsync(utf8Bytes, 0, utf8Bytes.Length);
|
||||
}
|
||||
|
||||
public static async Task WriteDateTimeAsync(this Stream stream, DateTime value)
|
||||
{
|
||||
var ticksBytes = BitConverter.GetBytes(value.Ticks);
|
||||
await stream.WriteAsync(ticksBytes, 0, 8);
|
||||
}
|
||||
|
||||
public static async Task<bool> ReadBoolAsync(this Stream stream)
|
||||
{
|
||||
var responseBuf = new byte[1];
|
||||
await stream.ReadAsync(responseBuf, 0, 1);
|
||||
return responseBuf[0] == 1;
|
||||
}
|
||||
|
||||
public static async Task<int> ReadIntAsync(this Stream stream)
|
||||
{
|
||||
var responseBuf = new byte[4];
|
||||
await stream.ReadAsync(responseBuf, 0, 4);
|
||||
return BitConverter.ToInt32(responseBuf, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the VS process that launched this app process (if any), and uses
|
||||
/// named pipes to communicate with its AutoRebuild listener (if any).
|
||||
/// </summary>
|
||||
internal class VSForWindowsRebuildService : IRebuildService
|
||||
{
|
||||
private const int _connectionTimeoutMilliseconds = 3000;
|
||||
private readonly Process _vsProcess;
|
||||
|
||||
public static bool TryCreate(out VSForWindowsRebuildService result)
|
||||
{
|
||||
var vsProcess = FindAncestorVSProcess();
|
||||
if (vsProcess != null)
|
||||
{
|
||||
result = new VSForWindowsRebuildService(vsProcess);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince)
|
||||
{
|
||||
var pipeName = $"BlazorAutoRebuild\\{_vsProcess.Id}";
|
||||
using (var pipeClient = new NamedPipeClientStream(pipeName))
|
||||
{
|
||||
await pipeClient.ConnectAsync(_connectionTimeoutMilliseconds);
|
||||
|
||||
// Protocol:
|
||||
// 1. Receive protocol version number from the VS listener
|
||||
// If we're incompatible with it, send back special string "abort" and end
|
||||
// 2. Send the project path to the VS listener
|
||||
// 3. Send the 'if not rebuilt since' timestamp to the VS listener
|
||||
// 4. Wait for it to send back a bool representing the result
|
||||
// Keep in sync with AutoRebuildService.cs in the BlazorExtension project
|
||||
// In the future we may extend this to getting back build error details
|
||||
var remoteProtocolVersion = await pipeClient.ReadIntAsync();
|
||||
if (remoteProtocolVersion == 1)
|
||||
{
|
||||
await pipeClient.WriteStringAsync(projectFullPath);
|
||||
await pipeClient.WriteDateTimeAsync(ifNotBuiltSince);
|
||||
return await pipeClient.ReadBoolAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await pipeClient.WriteStringAsync("abort");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private VSForWindowsRebuildService(Process vsProcess)
|
||||
{
|
||||
_vsProcess = vsProcess ?? throw new ArgumentNullException(nameof(vsProcess));
|
||||
}
|
||||
|
||||
private static Process FindAncestorVSProcess()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidateProcess = Process.GetCurrentProcess();
|
||||
try
|
||||
{
|
||||
while (candidateProcess != null && !candidateProcess.HasExited)
|
||||
{
|
||||
// It's unlikely that anyone's going to have a non-VS process in the process
|
||||
// hierarchy called 'devenv', but if that turns out to be a scenario, we could
|
||||
// (for example) write the VS PID to the obj directory during build, and then
|
||||
// only consider processes with that ID. We still want to be sure there really
|
||||
// is such a process in our ancestor chain, otherwise if you did "dotnet run"
|
||||
// in a command prompt, we'd be confused and think it was launched from VS.
|
||||
if (candidateProcess.ProcessName.Equals("devenv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return candidateProcess;
|
||||
}
|
||||
|
||||
candidateProcess = ProcessUtils.GetParent(candidateProcess);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// There's probably some permissions issue that prevents us from seeing
|
||||
// further up the ancestor list, so we have to stop looking here.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Server
|
||||
{
|
||||
internal class BlazorConfig
|
||||
{
|
||||
public string SourceMSBuildPath { get; }
|
||||
public string SourceOutputAssemblyPath { get; }
|
||||
public string WebRootPath { get; }
|
||||
public string DistPath
|
||||
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
|
||||
public bool EnableAutoRebuilding { get; }
|
||||
public bool EnableDebugging { get; }
|
||||
|
||||
public static BlazorConfig Read(string assemblyPath)
|
||||
=> new BlazorConfig(assemblyPath);
|
||||
|
||||
private BlazorConfig(string assemblyPath)
|
||||
{
|
||||
// TODO: Instead of assuming the lines are in a specific order, either JSON-encode
|
||||
// the whole thing, or at least give the lines key prefixes (e.g., "reload:<someuri>")
|
||||
// so we're not dependent on order and all lines being present.
|
||||
|
||||
var configFilePath = Path.ChangeExtension(assemblyPath, ".blazor.config");
|
||||
var configLines = File.ReadLines(configFilePath).ToList();
|
||||
SourceMSBuildPath = configLines[0];
|
||||
|
||||
if (SourceMSBuildPath == ".")
|
||||
{
|
||||
SourceMSBuildPath = assemblyPath;
|
||||
}
|
||||
|
||||
var sourceMsBuildDir = Path.GetDirectoryName(SourceMSBuildPath);
|
||||
SourceOutputAssemblyPath = Path.Combine(sourceMsBuildDir, configLines[1]);
|
||||
|
||||
var webRootPath = Path.Combine(sourceMsBuildDir, "wwwroot");
|
||||
if (Directory.Exists(webRootPath))
|
||||
{
|
||||
WebRootPath = webRootPath;
|
||||
}
|
||||
|
||||
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
|
||||
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public string FindIndexHtmlFile()
|
||||
{
|
||||
// Before publishing, the client project may have a wwwroot directory.
|
||||
// If so, and if it contains index.html, use that.
|
||||
if (!string.IsNullOrEmpty(WebRootPath))
|
||||
{
|
||||
var wwwrootIndexHtmlPath = Path.Combine(WebRootPath, "index.html");
|
||||
if (File.Exists(wwwrootIndexHtmlPath))
|
||||
{
|
||||
return wwwrootIndexHtmlPath;
|
||||
}
|
||||
}
|
||||
|
||||
// After publishing, the client project won't have a wwwroot directory.
|
||||
// The contents from that dir will have been copied to "dist" during publish.
|
||||
// So if "dist/index.html" now exists, use that.
|
||||
var distIndexHtmlPath = Path.Combine(DistPath, "index.html");
|
||||
if (File.Exists(distIndexHtmlPath))
|
||||
{
|
||||
return distIndexHtmlPath;
|
||||
}
|
||||
|
||||
// Since there's no index.html, we'll use the default DefaultPageStaticFileOptions,
|
||||
// hence we'll look for index.html in the host server app's wwwroot.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mime;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
|
||||
/// </summary>
|
||||
public static class BlazorHostingApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
|
||||
/// specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
UseClientSideBlazorFiles(app, typeof(TClientApp).Assembly.Location);
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
|
||||
/// specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseClientSideBlazorFiles(this IApplicationBuilder app, string clientAssemblyFilePath)
|
||||
{
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
var fileProviders = new List<IFileProvider>();
|
||||
|
||||
// TODO: Make the .blazor.config file contents sane
|
||||
// Currently the items in it are bizarre and don't relate to their purpose,
|
||||
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
|
||||
var config = BlazorConfig.Read(clientAssemblyFilePath);
|
||||
|
||||
// First, match the request against files in the client app dist directory
|
||||
fileProviders.Add(new PhysicalFileProvider(config.DistPath));
|
||||
|
||||
// * Before publishing, we serve the wwwroot files directly from source
|
||||
// (and don't require them to be copied into dist).
|
||||
// In this case, WebRootPath will be nonempty if that directory exists.
|
||||
// * After publishing, the wwwroot files are already copied to 'dist' and
|
||||
// will be served by the above middleware, so we do nothing here.
|
||||
// In this case, WebRootPath will be empty (the publish process sets this).
|
||||
if (!string.IsNullOrEmpty(config.WebRootPath))
|
||||
{
|
||||
fileProviders.Add(new PhysicalFileProvider(config.WebRootPath));
|
||||
}
|
||||
|
||||
// We can't modify an IFileContentTypeProvider, so we have to decorate.
|
||||
var contentTypeProvider = new FileExtensionContentTypeProvider();
|
||||
AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet);
|
||||
if (config.EnableDebugging)
|
||||
{
|
||||
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
|
||||
}
|
||||
|
||||
var options = new StaticFileOptions()
|
||||
{
|
||||
ContentTypeProvider = contentTypeProvider,
|
||||
FileProvider = new CompositeFileProvider(fileProviders),
|
||||
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
|
||||
};
|
||||
|
||||
app.UseStaticFiles(options);
|
||||
return app;
|
||||
|
||||
static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
|
||||
{
|
||||
if (!provider.Mappings.ContainsKey(name))
|
||||
{
|
||||
provider.Mappings.Add(name, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
|
||||
/// </summary>
|
||||
public static class BlazorHostingEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, clientAssemblyFilePath, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <typeparamref name="TClientApp"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="pattern">The route pattern to match.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string pattern, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, pattern, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side
|
||||
/// Blazor application specified by <paramref name="clientAssemblyFilePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <param name="pattern">The route pattern to match.</param>
|
||||
/// <param name="filePath">
|
||||
/// The relative path to the entry point of the client-side application. The path is relative to the
|
||||
/// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>.
|
||||
/// </param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to handle cases where URL path of the request does not contain a filename, and no other
|
||||
/// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor
|
||||
/// application, while also allowing requests for non-existent files to result in an HTTP 404.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string pattern, string filePath)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (clientAssemblyFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
|
||||
}
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
var config = BlazorConfig.Read(clientAssemblyFilePath);
|
||||
|
||||
// We want to serve "index.html" from whichever directory contains it in this priority order:
|
||||
// 1. Client app "dist" directory
|
||||
// 2. Client app "wwwroot" directory
|
||||
// 3. Server app "wwwroot" directory
|
||||
var directory = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>().WebRootPath;
|
||||
var indexHtml = config.FindIndexHtmlFile();
|
||||
if (indexHtml != null)
|
||||
{
|
||||
directory = Path.GetDirectoryName(indexHtml);
|
||||
}
|
||||
|
||||
var options = new StaticFileOptions()
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(directory),
|
||||
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
|
||||
};
|
||||
|
||||
return endpoints.MapFallbackToFile(pattern, filePath, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<Description>Runtime server features for ASP.NET Core Blazor applications.</Description>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.AspNetCore.SpaServices.Extensions" />
|
||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
||||
<Reference Include="Microsoft.AspNetCore.WebSockets" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
|
||||
<!-- Used by ws-proxy sources only. Remove this once we're able to consume ws-proxy as a NuGet package. -->
|
||||
<Reference Include="Mono.Cecil" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using WsProxy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides infrastructure for debugging Blazor applications.
|
||||
/// </summary>
|
||||
public static class BlazorMonoDebugProxyAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds middleware for needed for debugging Blazor applications
|
||||
/// inside Chromium dev tools.
|
||||
/// </summary>
|
||||
public static void UseBlazorDebugging(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
var requestPath = context.Request.Path;
|
||||
if (!requestPath.StartsWithSegments("/_framework/debug"))
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugWebSocketProxyRequest(context);
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugHome(context);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task DebugWebSocketProxyRequest(HttpContext context)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
var browserUri = new Uri(context.Request.Query["browser"]);
|
||||
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await new MonoProxy().Run(browserUri, ideSocket);
|
||||
}
|
||||
|
||||
private static async Task DebugHome(HttpContext context)
|
||||
{
|
||||
context.Response.ContentType = "text/html";
|
||||
|
||||
var request = context.Request;
|
||||
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
|
||||
var targetTabUrl = request.Query["url"];
|
||||
if (string.IsNullOrEmpty(targetTabUrl))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
await context.Response.WriteAsync("No value specified for 'url'");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Allow overriding port (but not hostname, as we're connecting to the
|
||||
// local browser, not to the webserver serving the app)
|
||||
var debuggerHost = "http://localhost:9222";
|
||||
var debuggerTabsListUrl = $"{debuggerHost}/json";
|
||||
IEnumerable<BrowserTab> availableTabs;
|
||||
|
||||
try
|
||||
{
|
||||
availableTabs = await GetOpenedBrowserTabs(debuggerHost);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
|
||||
Ensure your browser is running with debugging enabled.
|
||||
</p>
|
||||
<h2>Resolution</h2>
|
||||
<p>
|
||||
<h4>If you are using Google Chrome for your development, follow these instructions:</h4>
|
||||
{GetLaunchChromeInstructions(appRootUrl)}
|
||||
</p>
|
||||
<p>
|
||||
<h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4>
|
||||
{GetLaunchEdgeInstructions(appRootUrl)}
|
||||
</p>
|
||||
<strong>This should launch a new browser window with debugging enabled..</p>
|
||||
<h2>Underlying exception:</h2>
|
||||
<pre>{ex}</pre>
|
||||
");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var matchingTabs = availableTabs
|
||||
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
if (matchingTabs.Count == 0)
|
||||
{
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
The response from <code>{debuggerTabsListUrl}</code> does not include
|
||||
any entry for <code>{targetTabUrl}</code>.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
else if (matchingTabs.Count > 1)
|
||||
{
|
||||
// TODO: Automatically disambiguate by adding a GUID to the page title
|
||||
// when you press the debugger hotkey, include it in the querystring passed
|
||||
// here, then remove it once the debugger connects.
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Multiple matching tabs are open</h1>
|
||||
<p>
|
||||
There is more than one browser tab at <code>{targetTabUrl}</code>.
|
||||
Close the ones you do not wish to debug, then refresh this page.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we know uniquely which tab to debug, construct the URL to the debug
|
||||
// page and redirect there
|
||||
var tabToDebug = matchingTabs.Single();
|
||||
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
|
||||
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
|
||||
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
|
||||
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?ws={proxyEndpoint}";
|
||||
context.Response.Redirect(devToolsUrlWithProxy);
|
||||
}
|
||||
|
||||
private static string GetLaunchChromeInstructions(string appRootUrl)
|
||||
{
|
||||
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $@"<p>Press Win+R and enter the following:</p>
|
||||
<p><strong><code>chrome --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return $@"<p>In a terminal window execute the following:</p>
|
||||
<p><strong><code>google-chrome --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return $@"<p>Execute the following:</p>
|
||||
<p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown OS platform");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLaunchEdgeInstructions(string appRootUrl)
|
||||
{
|
||||
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $@"<p>Press Win+R and enter the following:</p>
|
||||
<p><strong><code>msedge --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return $@"<p>In a terminal window execute the following:</p>
|
||||
<p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown OS platform");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs(string debuggerHost)
|
||||
{
|
||||
using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
|
||||
{
|
||||
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
|
||||
return JsonConvert.DeserializeObject<BrowserTab[]>(jsonResponse);
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserTab
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string DevtoolsFrontendUrl { get; set; }
|
||||
public string WebSocketDebuggerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,651 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net.Http;
|
||||
using Mono.Cecil.Pdb;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WsProxy {
|
||||
internal class BreakPointRequest {
|
||||
public string Assembly { get; private set; }
|
||||
public string File { get; private set; }
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
|
||||
public override string ToString () {
|
||||
return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
}
|
||||
|
||||
public static BreakPointRequest Parse (JObject args, DebugStore store)
|
||||
{
|
||||
if (args == null)
|
||||
return null;
|
||||
|
||||
var url = args? ["url"]?.Value<string> ();
|
||||
if (url == null) {
|
||||
var urlRegex = args?["urlRegex"].Value<string>();
|
||||
var sourceFile = store.GetFileByUrlRegex (urlRegex);
|
||||
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) {
|
||||
var sourceFile = store.GetFileByUrl (url);
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
var parts = ParseDocumentUrl (url);
|
||||
if (parts.Assembly == null)
|
||||
return null;
|
||||
|
||||
var line = args? ["lineNumber"]?.Value<int> ();
|
||||
var column = args? ["columnNumber"]?.Value<int> ();
|
||||
if (line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new BreakPointRequest () {
|
||||
Assembly = parts.Assembly,
|
||||
File = parts.DocumentPath,
|
||||
Line = line.Value,
|
||||
Column = column.Value
|
||||
};
|
||||
}
|
||||
|
||||
static (string Assembly, string DocumentPath) ParseDocumentUrl (string url)
|
||||
{
|
||||
if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") {
|
||||
return (
|
||||
docUri.Host,
|
||||
docUri.PathAndQuery.Substring (1)
|
||||
);
|
||||
} else {
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class VarInfo {
|
||||
public VarInfo (VariableDebugInformation v)
|
||||
{
|
||||
this.Name = v.Name;
|
||||
this.Index = v.Index;
|
||||
}
|
||||
|
||||
public VarInfo (ParameterDefinition p)
|
||||
{
|
||||
this.Name = p.Name;
|
||||
this.Index = (p.Index + 1) * -1;
|
||||
}
|
||||
public string Name { get; private set; }
|
||||
public int Index { get; private set; }
|
||||
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"(var-info [{Index}] '{Name}')";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CliLocation {
|
||||
|
||||
private MethodInfo method;
|
||||
private int offset;
|
||||
|
||||
public CliLocation (MethodInfo method, int offset)
|
||||
{
|
||||
this.method = method;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get => method; }
|
||||
public int Offset { get => offset; }
|
||||
}
|
||||
|
||||
|
||||
internal class SourceLocation {
|
||||
SourceId id;
|
||||
int line;
|
||||
int column;
|
||||
CliLocation cliLoc;
|
||||
|
||||
public SourceLocation (SourceId id, int line, int column)
|
||||
{
|
||||
this.id = id;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public SourceLocation (MethodInfo mi, SequencePoint sp)
|
||||
{
|
||||
this.id = mi.SourceId;
|
||||
this.line = sp.StartLine - 1;
|
||||
this.column = sp.StartColumn - 1;
|
||||
this.cliLoc = new CliLocation (mi, sp.Offset);
|
||||
}
|
||||
|
||||
public SourceId Id { get => id; }
|
||||
public int Line { get => line; }
|
||||
public int Column { get => column; }
|
||||
public CliLocation CliLocation => this.cliLoc;
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"{id}:{Line}:{Column}";
|
||||
}
|
||||
|
||||
public static SourceLocation Parse (JObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var id = SourceId.TryParse (obj ["scriptId"]?.Value<string> ());
|
||||
var line = obj ["lineNumber"]?.Value<int> ();
|
||||
var column = obj ["columnNumber"]?.Value<int> ();
|
||||
if (id == null || line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new SourceLocation (id, line.Value, column.Value);
|
||||
}
|
||||
|
||||
internal JObject ToJObject ()
|
||||
{
|
||||
return JObject.FromObject (new {
|
||||
scriptId = id.ToString (),
|
||||
lineNumber = line,
|
||||
columnNumber = column
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class SourceId {
|
||||
readonly int assembly, document;
|
||||
|
||||
public int Assembly => assembly;
|
||||
public int Document => document;
|
||||
|
||||
internal SourceId (int assembly, int document)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
|
||||
public SourceId (string id)
|
||||
{
|
||||
id = id.Substring ("dotnet://".Length);
|
||||
var sp = id.Split ('_');
|
||||
this.assembly = int.Parse (sp [0]);
|
||||
this.document = int.Parse (sp [1]);
|
||||
}
|
||||
|
||||
public static SourceId TryParse (string id)
|
||||
{
|
||||
if (!id.StartsWith ("dotnet://", StringComparison.InvariantCulture))
|
||||
return null;
|
||||
return new SourceId (id);
|
||||
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"dotnet://{assembly}_{document}";
|
||||
}
|
||||
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
SourceId that = obj as SourceId;
|
||||
return that.assembly == this.assembly && that.document == this.document;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return this.assembly.GetHashCode () ^ this.document.GetHashCode ();
|
||||
}
|
||||
|
||||
public static bool operator == (SourceId a, SourceId b)
|
||||
{
|
||||
if ((object)a == null)
|
||||
return (object)b == null;
|
||||
return a.Equals (b);
|
||||
}
|
||||
|
||||
public static bool operator != (SourceId a, SourceId b)
|
||||
{
|
||||
return !a.Equals (b);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MethodInfo {
|
||||
AssemblyInfo assembly;
|
||||
internal MethodDefinition methodDef;
|
||||
SourceFile source;
|
||||
|
||||
public SourceId SourceId => source.SourceId;
|
||||
|
||||
public string Name => methodDef.Name;
|
||||
|
||||
public SourceLocation StartLocation { get; private set; }
|
||||
public SourceLocation EndLocation { get; private set; }
|
||||
public AssemblyInfo Assembly => assembly;
|
||||
public int Token => (int)methodDef.MetadataToken.RID;
|
||||
|
||||
public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.methodDef = methodDef;
|
||||
this.source = source;
|
||||
|
||||
var sps = methodDef.DebugInformation.SequencePoints;
|
||||
if (sps != null && sps.Count > 0) {
|
||||
StartLocation = new SourceLocation (this, sps [0]);
|
||||
EndLocation = new SourceLocation (this, sps [sps.Count - 1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SourceLocation GetLocationByIl (int pos)
|
||||
{
|
||||
SequencePoint prev = null;
|
||||
foreach (var sp in methodDef.DebugInformation.SequencePoints) {
|
||||
if (sp.Offset > pos)
|
||||
break;
|
||||
prev = sp;
|
||||
}
|
||||
|
||||
if (prev != null)
|
||||
return new SourceLocation (this, prev);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VarInfo [] GetLiveVarsAt (int offset)
|
||||
{
|
||||
var res = new List<VarInfo> ();
|
||||
|
||||
res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p)));
|
||||
|
||||
res.AddRange (methodDef.DebugInformation.GetScopes ()
|
||||
.Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
|
||||
.SelectMany (s => s.Variables)
|
||||
.Where (v => !v.IsDebuggerHidden)
|
||||
.Select (v => new VarInfo (v)));
|
||||
|
||||
|
||||
return res.ToArray ();
|
||||
}
|
||||
}
|
||||
|
||||
internal class AssemblyInfo {
|
||||
static int next_id;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> ();
|
||||
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
|
||||
readonly List<SourceFile> sources = new List<SourceFile>();
|
||||
|
||||
public AssemblyInfo (byte[] assembly, byte[] pdb)
|
||||
{
|
||||
lock (typeof (AssemblyInfo)) {
|
||||
this.id = ++next_id;
|
||||
}
|
||||
|
||||
try {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PortablePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
} catch (BadImageFormatException ex) {
|
||||
Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
}
|
||||
|
||||
if (this.image == null) {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new NativePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
}
|
||||
|
||||
Populate ();
|
||||
}
|
||||
|
||||
public AssemblyInfo ()
|
||||
{
|
||||
}
|
||||
|
||||
void Populate ()
|
||||
{
|
||||
ProcessSourceLink();
|
||||
|
||||
var d2s = new Dictionary<Document, SourceFile> ();
|
||||
|
||||
Func<Document, SourceFile> get_src = (doc) => {
|
||||
if (doc == null)
|
||||
return null;
|
||||
if (d2s.ContainsKey (doc))
|
||||
return d2s [doc];
|
||||
var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url));
|
||||
sources.Add (src);
|
||||
d2s [doc] = src;
|
||||
return src;
|
||||
};
|
||||
|
||||
foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) {
|
||||
Document first_doc = null;
|
||||
foreach (var sp in m.DebugInformation.SequencePoints) {
|
||||
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) {
|
||||
first_doc = sp.Document;
|
||||
}
|
||||
// else if (first_doc != sp.Document) {
|
||||
// //FIXME this is needed for (c)ctors in corlib
|
||||
// throw new Exception ($"Cant handle multi-doc methods in {m}");
|
||||
//}
|
||||
}
|
||||
|
||||
if (first_doc == null) {
|
||||
// all generated files
|
||||
first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document;
|
||||
}
|
||||
|
||||
if (first_doc != null) {
|
||||
var src = get_src (first_doc);
|
||||
var mi = new MethodInfo (this, m, src);
|
||||
int mt = (int)m.MetadataToken.RID;
|
||||
this.methods [mt] = mi;
|
||||
if (src != null)
|
||||
src.AddMethod (mi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSourceLink ()
|
||||
{
|
||||
var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink);
|
||||
|
||||
if (sourceLinkDebugInfo != null) {
|
||||
var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
|
||||
|
||||
if (sourceLinkContent != null) {
|
||||
var jObject = JObject.Parse (sourceLinkContent) ["documents"];
|
||||
sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Uri GetSourceLinkUrl (string document)
|
||||
{
|
||||
if (sourceLinkMappings.TryGetValue (document, out string url)) {
|
||||
return new Uri (url);
|
||||
}
|
||||
|
||||
foreach (var sourceLinkDocument in sourceLinkMappings) {
|
||||
string key = sourceLinkDocument.Key;
|
||||
|
||||
if (Path.GetFileName (key) != "*") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyTrim = key.TrimEnd ('*');
|
||||
|
||||
if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) {
|
||||
var docUrlPart = document.Replace (keyTrim, "");
|
||||
return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetRelativePath (string relativeTo, string path)
|
||||
{
|
||||
var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute);
|
||||
var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) {
|
||||
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> Sources {
|
||||
get { return this.sources; }
|
||||
}
|
||||
|
||||
public int Id => id;
|
||||
public string Name => image.Name;
|
||||
|
||||
public SourceFile GetDocById (int document)
|
||||
{
|
||||
return sources.FirstOrDefault (s => s.SourceId.Document == document);
|
||||
}
|
||||
|
||||
public MethodInfo GetMethodByToken (int token)
|
||||
{
|
||||
methods.TryGetValue (token, out var value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SourceFile {
|
||||
HashSet<MethodInfo> methods;
|
||||
AssemblyInfo assembly;
|
||||
int id;
|
||||
Document doc;
|
||||
|
||||
internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
|
||||
{
|
||||
this.methods = new HashSet<MethodInfo> ();
|
||||
this.SourceLinkUri = sourceLinkUri;
|
||||
this.assembly = assembly;
|
||||
this.id = id;
|
||||
this.doc = doc;
|
||||
this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", "");
|
||||
|
||||
this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
|
||||
if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) {
|
||||
this.Url = this.SourceUri.ToString ();
|
||||
} else {
|
||||
this.Url = DotNetUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void AddMethod (MethodInfo mi)
|
||||
{
|
||||
this.methods.Add (mi);
|
||||
}
|
||||
public string DebuggerFileName { get; }
|
||||
public string Url { get; }
|
||||
public string AssemblyName => assembly.Name;
|
||||
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
|
||||
public string DocHashCode => "abcdee" + id;
|
||||
public SourceId SourceId => new SourceId (assembly.Id, this.id);
|
||||
public Uri SourceLinkUri { get; }
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public IEnumerable<MethodInfo> Methods => this.methods;
|
||||
}
|
||||
|
||||
internal class DebugStore {
|
||||
List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
|
||||
|
||||
public DebugStore (string [] loaded_files)
|
||||
{
|
||||
bool MatchPdb (string asm, string pdb)
|
||||
{
|
||||
return Path.ChangeExtension (asm, "pdb") == pdb;
|
||||
}
|
||||
|
||||
var asm_files = new List<string> ();
|
||||
var pdb_files = new List<string> ();
|
||||
foreach (var f in loaded_files) {
|
||||
var file_name = f;
|
||||
if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase))
|
||||
pdb_files.Add (file_name);
|
||||
else
|
||||
asm_files.Add (file_name);
|
||||
}
|
||||
|
||||
//FIXME make this parallel
|
||||
foreach (var p in asm_files) {
|
||||
try {
|
||||
var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n));
|
||||
HttpClient h = new HttpClient ();
|
||||
var assembly_bytes = h.GetByteArrayAsync (p).Result;
|
||||
byte [] pdb_bytes = null;
|
||||
if (pdb != null)
|
||||
pdb_bytes = h.GetByteArrayAsync (pdb).Result;
|
||||
|
||||
this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes));
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine ($"Failed to read {p} ({e.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> AllSources ()
|
||||
{
|
||||
foreach (var a in assemblies) {
|
||||
foreach (var s in a.Sources)
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
public SourceFile GetFileById (SourceId id)
|
||||
{
|
||||
return AllSources ().FirstOrDefault (f => f.SourceId.Equals (id));
|
||||
}
|
||||
|
||||
public AssemblyInfo GetAssemblyByName (string name)
|
||||
{
|
||||
return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
|
||||
{
|
||||
var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
|
||||
var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
|
||||
|
||||
if (start.Line > spStart.Line)
|
||||
return false;
|
||||
if (start.Column > spStart.Column && start.Line == sp.StartLine)
|
||||
return false;
|
||||
|
||||
if (end.Line < spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (end.Column < spEnd.Column && end.Line == spEnd.Line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end)
|
||||
{
|
||||
//XXX FIXME no idea what todo with locations on different files
|
||||
if (start.Id != end.Id)
|
||||
return null;
|
||||
var src_id = start.Id;
|
||||
|
||||
var doc = GetFileById (src_id);
|
||||
|
||||
var res = new List<SourceLocation> ();
|
||||
if (doc == null) {
|
||||
//FIXME we need to write up logging here
|
||||
Console.WriteLine ($"Could not find document {src_id}");
|
||||
return res;
|
||||
}
|
||||
|
||||
foreach (var m in doc.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
if (Match (sp, start, end))
|
||||
res.Add (new SourceLocation (m, sp));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match (SequencePoint sp, int line, int column)
|
||||
{
|
||||
var bp = (line: line + 1, column: column + 1);
|
||||
|
||||
if (sp.StartLine > bp.line || sp.EndLine < bp.line)
|
||||
return false;
|
||||
|
||||
//Chrome sends a zero column even if getPossibleBreakpoints say something else
|
||||
if (column == 0)
|
||||
return true;
|
||||
|
||||
if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
|
||||
return false;
|
||||
|
||||
if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public SourceLocation FindBestBreakpoint (BreakPointRequest req)
|
||||
{
|
||||
var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase));
|
||||
var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (src == null)
|
||||
return null;
|
||||
|
||||
foreach (var m in src.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
//FIXME handle multi doc methods
|
||||
if (Match (sp, req.Line, req.Column))
|
||||
return new SourceLocation (m, sp);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ToUrl (SourceLocation location)
|
||||
=> location != null ? GetFileById (location.Id).Url : "";
|
||||
|
||||
public SourceFile GetFileByUrlRegex (string urlRegex)
|
||||
{
|
||||
var regex = new Regex (urlRegex);
|
||||
return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString()));
|
||||
}
|
||||
|
||||
public SourceFile GetFileByUrl (string url)
|
||||
=> AllSources ().FirstOrDefault (file => file.Url.ToString() == url);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,771 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace WsProxy {
|
||||
|
||||
internal class MonoCommands {
|
||||
public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()";
|
||||
public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready";
|
||||
public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})";
|
||||
public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])";
|
||||
public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})";
|
||||
public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
|
||||
public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
|
||||
public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
|
||||
public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
|
||||
public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
|
||||
}
|
||||
|
||||
internal enum MonoErrorCodes {
|
||||
BpNotFound = 100000,
|
||||
}
|
||||
|
||||
|
||||
internal class MonoConstants {
|
||||
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
|
||||
}
|
||||
class Frame {
|
||||
public Frame (MethodInfo method, SourceLocation location, int id)
|
||||
{
|
||||
this.Method = method;
|
||||
this.Location = location;
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; private set; }
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int Id { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
class Breakpoint {
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int LocalId { get; private set; }
|
||||
public int RemoteId { get; set; }
|
||||
public BreakPointState State { get; set; }
|
||||
|
||||
public Breakpoint (SourceLocation loc, int localId, BreakPointState state)
|
||||
{
|
||||
this.Location = loc;
|
||||
this.LocalId = localId;
|
||||
this.State = state;
|
||||
}
|
||||
}
|
||||
|
||||
enum BreakPointState {
|
||||
Active,
|
||||
Disabled,
|
||||
Pending
|
||||
}
|
||||
|
||||
enum StepKind {
|
||||
Into,
|
||||
Out,
|
||||
Over
|
||||
}
|
||||
|
||||
internal class MonoProxy : WsProxy {
|
||||
DebugStore store;
|
||||
List<Breakpoint> breakpoints = new List<Breakpoint> ();
|
||||
List<Frame> current_callstack;
|
||||
bool runtime_ready;
|
||||
int local_breakpoint_id;
|
||||
int ctx_id;
|
||||
JObject aux_ctx_data;
|
||||
|
||||
public MonoProxy () { }
|
||||
|
||||
protected override async Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
switch (method) {
|
||||
case "Runtime.executionContextCreated": {
|
||||
var ctx = args? ["context"];
|
||||
var aux_data = ctx? ["auxData"] as JObject;
|
||||
if (aux_data != null) {
|
||||
var is_default = aux_data ["isDefault"]?.Value<bool> ();
|
||||
if (is_default == true) {
|
||||
var ctx_id = ctx ["id"].Value<int> ();
|
||||
await OnDefaultContext (ctx_id, aux_data, token);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Debugger.paused": {
|
||||
//TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
|
||||
var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
|
||||
if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
|
||||
await OnBreakPointHit (args, token);
|
||||
return true;
|
||||
}
|
||||
if (top_func == MonoConstants.RUNTIME_IS_READY) {
|
||||
await OnRuntimeReady (token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Debugger.scriptParsed":{
|
||||
if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) {
|
||||
// Console.WriteLine ("ignoring wasm event");
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected override async Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
switch (method) {
|
||||
case "Debugger.getScriptSource": {
|
||||
var script_id = args? ["scriptId"]?.Value<string> ();
|
||||
if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
await OnGetScriptSource (id, script_id, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Runtime.compileScript": {
|
||||
var exp = args? ["expression"]?.Value<string> ();
|
||||
if (exp.StartsWith ("//dotnet:", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
OnCompileDotnetScript (id, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.getPossibleBreakpoints": {
|
||||
var start = SourceLocation.Parse (args? ["start"] as JObject);
|
||||
//FIXME support variant where restrictToFunction=true and end is omitted
|
||||
var end = SourceLocation.Parse (args? ["end"] as JObject);
|
||||
if (start != null && end != null)
|
||||
return GetPossibleBreakpoints (id, start, end, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.setBreakpointByUrl": {
|
||||
Info ($"BP req {args}");
|
||||
var bp_req = BreakPointRequest.Parse (args, store);
|
||||
if (bp_req != null) {
|
||||
await SetBreakPoint (id, bp_req, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Debugger.removeBreakpoint": {
|
||||
return await RemoveBreakpoint (id, args, token);
|
||||
}
|
||||
|
||||
case "Debugger.resume": {
|
||||
await OnResume (token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.stepInto": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Into, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.stepOut": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Out, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.stepOver": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Over, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Runtime.getProperties": {
|
||||
var objId = args? ["objectId"]?.Value<string> ();
|
||||
if (objId.StartsWith ("dotnet:scope:", StringComparison.InvariantCulture)) {
|
||||
await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token);
|
||||
return true;
|
||||
}
|
||||
if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture))
|
||||
{
|
||||
if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture))
|
||||
await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES);
|
||||
if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture))
|
||||
await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task OnRuntimeReady (CancellationToken token)
|
||||
{
|
||||
Info ("RUNTIME READY, PARTY TIME");
|
||||
await RuntimeReady (token);
|
||||
await SendCommand ("Debugger.resume", new JObject (), token);
|
||||
SendEvent ("Mono.runtimeReady", new JObject (), token);
|
||||
}
|
||||
|
||||
async Task OnBreakPointHit (JObject args, CancellationToken token)
|
||||
{
|
||||
//FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.GET_CALL_STACK,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true
|
||||
});
|
||||
|
||||
var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
|
||||
if (res.IsErr) {
|
||||
//Give up and send the original call stack
|
||||
SendEvent ("Debugger.paused", args, token);
|
||||
return;
|
||||
}
|
||||
|
||||
//step one, figure out where did we hit
|
||||
var res_value = res.Value? ["result"]? ["value"];
|
||||
if (res_value == null || res_value is JValue) {
|
||||
//Give up and send the original call stack
|
||||
SendEvent ("Debugger.paused", args, token);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug ($"call stack (err is {res.Error} value is:\n{res.Value}");
|
||||
var bp_id = res_value? ["breakpoint_id"]?.Value<int> ();
|
||||
Debug ($"We just hit bp {bp_id}");
|
||||
if (!bp_id.HasValue) {
|
||||
//Give up and send the original call stack
|
||||
SendEvent ("Debugger.paused", args, token);
|
||||
return;
|
||||
}
|
||||
var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value);
|
||||
|
||||
var src = bp == null ? null : store.GetFileById (bp.Location.Id);
|
||||
|
||||
var callFrames = new List<JObject> ();
|
||||
foreach (var frame in orig_callframes) {
|
||||
var function_name = frame ["functionName"]?.Value<string> ();
|
||||
var url = frame ["url"]?.Value<string> ();
|
||||
if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
|
||||
var frames = new List<Frame> ();
|
||||
int frame_id = 0;
|
||||
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
|
||||
foreach (var mono_frame in the_mono_frames) {
|
||||
var il_pos = mono_frame ["il_pos"].Value<int> ();
|
||||
var method_token = mono_frame ["method_token"].Value<int> ();
|
||||
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
|
||||
|
||||
var asm = store.GetAssemblyByName (assembly_name);
|
||||
var method = asm.GetMethodByToken (method_token);
|
||||
|
||||
if (method == null) {
|
||||
Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var location = method?.GetLocationByIl (il_pos);
|
||||
|
||||
// When hitting a breakpoint on the "IncrementCount" method in the standard
|
||||
// Blazor project template, one of the stack frames is inside mscorlib.dll
|
||||
// and we get location==null for it. It will trigger a NullReferenceException
|
||||
// if we don't skip over that stack frame.
|
||||
if (location == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Info ($"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
|
||||
Info ($"\tmethod {method.Name} location: {location}");
|
||||
frames.Add (new Frame (method, location, frame_id));
|
||||
|
||||
callFrames.Add (JObject.FromObject (new {
|
||||
functionName = method.Name,
|
||||
callFrameId = $"dotnet:scope:{frame_id}",
|
||||
functionLocation = method.StartLocation.ToJObject (),
|
||||
|
||||
location = location.ToJObject (),
|
||||
|
||||
url = store.ToUrl (location),
|
||||
|
||||
scopeChain = new [] {
|
||||
new {
|
||||
type = "local",
|
||||
@object = new {
|
||||
@type = "object",
|
||||
className = "Object",
|
||||
description = "Object",
|
||||
objectId = $"dotnet:scope:{frame_id}",
|
||||
},
|
||||
name = method.Name,
|
||||
startLocation = method.StartLocation.ToJObject (),
|
||||
endLocation = method.EndLocation.ToJObject (),
|
||||
}}
|
||||
}));
|
||||
|
||||
++frame_id;
|
||||
this.current_callstack = frames;
|
||||
|
||||
}
|
||||
} else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
|
||||
|| url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
|
||||
callFrames.Add (frame);
|
||||
}
|
||||
}
|
||||
|
||||
var bp_list = new string [bp == null ? 0 : 1];
|
||||
if (bp != null)
|
||||
bp_list [0] = $"dotnet:{bp.LocalId}";
|
||||
|
||||
o = JObject.FromObject (new {
|
||||
callFrames = callFrames,
|
||||
reason = "other", //other means breakpoint
|
||||
hitBreakpoints = bp_list,
|
||||
});
|
||||
|
||||
SendEvent ("Debugger.paused", o, token);
|
||||
}
|
||||
|
||||
async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken token)
|
||||
{
|
||||
Debug ("Default context created, clearing state and sending events");
|
||||
|
||||
//reset all bps
|
||||
foreach (var b in this.breakpoints){
|
||||
b.State = BreakPointState.Pending;
|
||||
}
|
||||
this.runtime_ready = false;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.IS_RUNTIME_READY_VAR,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true
|
||||
});
|
||||
this.ctx_id = ctx_id;
|
||||
this.aux_ctx_data = aux_data;
|
||||
|
||||
Debug ("checking if the runtime is ready");
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
|
||||
//Debug ($"\t{is_ready}");
|
||||
if (is_ready.HasValue && is_ready.Value == true) {
|
||||
Debug ("RUNTIME LOOK READY. GO TIME!");
|
||||
await RuntimeReady (token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async Task OnResume (CancellationToken token)
|
||||
{
|
||||
//discard frames
|
||||
this.current_callstack = null;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
async Task Step (int msg_id, StepKind kind, CancellationToken token)
|
||||
{
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
|
||||
SendResponse (msg_id, Result.Ok (new JObject ()), token);
|
||||
|
||||
this.current_callstack = null;
|
||||
|
||||
await SendCommand ("Debugger.resume", new JObject (), token);
|
||||
}
|
||||
|
||||
async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command)
|
||||
{
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
expression = string.Format(command, object_id),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand("Runtime.evaluate", o, token);
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (res.IsErr)
|
||||
{
|
||||
SendResponse(msg_id, res, token);
|
||||
return;
|
||||
}
|
||||
|
||||
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
|
||||
|
||||
var var_list = new List<JObject>();
|
||||
|
||||
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
|
||||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
for (int i = 0; i < values.Length; i+=2)
|
||||
{
|
||||
string fieldName = (string)values[i]["name"];
|
||||
if (fieldName.Contains("k__BackingField")){
|
||||
fieldName = fieldName.Replace("k__BackingField", "");
|
||||
fieldName = fieldName.Replace("<", "");
|
||||
fieldName = fieldName.Replace(">", "");
|
||||
}
|
||||
var_list.Add(JObject.FromObject(new
|
||||
{
|
||||
name = fieldName,
|
||||
value = values[i+1]["value"]
|
||||
}));
|
||||
|
||||
}
|
||||
o = JObject.FromObject(new
|
||||
{
|
||||
result = var_list
|
||||
});
|
||||
|
||||
SendResponse(msg_id, Result.Ok(o), token);
|
||||
}
|
||||
|
||||
|
||||
async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
|
||||
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
|
||||
|
||||
|
||||
var var_ids = string.Join (",", vars.Select (v => v.Index));
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (res.IsErr) {
|
||||
SendResponse (msg_id, res, token);
|
||||
return;
|
||||
}
|
||||
|
||||
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
|
||||
|
||||
var var_list = new List<JObject> ();
|
||||
int i = 0;
|
||||
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
|
||||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
while (values != null && i < vars.Length && i < values.Length) {
|
||||
var value = values [i] ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString();
|
||||
|
||||
var_list.Add (JObject.FromObject (new {
|
||||
name = vars [i].Name,
|
||||
value = values [i] ["value"]
|
||||
}));
|
||||
i++;
|
||||
}
|
||||
//Async methods are special in the way that local variables can be lifted to generated class fields
|
||||
//value of "this" comes here either
|
||||
while (i < values.Length) {
|
||||
String name = values [i] ["name"].ToString ();
|
||||
|
||||
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
|
||||
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
|
||||
var_list.Add (JObject.FromObject (new {
|
||||
name = name,
|
||||
value = values [i+1] ["value"]
|
||||
}));
|
||||
i = i + 2;
|
||||
}
|
||||
o = JObject.FromObject (new {
|
||||
result = var_list
|
||||
});
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
|
||||
async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)
|
||||
{
|
||||
var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
|
||||
var method_token = bp.Location.CliLocation.Method.Token;
|
||||
var il_offset = bp.Location.CliLocation.Offset;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
if (ret_code.HasValue) {
|
||||
bp.RemoteId = ret_code.Value;
|
||||
bp.State = BreakPointState.Active;
|
||||
//Debug ($"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async Task RuntimeReady (CancellationToken token)
|
||||
{
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.GET_LOADED_FILES,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
var loaded_pdbs = await SendCommand ("Runtime.evaluate", o, token);
|
||||
var the_value = loaded_pdbs.Value? ["result"]? ["value"];
|
||||
var the_pdbs = the_value?.ToObject<string[]> ();
|
||||
this.store = new DebugStore (the_pdbs);
|
||||
|
||||
foreach (var s in store.AllSources ()) {
|
||||
var ok = JObject.FromObject (new {
|
||||
scriptId = s.SourceId.ToString (),
|
||||
url = s.Url,
|
||||
executionContextId = this.ctx_id,
|
||||
hash = s.DocHashCode,
|
||||
executionContextAuxData = this.aux_ctx_data,
|
||||
dotNetUrl = s.DotNetUrl
|
||||
});
|
||||
//Debug ($"\tsending {s.Url}");
|
||||
SendEvent ("Debugger.scriptParsed", ok, token);
|
||||
}
|
||||
|
||||
o = JObject.FromObject (new {
|
||||
expression = MonoCommands.CLEAR_ALL_BREAKPOINTS,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var clear_result = await SendCommand ("Runtime.evaluate", o, token);
|
||||
if (clear_result.IsErr) {
|
||||
Debug ($"Failed to clear breakpoints due to {clear_result}");
|
||||
}
|
||||
|
||||
|
||||
runtime_ready = true;
|
||||
|
||||
foreach (var bp in breakpoints) {
|
||||
if (bp.State != BreakPointState.Pending)
|
||||
continue;
|
||||
var res = await EnableBreakPoint (bp, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (!ret_code.HasValue) {
|
||||
//FIXME figure out how to inform the IDE of that.
|
||||
Info ($"FAILED TO ENABLE BP {bp.LocalId}");
|
||||
bp.State = BreakPointState.Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> RemoveBreakpoint(int msg_id, JObject args, CancellationToken token) {
|
||||
var bpid = args? ["breakpointId"]?.Value<string> ();
|
||||
if (bpid?.StartsWith ("dotnet:") != true)
|
||||
return false;
|
||||
|
||||
var the_id = int.Parse (bpid.Substring ("dotnet:".Length));
|
||||
|
||||
var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id);
|
||||
if (bp == null) {
|
||||
Info ($"Could not find dotnet bp with id {the_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
breakpoints.Remove (bp);
|
||||
//FIXME verify result (and log?)
|
||||
var res = await RemoveBreakPoint (bp, token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async Task<Result> RemoveBreakPoint (Breakpoint bp, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand ("Runtime.evaluate", o, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
if (ret_code.HasValue) {
|
||||
bp.RemoteId = -1;
|
||||
bp.State = BreakPointState.Disabled;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken token)
|
||||
{
|
||||
var bp_loc = store.FindBestBreakpoint (req);
|
||||
Info ($"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'");
|
||||
if (bp_loc == null) {
|
||||
|
||||
Info ($"Could not resolve breakpoint request: {req}");
|
||||
SendResponse (msg_id, Result.Err(JObject.FromObject (new {
|
||||
code = (int)MonoErrorCodes.BpNotFound,
|
||||
message = $"C# Breakpoint at {req} not found."
|
||||
})), token);
|
||||
return;
|
||||
}
|
||||
|
||||
Breakpoint bp = null;
|
||||
if (!runtime_ready) {
|
||||
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending);
|
||||
} else {
|
||||
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled);
|
||||
|
||||
var res = await EnableBreakPoint (bp, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (!ret_code.HasValue) {
|
||||
SendResponse (msg_id, res, token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var locations = new List<JObject> ();
|
||||
|
||||
locations.Add (JObject.FromObject (new {
|
||||
scriptId = bp_loc.Id.ToString (),
|
||||
lineNumber = bp_loc.Line,
|
||||
columnNumber = bp_loc.Column
|
||||
}));
|
||||
|
||||
breakpoints.Add (bp);
|
||||
|
||||
var ok = JObject.FromObject (new {
|
||||
breakpointId = $"dotnet:{bp.LocalId}",
|
||||
locations = locations,
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (ok), token);
|
||||
}
|
||||
|
||||
bool GetPossibleBreakpoints (int msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
|
||||
{
|
||||
var bps = store.FindPossibleBreakpoints (start, end);
|
||||
if (bps == null)
|
||||
return false;
|
||||
|
||||
var loc = new List<JObject> ();
|
||||
foreach (var b in bps) {
|
||||
loc.Add (b.ToJObject ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
locations = loc
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnCompileDotnetScript (int msg_id, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new { });
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
|
||||
}
|
||||
|
||||
async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
|
||||
{
|
||||
var id = new SourceId (script_id);
|
||||
var src_file = store.GetFileById (id);
|
||||
|
||||
var res = new StringWriter ();
|
||||
//res.WriteLine ($"//{id}");
|
||||
|
||||
try {
|
||||
var uri = new Uri (src_file.Url);
|
||||
if (uri.IsFile && File.Exists(uri.LocalPath)) {
|
||||
using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
|
||||
await res.WriteAsync (await f.ReadToEndAsync ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
} else if(src_file.SourceLinkUri != null) {
|
||||
var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
|
||||
await res.WriteAsync (doc);
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
} else {
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = $"// Unable to find document {src_file.SourceUri}"
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = $"// Unable to read document ({e.Message})\n" +
|
||||
$"Local path: {src_file?.SourceUri}\n" +
|
||||
$"SourceLink path: {src_file?.SourceLinkUri}\n"
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WsProxy {
|
||||
|
||||
internal struct Result {
|
||||
public JObject Value { get; private set; }
|
||||
public JObject Error { get; private set; }
|
||||
|
||||
public bool IsOk => Value != null;
|
||||
public bool IsErr => Error != null;
|
||||
|
||||
Result (JObject result, JObject error)
|
||||
{
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
}
|
||||
|
||||
public static Result FromJson (JObject obj)
|
||||
{
|
||||
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
|
||||
}
|
||||
|
||||
public static Result Ok (JObject ok)
|
||||
{
|
||||
return new Result (ok, null);
|
||||
}
|
||||
|
||||
public static Result Err (JObject err)
|
||||
{
|
||||
return new Result (null, err);
|
||||
}
|
||||
|
||||
public JObject ToJObject (int id) {
|
||||
if (IsOk) {
|
||||
return JObject.FromObject (new {
|
||||
id = id,
|
||||
result = Value
|
||||
});
|
||||
} else {
|
||||
return JObject.FromObject (new {
|
||||
id = id,
|
||||
error = Error
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WsQueue {
|
||||
Task current_send;
|
||||
List<byte []> pending;
|
||||
|
||||
public WebSocket Ws { get; private set; }
|
||||
public Task CurrentSend { get { return current_send; } }
|
||||
public WsQueue (WebSocket sock)
|
||||
{
|
||||
this.Ws = sock;
|
||||
pending = new List<byte []> ();
|
||||
}
|
||||
|
||||
public Task Send (byte [] bytes, CancellationToken token)
|
||||
{
|
||||
pending.Add (bytes);
|
||||
if (pending.Count == 1) {
|
||||
if (current_send != null)
|
||||
throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send");
|
||||
//Console.WriteLine ("sending {0} bytes", bytes.Length);
|
||||
current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task Pump (CancellationToken token)
|
||||
{
|
||||
current_send = null;
|
||||
pending.RemoveAt (0);
|
||||
|
||||
if (pending.Count > 0) {
|
||||
if (current_send != null)
|
||||
throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send");
|
||||
//Console.WriteLine ("sending more {0} bytes", pending[0].Length);
|
||||
current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class WsProxy {
|
||||
TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
|
||||
List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> ();
|
||||
ClientWebSocket browser;
|
||||
WebSocket ide;
|
||||
int next_cmd_id;
|
||||
List<Task> pending_ops = new List<Task> ();
|
||||
List<WsQueue> queues = new List<WsQueue> ();
|
||||
|
||||
protected virtual Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult (false);
|
||||
}
|
||||
|
||||
protected virtual Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult (false);
|
||||
}
|
||||
|
||||
async Task<string> ReadOne (WebSocket socket, CancellationToken token)
|
||||
{
|
||||
byte [] buff = new byte [4000];
|
||||
var mem = new MemoryStream ();
|
||||
while (true) {
|
||||
var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
|
||||
if (result.MessageType == WebSocketMessageType.Close) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.EndOfMessage) {
|
||||
mem.Write (buff, 0, result.Count);
|
||||
return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length);
|
||||
} else {
|
||||
mem.Write (buff, 0, result.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WsQueue GetQueueForSocket (WebSocket ws)
|
||||
{
|
||||
return queues.FirstOrDefault (q => q.Ws == ws);
|
||||
}
|
||||
|
||||
WsQueue GetQueueForTask (Task task) {
|
||||
return queues.FirstOrDefault (q => q.CurrentSend == task);
|
||||
}
|
||||
|
||||
void Send (WebSocket to, JObject o, CancellationToken token)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
|
||||
|
||||
var queue = GetQueueForSocket (to);
|
||||
var task = queue.Send (bytes, token);
|
||||
if (task != null)
|
||||
pending_ops.Add (task);
|
||||
}
|
||||
|
||||
async Task OnEvent (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
try {
|
||||
if (!await AcceptEvent (method, args, token)) {
|
||||
//Console.WriteLine ("proxy browser: {0}::{1}",method, args);
|
||||
SendEventInternal (method, args, token);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
side_exception.TrySetException (e);
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnCommand (int id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
try {
|
||||
if (!await AcceptCommand (id, method, args, token)) {
|
||||
var res = await SendCommandInternal (method, args, token);
|
||||
SendResponseInternal (id, res, token);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
side_exception.TrySetException (e);
|
||||
}
|
||||
}
|
||||
|
||||
void OnResponse (int id, Result result)
|
||||
{
|
||||
//Console.WriteLine ("got id {0} res {1}", id, result);
|
||||
var idx = pending_cmds.FindIndex (e => e.Item1 == id);
|
||||
var item = pending_cmds [idx];
|
||||
pending_cmds.RemoveAt (idx);
|
||||
|
||||
item.Item2.SetResult (result);
|
||||
}
|
||||
|
||||
void ProcessBrowserMessage (string msg, CancellationToken token)
|
||||
{
|
||||
// Debug ($"browser: {msg}");
|
||||
var res = JObject.Parse (msg);
|
||||
|
||||
if (res ["id"] == null)
|
||||
pending_ops.Add (OnEvent (res ["method"].Value<string> (), res ["params"] as JObject, token));
|
||||
else
|
||||
OnResponse (res ["id"].Value<int> (), Result.FromJson (res));
|
||||
}
|
||||
|
||||
void ProcessIdeMessage (string msg, CancellationToken token)
|
||||
{
|
||||
var res = JObject.Parse (msg);
|
||||
|
||||
pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token));
|
||||
}
|
||||
|
||||
internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) {
|
||||
// Debug ($"sending command {method}: {args}");
|
||||
return await SendCommandInternal (method, args, token);
|
||||
}
|
||||
|
||||
Task<Result> SendCommandInternal (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
int id = ++next_cmd_id;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
id = id,
|
||||
method = method,
|
||||
@params = args
|
||||
});
|
||||
var tcs = new TaskCompletionSource<Result> ();
|
||||
//Console.WriteLine ("add cmd id {0}", id);
|
||||
pending_cmds.Add ((id, tcs));
|
||||
|
||||
Send (this.browser, o, token);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void SendEvent (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
//Debug ($"sending event {method}: {args}");
|
||||
SendEventInternal (method, args, token);
|
||||
}
|
||||
|
||||
void SendEventInternal (string method, JObject args, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new {
|
||||
method = method,
|
||||
@params = args
|
||||
});
|
||||
|
||||
Send (this.ide, o, token);
|
||||
}
|
||||
|
||||
internal void SendResponse (int id, Result result, CancellationToken token)
|
||||
{
|
||||
//Debug ($"sending response: {id}: {result.ToJObject (id)}");
|
||||
SendResponseInternal (id, result, token);
|
||||
}
|
||||
|
||||
void SendResponseInternal (int id, Result result, CancellationToken token)
|
||||
{
|
||||
JObject o = result.ToJObject (id);
|
||||
|
||||
Send (this.ide, o, token);
|
||||
}
|
||||
|
||||
// , HttpContext context)
|
||||
public async Task Run (Uri browserUri, WebSocket ideSocket)
|
||||
{
|
||||
Debug ("wsproxy start");
|
||||
using (this.ide = ideSocket) {
|
||||
Debug ("ide connected");
|
||||
queues.Add (new WsQueue (this.ide));
|
||||
using (this.browser = new ClientWebSocket ()) {
|
||||
this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
|
||||
await this.browser.ConnectAsync (browserUri, CancellationToken.None);
|
||||
queues.Add (new WsQueue (this.browser));
|
||||
|
||||
Debug ("client connected");
|
||||
var x = new CancellationTokenSource ();
|
||||
|
||||
pending_ops.Add (ReadOne (browser, x.Token));
|
||||
pending_ops.Add (ReadOne (ide, x.Token));
|
||||
pending_ops.Add (side_exception.Task);
|
||||
|
||||
try {
|
||||
while (!x.IsCancellationRequested) {
|
||||
var task = await Task.WhenAny (pending_ops.ToArray ());
|
||||
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
||||
if (task == pending_ops [0]) {
|
||||
var msg = ((Task<string>)task).Result;
|
||||
pending_ops [0] = ReadOne (browser, x.Token); //queue next read
|
||||
ProcessBrowserMessage (msg, x.Token);
|
||||
} else if (task == pending_ops [1]) {
|
||||
var msg = ((Task<string>)task).Result;
|
||||
pending_ops [1] = ReadOne (ide, x.Token); //queue next read
|
||||
ProcessIdeMessage (msg, x.Token);
|
||||
} else if (task == pending_ops [2]) {
|
||||
var res = ((Task<bool>)task).Result;
|
||||
throw new Exception ("side task must always complete with an exception, what's going on???");
|
||||
} else {
|
||||
//must be a background task
|
||||
pending_ops.Remove (task);
|
||||
var queue = GetQueueForTask (task);
|
||||
if (queue != null) {
|
||||
var tsk = queue.Pump (x.Token);
|
||||
if (tsk != null)
|
||||
pending_ops.Add (tsk);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Debug ($"got exception {e}");
|
||||
//throw;
|
||||
} finally {
|
||||
x.Cancel ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void Debug (string msg)
|
||||
{
|
||||
Console.WriteLine (msg);
|
||||
}
|
||||
|
||||
protected void Info (string msg)
|
||||
{
|
||||
Console.WriteLine (msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# We only track the .template.config.src items in source control
|
||||
# The .template.config files are generated on build
|
||||
src/content/**/.template.config/
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<!-- Used only in development when running the template contents directly from source -->
|
||||
<TemplateBlazorPackageVersion>0.8.0-preview-19064-0339</TemplateBlazorPackageVersion>
|
||||
<TemplateComponentsPackageVersion>3.0.0-preview-19064-0339</TemplateComponentsPackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||
|
||||
<Target Name="SetTemplateJsonSymbolReplacements">
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Properties here will be injected into the template config *.json files
|
||||
during the build, replacing tokens of the form ${PropertyName}
|
||||
-->
|
||||
<GeneratedContentProperties>
|
||||
TemplateBlazorVersion=$(PackageVersion);
|
||||
TemplateComponentsVersion=$(ComponentsPackageVersion);
|
||||
RepositoryCommit=$(SourceRevisionId);
|
||||
</GeneratedContentProperties>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NuspecFile>Microsoft.AspNetCore.Blazor.Templates.nuspec</NuspecFile>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<EnableDefaultItems>False</EnableDefaultItems>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<IncludeBuildOutput>False</IncludeBuildOutput>
|
||||
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
|
||||
<DebugType>none</DebugType>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);2008</NoWarn>
|
||||
<Description>Templates for ASP.NET Core Blazor projects.</Description>
|
||||
<PackageTags>aspnet;templates;blazor;spa</PackageTags>
|
||||
<IsProjectReferenceProvider>false</IsProjectReferenceProvider>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Include="content\**\.template.config.src\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PrepareFileLists" AfterTargets="PrepareForBuild">
|
||||
<ItemGroup>
|
||||
<_TemplateConfigMainFile Include="content\**\.template.config.src\template.json" />
|
||||
<_TemplateConfigDir Include="@(_TemplateConfigMainFile->'$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigMainFile.FullPath)'))')" />
|
||||
<_TemplateConfigFileToCopy Include="%(_TemplateConfigDir.Identity)\**\*.*">
|
||||
<DestDir>$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigDir.Identity)'))\.template.config\</DestDir>
|
||||
</_TemplateConfigFileToCopy>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="TransformTemplateConfigs"
|
||||
BeforeTargets="CoreBuild"
|
||||
DependsOnTargets="SetTemplateJsonSymbolReplacements"
|
||||
Inputs="@(_TemplateConfigFileToCopy)"
|
||||
Outputs="@(_TemplateConfigFileToCopy->'%(DestDir)%(FileName)%(Extension)')">
|
||||
|
||||
<!--
|
||||
For each template, copy its '.template.config.src' directory to '.template.config',
|
||||
removing any earlier output at that location
|
||||
-->
|
||||
<RemoveDir Directories="%(_TemplateConfigFileToCopy.DestDir)" />
|
||||
<Copy SourceFiles="%(_TemplateConfigFileToCopy.Identity)" DestinationFolder="%(_TemplateConfigFileToCopy.DestDir)" />
|
||||
|
||||
<!--
|
||||
Modify the .json files in the .template.config dirs to stamp MSBuild properties into them
|
||||
-->
|
||||
<ItemGroup>
|
||||
<GeneratedContent Include="@(_TemplateConfigFileToCopy->WithMetadataValue('Extension','.json'))">
|
||||
<OutputPath>%(DestDir)%(RecursiveDir)%(Filename)%(Extension)</OutputPath>
|
||||
<Properties>$(GeneratedContentProperties)</Properties>
|
||||
</GeneratedContent>
|
||||
</ItemGroup>
|
||||
<GenerateFileFromTemplate
|
||||
TemplateFile="%(GeneratedContent.Identity)"
|
||||
Properties="%(GeneratedContent.Properties)"
|
||||
OutputPath="%(GeneratedContent.OutputPath)">
|
||||
|
||||
<Output TaskParameter="OutputPath" ItemName="FileWrites" />
|
||||
<Output TaskParameter="OutputPath" ItemName="Content" />
|
||||
</GenerateFileFromTemplate>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
$CommonMetadataElements$
|
||||
<packageTypes>
|
||||
<packageType name="Template" />
|
||||
</packageTypes>
|
||||
</metadata>
|
||||
<files>
|
||||
$CommonFileElements$
|
||||
<file
|
||||
src="content/**"
|
||||
exclude="**/bin/**;**/obj/**;**/.template.config.src/**;content/Directory.Build.props;content/Directory.Build.targets;"
|
||||
target="Content" />
|
||||
</files>
|
||||
</package>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/dotnetcli.host",
|
||||
"symbolInfo": {
|
||||
"skipRestore": {
|
||||
"longName": "no-restore",
|
||||
"shortName": ""
|
||||
},
|
||||
"Hosted": {
|
||||
"longName": "hosted"
|
||||
},
|
||||
"Framework": {
|
||||
"longName": "framework"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
{
|
||||
"author": "Microsoft",
|
||||
"classifications": [
|
||||
"Web",
|
||||
"Blazor",
|
||||
"WebAssembly"
|
||||
],
|
||||
"defaultName": "WebApplication",
|
||||
"description": "A project template for creating a Blazor app that runs on WebAssembly and is optionally hosted by an ASP.NET Core app. This template can be used for web apps with rich dynamic user interfaces (UIs).",
|
||||
"groupIdentity": "Microsoft.Web.Blazor.Wasm",
|
||||
"guids": [
|
||||
"4C26868E-5E7C-458D-82E3-040509D0C71F",
|
||||
"5990939C-7E7B-4CFA-86FF-44CA5756498A",
|
||||
"650B3CE7-2E93-4CC4-9F46-466686815EAA",
|
||||
"0AFFA7FD-4E37-4636-AB91-3753E746DB98"
|
||||
],
|
||||
"identity": "Microsoft.Web.Blazor.Wasm.CSharp",
|
||||
"name": "Blazor WebAssembly App",
|
||||
"preferNameDirectory": true,
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"condition": "(Hosted && (HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\"))",
|
||||
"path": "BlazorWasm-CSharp.sln"
|
||||
},
|
||||
{
|
||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
||||
"path": "Server/BlazorWasm-CSharp.Server.csproj"
|
||||
},
|
||||
{
|
||||
"condition": "(!Hosted)",
|
||||
"path": "BlazorWasm-CSharp.Client.csproj"
|
||||
},
|
||||
{
|
||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
||||
"path": "Client/BlazorWasm-CSharp.Client.csproj"
|
||||
},
|
||||
{
|
||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
||||
"path": "Shared/BlazorWasm-CSharp.Shared.csproj"
|
||||
}
|
||||
],
|
||||
"shortName": "blazorwasm",
|
||||
"sourceName": "BlazorWasm-CSharp",
|
||||
"sources": [
|
||||
{
|
||||
"source": "./",
|
||||
"target": "./",
|
||||
"exclude": [
|
||||
".template.config/**"
|
||||
],
|
||||
"modifiers": [
|
||||
{
|
||||
"condition": "(!Hosted)",
|
||||
"exclude": [
|
||||
"Server/**",
|
||||
"Shared/**",
|
||||
"*.sln"
|
||||
],
|
||||
"rename": {
|
||||
".Client.csproj": ".csproj",
|
||||
"Client": "."
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "(Hosted)",
|
||||
"exclude": [
|
||||
"Client/wwwroot/sample-data/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")",
|
||||
"exclude": [
|
||||
"*.sln"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"type": "parameter",
|
||||
"description": "The target framework for the project.",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "netcoreapp3.1",
|
||||
"description": "Target netcoreapp3.1"
|
||||
}
|
||||
],
|
||||
"replaces": "netcoreapp3.1",
|
||||
"defaultValue": "netcoreapp3.1"
|
||||
},
|
||||
"HostIdentifier": {
|
||||
"type": "bind",
|
||||
"binding": "HostIdentifier"
|
||||
},
|
||||
"TemplateBlazorVersionSymbol": {
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"description": "Specifies which version of Blazor packages to use.",
|
||||
"replaces": "$(TemplateBlazorPackageVersion)",
|
||||
"defaultValue": "${TemplateBlazorVersion}"
|
||||
},
|
||||
"TemplateComponentsVersionSymbol": {
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"description": "Specifies which version of Components packages to use.",
|
||||
"replaces": "$(TemplateComponentsPackageVersion)",
|
||||
"defaultValue": "${TemplateComponentsVersion}"
|
||||
},
|
||||
"skipRestore": {
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"Hosted": {
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false",
|
||||
"description": "If specified, includes an ASP.NET Core host for the Blazor app."
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"postActions": [
|
||||
{
|
||||
"condition": "(!skipRestore && Hosted)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
},
|
||||
{
|
||||
"condition": "(!skipRestore && !Hosted)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"args": {
|
||||
"files": [ "BlazorWasm-CSharp.Client.csproj" ]
|
||||
},
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(TemplateBlazorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="$(TemplateBlazorPackageVersion)" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="$(TemplateBlazorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="$(TemplateBlazorPackageVersion)" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<!--#if Hosted -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<!--#endif -->
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
using Microsoft.AspNetCore.Blazor.Hosting;
|
||||
|
||||
#if (Hosted)
|
||||
namespace BlazorWasm_CSharp.Client
|
||||
#else
|
||||
namespace BlazorWasm_CSharp
|
||||
#endif
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
|
||||
BlazorWebAssemblyHost.CreateDefaultBuilder()
|
||||
.UseBlazorStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
#if (Hosted)
|
||||
namespace BlazorWasm_CSharp.Client
|
||||
#else
|
||||
namespace BlazorWasm_CSharp
|
||||
#endif
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app)
|
||||
{
|
||||
app.AddComponent<App>("app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.JSInterop
|
||||
@*#if (!Hosted)
|
||||
@using BlazorWasm_CSharp
|
||||
@using BlazorWasm_CSharp.Shared
|
||||
#else
|
||||
@using BlazorWasm_CSharp.Client
|
||||
@using BlazorWasm_CSharp.Client.Shared
|
||||
#endif*@
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="$(TemplateBlazorPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Client\BlazorWasm-CSharp.Client.csproj" />
|
||||
<ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BlazorWasm_CSharp.Server
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.Build())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue