Merge blazor-wasm in to master

This commit is contained in:
Pranav K 2020-05-27 11:00:38 -07:00
commit 4c50b6cb16
No known key found for this signature in database
GPG Key ID: F748807460A27E91
621 changed files with 33472 additions and 13989 deletions

View File

@ -0,0 +1,59 @@
# Uses Scheduled Triggers, which aren't supported in YAML yet.
# https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=vsts&tabs=yaml#scheduled
# Daily Tests for Blazor
# These use Sauce Labs resources, hence they run daily rather than per-commit.
# We just need one Windows machine because all it does is trigger SauceLabs.
variables:
SAUCE_CONNECT_DOWNLOAD_ON_INSTALL: true
E2ETESTS_SauceTest: true
E2ETESTS_Sauce__TunnelIdentifier: 'blazor-e2e-sc-proxy-tunnel'
E2ETESTS_Sauce__HostName: 'sauce.local'
jobs:
- template: jobs/default-build.yml
parameters:
buildDirectory: src/Components
isTestingJob: true
agentOs: Windows
jobName: BlazorDailyTests
jobDisplayName: "Blazor Daily Tests"
afterBuild:
# macOS/Safari
- script: 'dotnet test --filter "StandaloneAppTest"'
workingDirectory: 'src/Components/test/E2ETest'
displayName: 'Run Blazor tests - macOS/Safari'
condition: succeededOrFailed()
env:
# Secrets need to be explicitly mapped to env variables.
E2ETESTS_Sauce__Username: '$(asplab-sauce-labs-username)'
E2ETESTS_Sauce__AccessKey: '$(asplab-sauce-labs-access-key)'
# Set platform/browser configuration.
E2ETESTS_Sauce__TestName: 'Blazor Daily Tests - macOS/Safari'
E2ETESTS_Sauce__PlatformName: 'macOS 10.14'
E2ETESTS_Sauce__BrowserName: 'Safari'
# Need to explicitly set version here because some older versions don't support timeouts in Safari.
E2ETESTS_Sauce__SeleniumVersion: '3.4.0'
# Android/Chrome
- script: 'dotnet test --filter "StandaloneAppTest"'
workingDirectory: 'src/Components/test/E2ETest'
displayName: 'Run Blazor tests - Android/Chrome'
condition: succeededOrFailed()
env:
# Secrets need to be explicitly mapped to env variables.
E2ETESTS_Sauce__Username: '$(asplab-sauce-labs-username)'
E2ETESTS_Sauce__AccessKey: '$(asplab-sauce-labs-access-key)'
# Set platform/browser configuration.
E2ETESTS_Sauce__TestName: 'Blazor Daily Tests - Android/Chrome'
E2ETESTS_Sauce__PlatformName: 'Android'
E2ETESTS_Sauce__PlatformVersion: '10.0'
E2ETESTS_Sauce__BrowserName: 'Chrome'
E2ETESTS_Sauce__DeviceName: 'Android GoogleAPI Emulator'
E2ETESTS_Sauce__DeviceOrientation: 'portrait'
E2ETESTS_Sauce__AppiumVersion: '1.9.1'
artifacts:
- name: Windows_Logs
path: ../../artifacts/log/
publishOnError: true

View File

@ -18,6 +18,13 @@
<PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
</PropertyGroup>
<!-- Package: Microsoft.Authentication.WebAssembly.Msal-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="[3.2.0, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.ApiAuthorization.IdentityServer-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>
@ -240,6 +247,49 @@
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" />
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.JSInterop.WebAssembly" Version="[3.2.0, )" />
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="[3.2.0, )" />
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.Extensions.Configuration.Json" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Build-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' AND '$(TargetFramework)' == 'any' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="[3.2.0, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Server-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' AND '$(TargetFramework)' == 'netcoreapp3.1' " />
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Authentication-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="[3.1.3, )" />
<BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.Components.WebAssembly.HttpHandler-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' AND '$(TargetFramework)' == 'netstandard2.1' " />
<!-- Package: Microsoft.JSInterop.WebAssembly-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' ">
<BaselinePackageVersion>3.2.0</BaselinePackageVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' ">
<BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.3, )" />
</ItemGroup>
<!-- Package: Microsoft.AspNetCore.ConcurrencyLimiter-->
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' ">
<BaselinePackageVersion>3.1.4</BaselinePackageVersion>

View File

@ -8,6 +8,7 @@ Update this list when preparing for a new patch.
<Package Id="AspNetCoreRuntime.3.0.x64" Version="3.0.3" />
<Package Id="AspNetCoreRuntime.3.0.x86" Version="3.0.3" />
<Package Id="dotnet-sql-cache" Version="3.1.4" />
<Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.4" />
@ -36,6 +37,12 @@ Update this list when preparing for a new patch.
<Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="3.2.0" />
<Package Id="Microsoft.JSInterop.WebAssembly" Version="3.2.0" />
<Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" />

View File

@ -25,8 +25,8 @@
$(RepoRoot)src\Installers\**\*.*proj;
$(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj;
$(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj;
$(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj;
$(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj;
$(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj;

View File

@ -73,6 +73,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" />
<LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
<LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
<LatestPackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonPackageVersion)" />
<LatestPackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
<LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
<LatestPackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="$(SystemRuntimeInteropServicesRuntimeInformationPackageVersion)" />
@ -106,7 +107,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.3.1.x64" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.3.1.x86" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" />
<LatestPackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="$(MicrosoftBclAsyncInterfacesPackageVersion)" />
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />

View File

@ -62,13 +62,14 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
<ProjectReferenceProvider Include="Mono.WebAssembly.Interop" ProjectPath="$(RepoRoot)src\Components\Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" />
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Build" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" />
<ProjectReferenceProvider Include="blazor-brotli" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" />
<ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Testing" ProjectPath="$(RepoRoot)src\Testing\src\Microsoft.AspNetCore.Testing.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Abstractions" ProjectPath="$(RepoRoot)src\DataProtection\Abstractions\src\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Abstractions\ref\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" />

View File

@ -9,9 +9,13 @@
-->
<Dependencies>
<ProductDependencies>
<Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.2.0-preview1.20067.1">
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0">
<Uri>https://github.com/dotnet/blazor</Uri>
<Sha>dd7fb4d3931d556458f62642c2edfc59f6295bfb</Sha>
<Sha>cc449601d638ffaab58ae9487f0fd010bb178a12</Sha>
</Dependency>
<Dependency Name="System.Net.Http.Json" Version="3.2.0">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha>
</Dependency>
<Dependency Name="dotnet-ef" Version="5.0.0-preview.6.20276.2">
<Uri>https://github.com/dotnet/efcore</Uri>

View File

@ -30,6 +30,7 @@
<!-- Servicing builds have different characteristics for the way dependencies, baselines, and versions are handled. -->
<IsServicingBuild Condition=" '$(DisableServicingFeatures)' != 'true' AND '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild>
<VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix>
<ComponentsWebAssemblyVersionPrefix>$(ComponentsWebAssemblyMajorVersion).$(ComponentsWebAssemblyMinorVersion).$(ComponentsWebAssemblyPatchVersion)</ComponentsWebAssemblyVersionPrefix>
<!-- TargetingPackVersionPrefix is used by projects, like .deb and .rpm, which use slightly different version formats. -->
<TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix>
<!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. -->
@ -128,7 +129,7 @@
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
<MicrosoftNETCorePlatformsPackageVersion>5.0.0-preview.6.20271.10</MicrosoftNETCorePlatformsPackageVersion>
<!-- Packages from dotnet/blazor -->
<MicrosoftAspNetCoreBlazorMonoPackageVersion>3.2.0-preview1.20067.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
<!-- Packages from dotnet/efcore -->
<dotnetefPackageVersion>5.0.0-preview.6.20276.2</dotnetefPackageVersion>
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>5.0.0-preview.6.20276.2</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>

View File

@ -297,7 +297,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
if ($msbuildCmd -ne $null) {
# Workaround for https://github.com/dotnet/roslyn/issues/35793
# Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0])
$msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0])
if ($msbuildVersion -ge $vsMinVersion) {
return $global:_MSBuildExe = $msbuildCmd.Path

View File

@ -1,2 +0,0 @@
<Project>
</Project>

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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>());
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
// Equivalent to https://github.com/dotnet/extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs
internal interface IWebAssemblyServiceFactoryAdapter
{
object CreateBuilder(IServiceCollection services);
IServiceProvider CreateServiceProvider(object containerBuilder);
}
}

View File

@ -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;
}
}
}

View File

@ -1,98 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Rendering;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
internal class WebAssemblyHost : IWebAssemblyHost
{
private readonly IJSRuntime _runtime;
private IServiceScope _scope;
private WebAssemblyRenderer _renderer;
public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime)
{
// To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types
GC.KeepAlive(typeof(EntrypointInvoker));
GC.KeepAlive(typeof(JSInteropMethods));
GC.KeepAlive(typeof(WebAssemblyEventDispatcher));
Services = services ?? throw new ArgumentNullException(nameof(services));
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken = default)
{
return StartAsyncAwaited();
}
private async Task StartAsyncAwaited()
{
var scopeFactory = Services.GetRequiredService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
try
{
var startup = _scope.ServiceProvider.GetService<IBlazorStartup>();
if (startup == null)
{
var message =
$"Could not find a registered Blazor Startup class. " +
$"Using {nameof(IWebAssemblyHost)} requires a call to {nameof(IWebAssemblyHostBuilder)}.UseBlazorStartup.";
throw new InvalidOperationException(message);
}
// Note that we differ from the WebHost startup path here by using a 'scope' for the app builder
// as well as the Configure method.
var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider);
startup.Configure(builder, _scope.ServiceProvider);
_renderer = await builder.CreateRendererAsync();
}
catch
{
_scope.Dispose();
_scope = null;
if (_renderer != null)
{
_renderer.Dispose();
_renderer = null;
}
throw;
}
}
public Task StopAsync(CancellationToken cancellationToken = default)
{
if (_scope != null)
{
_scope.Dispose();
_scope = null;
}
if (_renderer != null)
{
_renderer.Dispose();
_renderer = null;
}
return Task.CompletedTask;
}
public void Dispose()
{
(Services as IDisposable)?.Dispose();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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));
}
}
}

View File

@ -1,36 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
/// <summary>
/// Extension methods for <see cref="IWebAssemblyHost"/>.
/// </summary>
public static class WebAssemblyHostExtensions
{
/// <summary>
/// Runs the application.
/// </summary>
/// <param name="host">The <see cref="IWebAssemblyHost"/> to run.</param>
/// <remarks>
/// Currently, Blazor applications running in the browser don't have a lifecycle - the application does not
/// get a chance to gracefully shut down. For now, <see cref="Run(IWebAssemblyHost)"/> simply starts the host
/// and allows execution to continue.
/// </remarks>
public static void Run(this IWebAssemblyHost host)
{
// Behave like async void, because we don't yet support async-main properly on WebAssembly.
// However, don't actually make this method async, because we rely on startup being synchronous
// for things like attaching navigation event handlers.
host.StartAsync().ContinueWith(task =>
{
if (task.Exception != null)
{
Console.WriteLine(task.Exception);
}
});
}
}
}

View File

@ -1,52 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
// Equivalent to https://github.com/dotnet/extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs
internal class WebAssemblyServiceFactoryAdapter<TContainerBuilder> : IWebAssemblyServiceFactoryAdapter
{
private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
private readonly Func<WebAssemblyHostBuilderContext> _contextResolver;
private Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;
public WebAssemblyServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
{
_serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory));
}
public WebAssemblyServiceFactoryAdapter(Func<WebAssemblyHostBuilderContext> contextResolver, Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
{
_contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver));
_factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver));
}
public object CreateBuilder(IServiceCollection services)
{
if (_serviceProviderFactory == null)
{
_serviceProviderFactory = _factoryResolver(_contextResolver());
if (_serviceProviderFactory == null)
{
throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
}
}
return _serviceProviderFactory.CreateBuilder(services);
}
public IServiceProvider CreateServiceProvider(object containerBuilder)
{
if (_serviceProviderFactory == null)
{
throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
}
return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
}
}
}

View File

@ -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));
});
}
}

View File

@ -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);
}
}
}

View File

@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description>
<IsShippingPackage>false</IsShippingPackage>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.WebAssembly.Interop" />
<Reference Include="Microsoft.AspNetCore.Components.Web" />
<Reference Include="Microsoft.Extensions.Options" />
<!--
Supress a "BUILD001: Reference to 'Microsoft.Extensions.Configuration' was removed since the last stable release
of this package." warning.
-->
<SuppressBaselineReference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" />
</ItemGroup>
</Project>

View File

@ -1,39 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Blazor.Services
{
internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger
{
public IDisposable BeginScope<TState>(TState state)
{
return NoOpDisposable.Instance;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= LogLevel.Warning;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
var formattedMessage = formatter(state, exception);
Console.WriteLine($"[{logLevel}] {formattedMessage}");
}
private class NoOpDisposable : IDisposable
{
public static NoOpDisposable Instance = new NoOpDisposable();
public void Dispose() { }
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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)
{
}
}
}
}
}

View File

@ -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)
{
}
}
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
<!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. -->
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. -->
<Reference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@ -1,3 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1,86 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class GenerateBlazorBootJson : Task
{
[Required]
public string AssemblyPath { get; set; }
[Required]
public ITaskItem[] References { get; set; }
[Required]
public bool LinkerEnabled { get; set; }
[Required]
public string OutputPath { get; set; }
public override bool Execute()
{
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
using var fileStream = File.Create(OutputPath);
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
return true;
static string GetUriPath(ITaskItem item)
{
var outputPath = item.GetMetadata("RelativeOutputPath");
if (string.IsNullOrEmpty(outputPath))
{
outputPath = Path.GetFileName(item.ItemSpec);
}
return outputPath.Replace('\\', '/');
}
}
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
{
var data = new BootJsonData
{
entryAssembly = entryAssemblyName,
assemblies = assemblies,
linkerEnabled = linkerEnabled,
};
var serializer = new DataContractJsonSerializer(typeof(BootJsonData));
serializer.WriteObject(stream, data);
}
/// <summary>
/// Defines the structure of a Blazor boot JSON file
/// </summary>
#pragma warning disable IDE1006 // Naming Styles
public class BootJsonData
{
/// <summary>
/// Gets the name of the assembly with the application entry point
/// </summary>
public string entryAssembly { get; set; }
/// <summary>
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
/// </summary>
public string[] assemblies { get; set; }
/// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool linkerEnabled { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
}

View File

@ -1,3 +0,0 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" />
</Project>

View File

@ -1,49 +0,0 @@
<Project>
<!-- Require rebuild if the targets change -->
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<PropertyGroup>
<BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir>
<_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM>
<_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM>
<BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath>
<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- By default, enable debugging for debug builds. -->
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
</PropertyGroup>
<Import Project="Blazor.MonoRuntime.targets" />
<Import Project="Publish.targets" />
<Import Project="StaticWebAssets.targets" />
<Target Name="GenerateBlazorMetadataFile"
BeforeTargets="GetCopyToOutputDirectoryItems">
<PropertyGroup>
<BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName>
<BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath>
</PropertyGroup>
<ItemGroup>
<_BlazorConfigContent Include="$(MSBuildProjectFullPath)" />
<_BlazorConfigContent Include="$(TargetPath)" />
<_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" />
</ItemGroup>
<WriteLinesToFile
File="$(BlazorMetadataFilePath)"
Lines="@(_BlazorConfigContent)"
Overwrite="true"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,20 +0,0 @@
<Project>
<PropertyGroup>
<BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath>
</PropertyGroup>
<PropertyGroup Label="Blazor build outputs">
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
<BaseBlazorDistPath>dist\</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath>
<BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath>
<BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath>
<BlazorWebRootName>wwwroot\</BlazorWebRootName>
<BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName>
<_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor>
</PropertyGroup>
</Project>

View File

@ -1,336 +0,0 @@
<Project>
<PropertyGroup>
<BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild>
</PropertyGroup>
<PropertyGroup>
<!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention -->
<DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''">
<!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds -->
<DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath>
<DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath>
<DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath>
<DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath>
</PropertyGroup>
<Target
Name="_BlazorCopyFilesToOutputDirectory"
DependsOnTargets="PrepareBlazorOutputs"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
<!-- Copy the blazor output files -->
<Copy
SourceFiles="@(BlazorOutputWithTargetPath)"
DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'">
</Copy>
<ItemGroup>
<FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" />
</ItemGroup>
<ItemGroup>
<_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" />
</ItemGroup>
<Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" />
</Target>
<Target
Name="PrepareBlazorOutputs"
DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson">
<ItemGroup>
<MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" />
<BlazorJSFile Include="$(BlazorJSPath)" />
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
<ItemGroup Label="Static content supplied by NuGet packages">
<_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''">
<TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath>
</_BlazorPackageContentOutput>
<BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" />
</ItemGroup>
</Target>
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
<PropertyGroup>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
<_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json -->
<BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath>
<_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache>
<_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile>
</PropertyGroup>
<ItemGroup>
<_WebAssemblyBCLFolder Include="
$(DotNetWebAssemblyBCLPath);
$(DotNetWebAssemblyBCLFacadesPath);
$(DotNetWebAssemblyFrameworkPath)" />
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
</ItemGroup>
<!--
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
-->
<ItemGroup>
<!-- Assemblies from packages -->
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
<!-- Assemblies from other references -->
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
</ItemGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
<Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked">
<Error
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
<ItemGroup>
<!--
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
-->
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<!--
Linker enabled part of the pipeline:
* If there are no descriptors defined, generate a new linker descriptor.
* Invoke the linker and write linked files to a well-known directory.
* Collect the outputs of the linker.
-->
<Target
Name="_ResolveBlazorOutputsWhenLinked"
Condition="'$(BlazorLinkOnBuild)' == 'true'"
DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication">
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
</Target>
<Target Name="_PrepareBlazorLinkerInputs">
<ItemGroup>
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
<!--
Any assembly from a package reference that starts with System. file name is allowed to be linked.
Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity.
-->
<_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
<Target Name="_GenerateBlazorLinkerDescriptor"
Inputs="@(IntermediateAssembly)"
Outputs="$(GeneratedBlazorLinkerDescriptor)"
Condition="'@(BlazorLinkerDescriptor)' == ''">
<!-- Generate linker descriptors if the project doesn't explicitly provide one. -->
<BlazorCreateRootDescriptorFile
AssemblyNames="@(IntermediateAssembly->'%(Filename)')"
RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" />
<ItemGroup>
<FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" />
<BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" />
<Target Name="_GenerateTypeGranularLinkerDescriptor"
Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
Outputs="$(_TypeGranularityLinkerDescriptor)">
<GenerateTypeGranularityLinkingConfig
Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
OutputPath="$(_TypeGranularityLinkerDescriptor)" />
<ItemGroup>
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" />
<FileWrites Include="$(_TypeGranularityLinkerDescriptor)" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_LinkBlazorApplication"
Inputs="$(ProjectAssetsFile);
@(_BlazorManagedRuntimeAssemby);
@(BlazorLinkerDescriptor);
$(MSBuildAllProjects)"
Outputs="$(_BlazorLinkerOutputCache)">
<PropertyGroup>
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
</PropertyGroup>
<ItemGroup>
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
</ItemGroup>
<Delete Files="@(_OldLinkedFile)" />
<!--
When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
In this case, explicitly specify the path to the dotnet host.
-->
<PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' ">
<_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
<_DotNetHostFileName>dotnet</_DotNetHostFileName>
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
</PropertyGroup>
<BlazorILLink
ILLinkPath="$(MonoLinkerPath)"
AssemblyPaths="@(_BlazorAssemblyToLink)"
RootAssemblyNames="@(_BlazorLinkerRoot)"
RootDescriptorFiles="@(BlazorLinkerDescriptor)"
OutputDirectory="$(BlazorIntermediateLinkerOutputPath)"
ExtraArgs="$(_BlazorLinkerAdditionalOptions)"
ToolExe="$(_DotNetHostFileName)"
ToolPath="$(_DotNetHostDirectory)" />
<ItemGroup>
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" />
</ItemGroup>
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
</Target>
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_ResolveBlazorOutputsWhenNotLinked"
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
Condition="'$(BlazorLinkOnBuild)' != 'true'">
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
</Target>
<Target
Name="_ResolveBlazorRuntimeDependencies"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorManagedRuntimeAssemby)"
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
<!--
At this point we have decided not to run the linker and instead to just copy the assemblies
from the BCL referenced by the app the nuget package into the _framework/_bin folder.
The only thing we need to do here is collect the list of items that will go into _framework/_bin.
-->
<ResolveBlazorRuntimeDependencies
EntryPoint="@(IntermediateAssembly)"
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
</ResolveBlazorRuntimeDependencies>
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
<ItemGroup>
<FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" />
</ItemGroup>
</Target>
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_GenerateBlazorBootJson"
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
<ItemGroup>
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
</ItemGroup>
<GenerateBlazorBootJson
AssemblyPath="@(IntermediateAssembly)"
References="@(_BlazorRuntimeFile)"
LinkerEnabled="$(BlazorLinkOnBuild)"
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />
<ItemGroup>
<BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" />
<FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,70 +0,0 @@
<Project>
<PropertyGroup>
<BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild>
<BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir>
<!-- Disable unwanted parts of the default publish process -->
<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
<PreserveCompilationContext>false</PreserveCompilationContext>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
<GenerateDependencyFile>false</GenerateDependencyFile>
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
</PropertyGroup>
<Target Name="BlazorGetCopyToPublishDirectoryItems"
BeforeTargets="GetCopyToPublishDirectoryItems"
DependsOnTargets="PrepareBlazorOutputs"
Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
<ItemGroup>
<!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. -->
<ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" />
<!-- Move wwwroot files to output root -->
<ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))">
<TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath>
</ContentWithTargetPath>
<!-- Publish all the 'dist' files -->
<_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)">
<TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath>
</_BlazorGCTPDI>
<ContentWithTargetPath Include="@(_BlazorGCTPDI)">
<TargetPath>%(TargetPath)</TargetPath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ContentWithTargetPath>
</ItemGroup>
<!-- Replace the .blazor.config contents with what we need to serve in production -->
<PropertyGroup>
<_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath>
</PropertyGroup>
<ItemGroup>
<_BlazorPublishConfigContent Include="." />
<_BlazorPublishConfigContent Include="$(AssemblyName)/" />
</ItemGroup>
<WriteLinesToFile
File="$(_BlazorConfigPath)"
Lines="@(_BlazorPublishConfigContent)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
</Target>
<!-- The following target runs only for standalone publishing -->
<Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory">
<!-- Add a suitable web.config file if there isn't one already -->
<ItemGroup>
<_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/>
</ItemGroup>
<WriteLinesToFile
Condition="!Exists('$(PublishDir)web.config')"
File="$(PublishDir)web.config"
Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" />
<!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing -->
<Delete Files="$(PublishDir)$(AssemblyName).blazor.config" />
</Target>
</Project>

View File

@ -1,37 +0,0 @@
<Project>
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>
$(ResolveStaticWebAssetsInputsDependsOn);
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
</ResolveStaticWebAssetsInputsDependsOn>
<GetCurrentProjectStaticWebAssetsDependsOn>
$(GetCurrentProjectStaticWebAssetsDependsOn);
_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets;
</GetCurrentProjectStaticWebAssetsDependsOn>
</PropertyGroup>
<Target Name="_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" />
</ItemGroup>
</Target>
<Target Name="BlazorStaticWebAssetsComputeFilesToPublish"
AfterTargets="_StaticWebAssetsComputeFilesToPublish">
<ItemGroup>
<!-- We need to update the external static web assets to follow the blazor publish output convention that puts them inside $(TargetName)/dist instead of wwwroot -->
<_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''">
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))'))</RelativePath>
</_StandaloneExternalPublishStaticWebAsset>
<!-- Update doesn't work inside targets so we need to remove the items and re-add them. See https://github.com/microsoft/msbuild/issues/2835 for details -->
<ResolvedFileToPublish Remove="@(_StandaloneExternalPublishStaticWebAsset)" />
<ResolvedFileToPublish Include="@(_StandaloneExternalPublishStaticWebAsset)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,534 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class BindRazorIntegrationTest : RazorIntegrationTestBase
{
public BindRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public int Value { get; set; }
[Parameter]
public Action<int> ValueChanged { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public int Value { get; set; }
[Parameter]
public Action<int> OnChanged { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2));
}
[Fact]
public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}"));
var component = CompileToComponent(@"
<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "Value", 42, 1),
frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2));
}
[Fact]
public void Render_BindToElement_WritesAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", null, ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
var component = CompileToComponent(@"
<div @bind=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 3, 0),
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
}
[Fact]
public void Render_BindToElementWithSuffix_WritesAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
var component = CompileToComponent(@"
<div @bind-value=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 3, 0),
frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1),
frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2));
}
[Fact]
public void Render_BindDuplicates_ReportsDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
// Act
var result = CompileToCSharp(@"
<div @bind-value=""@ParentValue"" />
@code {
public string ParentValue { get; set; } = ""hi"";
}");
// Assert
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ9989", diagnostic.Id);
Assert.Equal(
"The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
"Test.BindAttributes" + Environment.NewLine +
"Test.BindAttributes",
diagnostic.GetMessage());
}
[Fact]
public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input @bind=""@ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "value", "42", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/>
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/>
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
public string Format { get; set; } = ""MM/dd/yyyy"";
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputText_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""text"" @bind=""@ParentValue"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", "42", 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<input type=""checkbox"" @bind=""@Enabled"" />
@code {
public bool Enabled { get; set; }
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 3, 0),
frame => AssertFrame.Attribute(frame, "type", "checkbox", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BindToElementFallback_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", "42", 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact]
public void Render_BindToElementFallback_WithFormat_WritesAttributes()
{
// Arrange
var component = CompileToComponent(@"
<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" />
@code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 4, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3));
}
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute()
{
// Arrange
var component = CompileToComponent(@"
<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 5, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "visible", 2),
frame => AssertFrame.Attribute(frame, "value", "42", 3),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
}
[Fact] // See https://github.com/dotnet/blazor/issues/703
public void Workaround_703()
{
// Arrange
var component = CompileToComponent(@"
<input @bind-value=""@ParentValue"" @bind-value:event=""onchange"" type=""text"" visible />
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
//
// The workaround for 703 is that the value attribute MUST be after the type
// attribute.
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "input", 5, 0),
frame => AssertFrame.Attribute(frame, "type", "text", 1),
frame => AssertFrame.Attribute(frame, "visible", 2),
frame => AssertFrame.Attribute(frame, "value", "42", 3),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4));
}
[Fact] // Additional coverage of OrphanTagHelperLoweringPass
public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_BodyContent()
{
// Arrange
var component = CompileToComponent(@"
<div @bind-value=""@ParentValue"" @bind-value:event=""onchange"">
<span>@(42.ToString())</span>
</div>
@code {
public int ParentValue { get; set; } = 42;
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "div", 7, 0),
frame => AssertFrame.Attribute(frame, "value", "42", 1),
frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2),
frame => AssertFrame.MarkupWhitespace(frame, 3),
frame => AssertFrame.Element(frame, "span", 2, 4),
frame => AssertFrame.Text(frame, "42", 5),
frame => AssertFrame.MarkupWhitespace(frame, 6));
}
[Fact]
public void Render_BindFallback_InvalidSyntax_TooManyParts()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" @bind-first-second-third=""Text"" />
@code {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
[Fact]
public void Render_BindFallback_InvalidSyntax_TrailingDash()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" @bind-first-=""Text"" />
@code {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
}
}

View File

@ -1,41 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BootJsonWriterTest
{
[Fact]
public async Task ProducesJsonReferencingAssemblyAndDependencies()
{
// Arrange/Act
var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
using var stream = new MemoryStream();
// Act
GenerateBlazorBootJson.WriteBootJson(
stream,
"MyApp.Entrypoint.dll",
assemblyReferences,
linkerEnabled: true);
// Assert
stream.Position = 0;
using var parsedContent = await JsonDocument.ParseAsync(stream);
var rootElement = parsedContent.RootElement;
Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString());
var assembliesElement = rootElement.GetProperty("assemblies");
Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength());
for (var i = 0; i < assemblyReferences.Length; i++)
{
Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString());
}
Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean());
}
}
}

View File

@ -1,40 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIncrementalismTest
{
[Fact]
public async Task Build_WithLinker_IsIncremental()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
// Act
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
// Assert
for (var i = 0; i < 3; i++)
{
result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory);
Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
for (var j = 0; j < thumbPrint.Count; j++)
{
Assert.Equal(thumbPrint[j], newThumbPrint[j]);
}
}
}
}
}

View File

@ -1,135 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class BuildIntegrationTest
{
[Fact]
public async Task Build_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
[Fact]
public async Task Build_Hosted_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "net5.0";
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
var blazorConfig = Path.Combine(buildOutputDirectory, "standalone.blazor.config");
Assert.FileExists(result, blazorConfig);
var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll"));
Assert.FileContains(result, blazorConfig, path);
Assert.FileDoesNotExist(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
}
[Fact]
public async Task Build_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone");
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project);
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
[Fact]
public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
}
}

View File

@ -1,224 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build
{
public class PublishIntegrationTest
{
[Fact]
public async Task Publish_WithDefaultSettings_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" });
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify static web assets from referenced projects are copied.
// Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved.
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
// Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_WithLinkOnBuildDisabled_Works()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" });
project.AddProjectFileContent(
@"<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify referenced static web assets
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Publish_HostedApp_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "net5.0";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify static web assets from referenced projects are copied.
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
var blazorConfig = Path.Combine(result.Project.DirectoryPath, publishDirectory, "standalone.blazor.config");
var blazorConfigLines = File.ReadAllLines(blazorConfig);
Assert.Equal(".", blazorConfigLines[0]);
Assert.Equal("standalone/", blazorConfigLines[1]);
}
[Fact]
public async Task Publish_HostedApp_WithNoBuild_Works()
{
// Arrange
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", });
project.TargetFramework = "net5.0";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build");
Assert.BuildPassed(result);
result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true");
var publishDirectory = project.PublishOutputDirectory;
// Make sure the main project exists
Assert.FileExists(result, publishDirectory, "blazorhosted.dll");
var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify static web assets from referenced projects are copied.
// Uncomment once https://github.com/dotnet/aspnetcore/issues/17426 is resolved.
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
// Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css");
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html");
// Verify web.config
Assert.FileExists(result, publishDirectory, "web.config");
}
}
}

View File

@ -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));
}
}
}

View File

@ -1,616 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
{
public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_ChildComponent_Simple()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
var component = CompileToComponent(@"
<MyComponent/>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
}
[Fact]
public void Render_ChildComponent_WithParameters()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class SomeType
{
}
public class MyComponent : ComponentBase
{
[Parameter] public int IntProperty { get; set; }
[Parameter] public bool BoolProperty { get; set; }
[Parameter] public string StringProperty { get; set; }
[Parameter] public SomeType ObjectProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent
IntProperty=""123""
BoolProperty=""true""
StringProperty=""My string""
ObjectProperty=""new SomeType()"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
frame =>
{
AssertFrame.Attribute(frame, "ObjectProperty", 4);
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
});
}
[Fact]
public void Render_ChildComponent_TriesToSetNonParameter()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
public int IntProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent IntProperty=""123"" />");
// Act
var ex = Assert.Throws<InvalidOperationException>(() => GetRenderTree(component));
// Assert
Assert.Equal(
"Object of type 'Test.MyComponent' has a property matching the name 'IntProperty', " +
"but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.",
ex.Message);
}
[Fact]
public void Render_ChildComponent_WithExplicitStringParameter()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public string StringProperty { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent StringProperty=""@(42.ToString())"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1));
}
[Fact]
public void Render_ChildComponent_WithNonPropertyAttributes()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase, IComponent
{
Task IComponent.SetParametersAsync(ParameterView parameters)
{
return Task.CompletedTask;
}
}
}
"));
var component = CompileToComponent(@"
<MyComponent some-attribute=""foo"" another-attribute=""@(42.ToString())"" />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "some-attribute", "foo", 1),
frame => AssertFrame.Attribute(frame, "another-attribute", "42", 2));
}
[Theory]
[InlineData("e => Increment(e)")]
[InlineData("(e) => Increment(e)")]
[InlineData("@(e => Increment(e))")]
[InlineData("@(e => { Increment(e); })")]
[InlineData("Increment")]
[InlineData("@Increment")]
[InlineData("@(Increment)")]
public void Render_ChildComponent_WithEventHandler(string expression)
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public Action<MouseEventArgs> OnClick { get; set; }
}
}
"));
var component = CompileToComponent($@"
@using Microsoft.AspNetCore.Components.Web
<MyComponent OnClick=""{expression}""/>
@code {{
private int counter;
private void Increment(MouseEventArgs e) {{
counter++;
}}
}}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "OnClick", 1);
// The handler will have been assigned to a lambda
var handler = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue);
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
});
}
[Fact]
public void Render_ChildComponent_WithExplicitEventHandler()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public Action<EventArgs> OnClick { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent OnClick=""@Increment""/>
@code {
private int counter;
private void Increment(EventArgs e) {
counter++;
}
}");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame =>
{
AssertFrame.Attribute(frame, "OnClick", 1);
// The handler will have been assigned to a lambda
var handler = Assert.IsType<Action<EventArgs>>(frame.AttributeValue);
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
Assert.Equal("Increment", handler.Method.Name);
});
}
[Fact]
public void Render_ChildComponent_WithMinimizedBoolAttribute()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public bool BoolProperty { get; set; }
}
}"));
var component = CompileToComponent(@"
<MyComponent BoolProperty />");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1));
}
[Fact]
public void Render_ChildComponent_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public string MyAttr { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text @(""Hello"")</some-child></MyComponent>");
// Act
var frames = GetRenderTree(component);
// Assert: component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
frame => AssertFrame.Attribute(frame, "ChildContent", 2));
// Assert: Captured ChildContent frames are correct
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
Assert.Collection(
childFrames.AsEnumerable(),
frame => AssertFrame.Text(frame, "Some text", 3),
frame => AssertFrame.Element(frame, "some-child", 4, 4),
frame => AssertFrame.Attribute(frame, "a", "1", 5),
frame => AssertFrame.Text(frame, "Nested text ", 6),
frame => AssertFrame.Text(frame, "Hello", 7));
}
[Fact]
public void Render_ChildComponent_Nested()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<MyComponent><MyComponent>Some text</MyComponent></MyComponent>");
// Act
var frames = GetRenderTree(component);
// Assert: outer component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
frame => AssertFrame.Attribute(frame, "ChildContent", 1));
// Assert: first level of ChildContent is correct
// Note that we don't really need the sequence numbers to continue on from the
// sequence numbers at the parent level. All that really matters is that they are
// correct relative to each other (i.e., incrementing) within the nesting level.
// As an implementation detail, it happens that they do follow on from the parent
// level, but we could change that part of the implementation if we wanted.
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).AsEnumerable().ToArray();
Assert.Collection(
innerFrames,
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
frame => AssertFrame.Attribute(frame, "ChildContent", 3));
// Assert: second level of ChildContent is correct
Assert.Collection(
GetFrames((RenderFragment)innerFrames[1].AttributeValue).AsEnumerable(),
frame => AssertFrame.Text(frame, "Some text", 4));
}
[Fact] // https://github.com/dotnet/blazor/issues/773
public void Regression_773()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class SurveyPrompt : ComponentBase
{
[Parameter] public string Title { get; set; }
}
}
"));
var component = CompileToComponent(@"
@page ""/""
<SurveyPrompt Title=""<div>Test!</div>"" />
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.SurveyPrompt", 2, 0),
frame => AssertFrame.Attribute(frame, "Title", "<div>Test!</div>", 1));
}
[Fact]
public void Regression_784()
{
// Arrange
// Act
var component = CompileToComponent(@"
@using Microsoft.AspNetCore.Components.Web
<p @onmouseover=""OnComponentHover"" style=""background: @ParentBgColor;"" />
@code {
public string ParentBgColor { get; set; } = ""#FFFFFF"";
public void OnComponentHover(MouseEventArgs e)
{
}
}
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "p", 3, 0),
frame => AssertFrame.Attribute(frame, "onmouseover", 1),
frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2));
}
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/6185")]
public void Render_Component_HtmlEncoded()
{
// Arrange
var component = CompileToComponent(@"&lt;span&gt;Hi&lt;/span&gt;");
// 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>&lt;span&gt;Hi&lt/span&gt;</div>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Markup(frame, "<div>&lt;span&gt;Hi&lt/span&gt;</div>"));
}
// Integration test for HTML block rewriting
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/6183")]
public void Render_HtmlBlock_Integration()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
}
"));
var component = CompileToComponent(@"
<html>
<head><meta><meta></head>
<body>
<MyComponent>
<div><span></span><span></span></div>
<div>@(""hi"")</div>
<div><span></span><span></span></div>
<div></div>
<div>@(""hi"")</div>
<div></div>
</MyComponent>
</body>
</html>");
// Act
var frames = GetRenderTree(component);
// Assert: component frames are correct
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "html", 9, 0),
frame => AssertFrame.MarkupWhitespace(frame, 1),
frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2),
frame => AssertFrame.Element(frame, "body", 5, 3),
frame => AssertFrame.MarkupWhitespace(frame, 4),
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5),
frame => AssertFrame.Attribute(frame, "ChildContent", 6),
frame => AssertFrame.MarkupWhitespace(frame, 16),
frame => AssertFrame.MarkupWhitespace(frame, 17));
// Assert: Captured ChildContent frames are correct
var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue);
Assert.Collection(
childFrames.AsEnumerable(),
frame => AssertFrame.MarkupWhitespace(frame, 7),
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8),
frame => AssertFrame.Element(frame, "div", 2, 9),
frame => AssertFrame.Text(frame, "hi", 10),
frame => AssertFrame.MarkupWhitespace(frame, 11),
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12),
frame => AssertFrame.Element(frame, "div", 2, 13),
frame => AssertFrame.Text(frame, "hi", 14),
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15));
}
[Fact]
public void RazorTemplate_CanBeUsedFromComponent()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Test
{
public class Repeater : ComponentBase
{
[Parameter] public int Count { get; set; }
[Parameter] public RenderFragment<string> Template { get; set; }
[Parameter] public string Value { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
for (var i = 0; i < Count; i++)
{
builder.AddContent(i, Template, Value);
}
}
}
}
"));
var component = CompileToComponent(@"
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
<Repeater Count=3 Value=""Hello, World!"" Template=""template"" />
");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, "Test.Repeater", 4, 2),
frame => AssertFrame.Attribute(frame, "Count", typeof(int), 3),
frame => AssertFrame.Attribute(frame, "Value", typeof(string), 4),
frame => AssertFrame.Attribute(frame, "Template", typeof(RenderFragment<string>), 5),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1),
frame => AssertFrame.Element(frame, "div", 2, 0),
frame => AssertFrame.Text(frame, "hello, world!", 1));
}
}
}

View File

@ -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 { }
}
}

View File

@ -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));
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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.");
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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));
}
}
}

View File

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="$(ReferenceBlazorBuildFromSourceProps)" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<!-- Test Placeholder -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\razorclasslibrary\RazorClassLibrary.csproj" />
</ItemGroup>
</Project>

View File

@ -1,8 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
<PropertyGroup>
<ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion>
</PropertyGroup>
</Project>

View File

@ -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 { }
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@ -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);
}
}
}

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
</ItemGroup>
</Project>

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Description>Runtime server features for ASP.NET Core Blazor applications.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<Reference Include="Microsoft.AspNetCore.SpaServices.Extensions" />
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
<Reference Include="Microsoft.AspNetCore.WebSockets" />
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
<Reference Include="Newtonsoft.Json" />
<!-- Used by ws-proxy sources only. Remove this once we're able to consume ws-proxy as a NuGet package. -->
<Reference Include="Mono.Cecil" />
</ItemGroup>
</Project>

View File

@ -1,317 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using WsProxy;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Provides infrastructure for debugging Blazor applications.
/// </summary>
public static class BlazorMonoDebugProxyAppBuilderExtensions
{
private static JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
IgnoreNullValues = true
};
private static string DefaultDebuggerHost = "http://localhost:9222";
/// <summary>
/// Adds middleware for needed for debugging Blazor applications
/// inside Chromium dev tools.
/// </summary>
public static void UseBlazorDebugging(this IApplicationBuilder app)
{
app.UseWebSockets();
app.UseVisualStudioDebuggerConnectionRequestHandlers();
app.Use((context, next) =>
{
var requestPath = context.Request.Path;
if (!requestPath.StartsWithSegments("/_framework/debug"))
{
return next();
}
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
{
return DebugWebSocketProxyRequest(context);
}
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
{
return DebugHome(context);
}
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Task.CompletedTask;
});
}
private static string GetDebuggerHost()
{
var envVar = Environment.GetEnvironmentVariable("ASPNETCORE_WEBASSEMBLYDEBUGHOST");
if (string.IsNullOrEmpty(envVar))
{
return DefaultDebuggerHost;
}
else
{
return envVar;
}
}
private static int GetDebuggerPort()
{
var host = GetDebuggerHost();
return new Uri(host).Port;
}
private static void UseVisualStudioDebuggerConnectionRequestHandlers(this IApplicationBuilder app)
{
// Unfortunately VS doesn't send any deliberately distinguishing information so we know it's
// not a regular browser or API client. The closest we can do is look for the *absence* of a
// User-Agent header. In the future, we should try to get VS to send a special header to indicate
// this is a debugger metadata request.
app.Use(async (context, next) =>
{
var request = context.Request;
var requestPath = request.Path;
if (requestPath.StartsWithSegments("/json")
&& !request.Headers.ContainsKey("User-Agent"))
{
if (requestPath.Equals("/json", StringComparison.OrdinalIgnoreCase) || requestPath.Equals("/json/list", StringComparison.OrdinalIgnoreCase))
{
var availableTabs = await GetOpenedBrowserTabs();
// Filter the list to only include tabs displaying the requested app,
// but only during the "choose application to debug" phase. We can't apply
// the same filter during the "connecting" phase (/json/list), nor do we need to.
if (requestPath.Equals("/json"))
{
availableTabs = availableTabs.Where(tab => tab.Url.StartsWith($"{request.Scheme}://{request.Host}{request.PathBase}/"));
}
var proxiedTabInfos = availableTabs.Select(tab =>
{
var underlyingV8Endpoint = tab.WebSocketDebuggerUrl;
var proxiedV8Endpoint = $"ws://{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
return new
{
description = "",
devtoolsFrontendUrl = "",
id = tab.Id,
title = tab.Title,
type = tab.Type,
url = tab.Url,
webSocketDebuggerUrl = proxiedV8Endpoint
};
});
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(proxiedTabInfos));
}
else if (requestPath.Equals("/json/version", StringComparison.OrdinalIgnoreCase))
{
var browserVersionJson = await GetBrowserVersionInfoAsync();
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(browserVersionJson);
}
}
else
{
await next();
}
});
}
private static async Task DebugWebSocketProxyRequest(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return;
}
var browserUri = new Uri(context.Request.Query["browser"]);
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
await new MonoProxy().Run(browserUri, ideSocket);
}
private static async Task DebugHome(HttpContext context)
{
context.Response.ContentType = "text/html";
var request = context.Request;
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
var targetTabUrl = request.Query["url"];
if (string.IsNullOrEmpty(targetTabUrl))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("No value specified for 'url'");
return;
}
// TODO: Allow overriding port (but not hostname, as we're connecting to the
// local browser, not to the webserver serving the app)
var debuggerHost = GetDebuggerHost();
var debuggerTabsListUrl = $"{debuggerHost}/json";
IEnumerable<BrowserTab> availableTabs;
try
{
availableTabs = await GetOpenedBrowserTabs();
}
catch (Exception ex)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
Ensure your browser is running with debugging enabled.
</p>
<h2>Resolution</h2>
<p>
<h4>If you are using Google Chrome for your development, follow these instructions:</h4>
{GetLaunchChromeInstructions(appRootUrl)}
</p>
<p>
<h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4>
{GetLaunchEdgeInstructions(appRootUrl)}
</p>
<strong>This should launch a new browser window with debugging enabled..</p>
<h2>Underlying exception:</h2>
<pre>{ex}</pre>
");
return;
}
var matchingTabs = availableTabs
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
.ToList();
if (matchingTabs.Count == 0)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
The response from <code>{debuggerTabsListUrl}</code> does not include
any entry for <code>{targetTabUrl}</code>.
</p>");
return;
}
else if (matchingTabs.Count > 1)
{
// TODO: Automatically disambiguate by adding a GUID to the page title
// when you press the debugger hotkey, include it in the querystring passed
// here, then remove it once the debugger connects.
await context.Response.WriteAsync($@"
<h1>Multiple matching tabs are open</h1>
<p>
There is more than one browser tab at <code>{targetTabUrl}</code>.
Close the ones you do not wish to debug, then refresh this page.
</p>");
return;
}
// Now we know uniquely which tab to debug, construct the URL to the debug
// page and redirect there
var tabToDebug = matchingTabs.Single();
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
var wsParamName = request.IsHttps ? "wss" : "ws";
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{wsParamName}={proxyEndpoint}";
context.Response.Redirect(devToolsUrlWithProxy);
}
private static string GetLaunchChromeInstructions(string appRootUrl)
{
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug");
var debuggerPort = GetDebuggerPort();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>Execute the following:</p>
<p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static string GetLaunchEdgeInstructions(string appRootUrl)
{
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
var debugggerPort = GetDebuggerPort();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>msedge --remote-debugging-port={debugggerPort} --user-data-dir=""{profilePath}"" --no-first-run {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debugggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static async Task<string> GetBrowserVersionInfoAsync()
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var debuggerHost = GetDebuggerHost();
return await httpClient.GetStringAsync($"{debuggerHost}/json/version");
}
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs()
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var debuggerHost = GetDebuggerHost();
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
return JsonSerializer.Deserialize<BrowserTab[]>(jsonResponse, JsonOptions);
}
class BrowserTab
{
public string Id { get; set; }
public string Type { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string DevtoolsFrontendUrl { get; set; }
public string WebSocketDebuggerUrl { get; set; }
}
}
}

View File

@ -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);
}
}

View File

@ -1,771 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net;
namespace WsProxy {
internal class MonoCommands {
public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()";
public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready";
public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})";
public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])";
public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})";
public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
}
internal enum MonoErrorCodes {
BpNotFound = 100000,
}
internal class MonoConstants {
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
}
class Frame {
public Frame (MethodInfo method, SourceLocation location, int id)
{
this.Method = method;
this.Location = location;
this.Id = id;
}
public MethodInfo Method { get; private set; }
public SourceLocation Location { get; private set; }
public int Id { get; private set; }
}
class Breakpoint {
public SourceLocation Location { get; private set; }
public int LocalId { get; private set; }
public int RemoteId { get; set; }
public BreakPointState State { get; set; }
public Breakpoint (SourceLocation loc, int localId, BreakPointState state)
{
this.Location = loc;
this.LocalId = localId;
this.State = state;
}
}
enum BreakPointState {
Active,
Disabled,
Pending
}
enum StepKind {
Into,
Out,
Over
}
internal class MonoProxy : WsProxy {
DebugStore store;
List<Breakpoint> breakpoints = new List<Breakpoint> ();
List<Frame> current_callstack;
bool runtime_ready;
int local_breakpoint_id;
int ctx_id;
JObject aux_ctx_data;
public MonoProxy () { }
protected override async Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
{
switch (method) {
case "Runtime.executionContextCreated": {
var ctx = args? ["context"];
var aux_data = ctx? ["auxData"] as JObject;
if (aux_data != null) {
var is_default = aux_data ["isDefault"]?.Value<bool> ();
if (is_default == true) {
var ctx_id = ctx ["id"].Value<int> ();
await OnDefaultContext (ctx_id, aux_data, token);
}
}
break;
}
case "Debugger.paused": {
//TODO figure out how to stitch out more frames and, in particular what happens when real wasm is on the stack
var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
await OnBreakPointHit (args, token);
return true;
}
if (top_func == MonoConstants.RUNTIME_IS_READY) {
await OnRuntimeReady (token);
return true;
}
break;
}
case "Debugger.scriptParsed":{
if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) {
// Console.WriteLine ("ignoring wasm event");
return true;
}
break;
}
}
return false;
}
protected override async Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
{
switch (method) {
case "Debugger.getScriptSource": {
var script_id = args? ["scriptId"]?.Value<string> ();
if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
await OnGetScriptSource (id, script_id, token);
return true;
}
break;
}
case "Runtime.compileScript": {
var exp = args? ["expression"]?.Value<string> ();
if (exp.StartsWith ("//dotnet:", StringComparison.InvariantCultureIgnoreCase)) {
OnCompileDotnetScript (id, token);
return true;
}
break;
}
case "Debugger.getPossibleBreakpoints": {
var start = SourceLocation.Parse (args? ["start"] as JObject);
//FIXME support variant where restrictToFunction=true and end is omitted
var end = SourceLocation.Parse (args? ["end"] as JObject);
if (start != null && end != null)
return GetPossibleBreakpoints (id, start, end, token);
break;
}
case "Debugger.setBreakpointByUrl": {
Info ($"BP req {args}");
var bp_req = BreakPointRequest.Parse (args, store);
if (bp_req != null) {
await SetBreakPoint (id, bp_req, token);
return true;
}
break;
}
case "Debugger.removeBreakpoint": {
return await RemoveBreakpoint (id, args, token);
}
case "Debugger.resume": {
await OnResume (token);
break;
}
case "Debugger.stepInto": {
if (this.current_callstack != null) {
await Step (id, StepKind.Into, token);
return true;
}
break;
}
case "Debugger.stepOut": {
if (this.current_callstack != null) {
await Step (id, StepKind.Out, token);
return true;
}
break;
}
case "Debugger.stepOver": {
if (this.current_callstack != null) {
await Step (id, StepKind.Over, token);
return true;
}
break;
}
case "Runtime.getProperties": {
var objId = args? ["objectId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:scope:", StringComparison.InvariantCulture)) {
await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token);
return true;
}
if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture))
{
if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture))
await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES);
if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture))
await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES);
return true;
}
break;
}
}
return false;
}
async Task OnRuntimeReady (CancellationToken token)
{
Info ("RUNTIME READY, PARTY TIME");
await RuntimeReady (token);
await SendCommand ("Debugger.resume", new JObject (), token);
SendEvent ("Mono.runtimeReady", new JObject (), token);
}
async Task OnBreakPointHit (JObject args, CancellationToken token)
{
//FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
var o = JObject.FromObject (new {
expression = MonoCommands.GET_CALL_STACK,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true
});
var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
var res = await SendCommand ("Runtime.evaluate", o, token);
if (res.IsErr) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
//step one, figure out where did we hit
var res_value = res.Value? ["result"]? ["value"];
if (res_value == null || res_value is JValue) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
Debug ($"call stack (err is {res.Error} value is:\n{res.Value}");
var bp_id = res_value? ["breakpoint_id"]?.Value<int> ();
Debug ($"We just hit bp {bp_id}");
if (!bp_id.HasValue) {
//Give up and send the original call stack
SendEvent ("Debugger.paused", args, token);
return;
}
var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value);
var src = bp == null ? null : store.GetFileById (bp.Location.Id);
var callFrames = new List<JObject> ();
foreach (var frame in orig_callframes) {
var function_name = frame ["functionName"]?.Value<string> ();
var url = frame ["url"]?.Value<string> ();
if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
var frames = new List<Frame> ();
int frame_id = 0;
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
foreach (var mono_frame in the_mono_frames) {
var il_pos = mono_frame ["il_pos"].Value<int> ();
var method_token = mono_frame ["method_token"].Value<int> ();
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
var asm = store.GetAssemblyByName (assembly_name);
var method = asm.GetMethodByToken (method_token);
if (method == null) {
Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
continue;
}
var location = method?.GetLocationByIl (il_pos);
// When hitting a breakpoint on the "IncrementCount" method in the standard
// Blazor project template, one of the stack frames is inside mscorlib.dll
// and we get location==null for it. It will trigger a NullReferenceException
// if we don't skip over that stack frame.
if (location == null) {
continue;
}
Info ($"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
Info ($"\tmethod {method.Name} location: {location}");
frames.Add (new Frame (method, location, frame_id));
callFrames.Add (JObject.FromObject (new {
functionName = method.Name,
callFrameId = $"dotnet:scope:{frame_id}",
functionLocation = method.StartLocation.ToJObject (),
location = location.ToJObject (),
url = store.ToUrl (location),
scopeChain = new [] {
new {
type = "local",
@object = new {
@type = "object",
className = "Object",
description = "Object",
objectId = $"dotnet:scope:{frame_id}",
},
name = method.Name,
startLocation = method.StartLocation.ToJObject (),
endLocation = method.EndLocation.ToJObject (),
}}
}));
++frame_id;
this.current_callstack = frames;
}
} else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
|| url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
callFrames.Add (frame);
}
}
var bp_list = new string [bp == null ? 0 : 1];
if (bp != null)
bp_list [0] = $"dotnet:{bp.LocalId}";
o = JObject.FromObject (new {
callFrames = callFrames,
reason = "other", //other means breakpoint
hitBreakpoints = bp_list,
});
SendEvent ("Debugger.paused", o, token);
}
async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken token)
{
Debug ("Default context created, clearing state and sending events");
//reset all bps
foreach (var b in this.breakpoints){
b.State = BreakPointState.Pending;
}
this.runtime_ready = false;
var o = JObject.FromObject (new {
expression = MonoCommands.IS_RUNTIME_READY_VAR,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true
});
this.ctx_id = ctx_id;
this.aux_ctx_data = aux_data;
Debug ("checking if the runtime is ready");
var res = await SendCommand ("Runtime.evaluate", o, token);
var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
//Debug ($"\t{is_ready}");
if (is_ready.HasValue && is_ready.Value == true) {
Debug ("RUNTIME LOOK READY. GO TIME!");
await RuntimeReady (token);
}
}
async Task OnResume (CancellationToken token)
{
//discard frames
this.current_callstack = null;
await Task.CompletedTask;
}
async Task Step (int msg_id, StepKind kind, CancellationToken token)
{
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
SendResponse (msg_id, Result.Ok (new JObject ()), token);
this.current_callstack = null;
await SendCommand ("Debugger.resume", new JObject (), token);
}
async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command)
{
var o = JObject.FromObject(new
{
expression = string.Format(command, object_id),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand("Runtime.evaluate", o, token);
//if we fail we just bubble that to the IDE (and let it panic over it)
if (res.IsErr)
{
SendResponse(msg_id, res, token);
return;
}
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
var var_list = new List<JObject>();
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
for (int i = 0; i < values.Length; i+=2)
{
string fieldName = (string)values[i]["name"];
if (fieldName.Contains("k__BackingField")){
fieldName = fieldName.Replace("k__BackingField", "");
fieldName = fieldName.Replace("<", "");
fieldName = fieldName.Replace(">", "");
}
var_list.Add(JObject.FromObject(new
{
name = fieldName,
value = values[i+1]["value"]
}));
}
o = JObject.FromObject(new
{
result = var_list
});
SendResponse(msg_id, Result.Ok(o), token);
}
async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token)
{
var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
var var_ids = string.Join (",", vars.Select (v => v.Index));
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
//if we fail we just bubble that to the IDE (and let it panic over it)
if (res.IsErr) {
SendResponse (msg_id, res, token);
return;
}
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
var var_list = new List<JObject> ();
int i = 0;
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
while (values != null && i < vars.Length && i < values.Length) {
var value = values [i] ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString();
var_list.Add (JObject.FromObject (new {
name = vars [i].Name,
value = values [i] ["value"]
}));
i++;
}
//Async methods are special in the way that local variables can be lifted to generated class fields
//value of "this" comes here either
while (i < values.Length) {
String name = values [i] ["name"].ToString ();
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
var_list.Add (JObject.FromObject (new {
name = name,
value = values [i+1] ["value"]
}));
i = i + 2;
}
o = JObject.FromObject (new {
result = var_list
});
SendResponse (msg_id, Result.Ok (o), token);
}
async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)
{
var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
var method_token = bp.Location.CliLocation.Method.Token;
var il_offset = bp.Location.CliLocation.Offset;
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
if (ret_code.HasValue) {
bp.RemoteId = ret_code.Value;
bp.State = BreakPointState.Active;
//Debug ($"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
}
return res;
}
async Task RuntimeReady (CancellationToken token)
{
var o = JObject.FromObject (new {
expression = MonoCommands.GET_LOADED_FILES,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var loaded_pdbs = await SendCommand ("Runtime.evaluate", o, token);
var the_value = loaded_pdbs.Value? ["result"]? ["value"];
var the_pdbs = the_value?.ToObject<string[]> ();
this.store = new DebugStore (the_pdbs);
foreach (var s in store.AllSources ()) {
var ok = JObject.FromObject (new {
scriptId = s.SourceId.ToString (),
url = s.Url,
executionContextId = this.ctx_id,
hash = s.DocHashCode,
executionContextAuxData = this.aux_ctx_data,
dotNetUrl = s.DotNetUrl
});
//Debug ($"\tsending {s.Url}");
SendEvent ("Debugger.scriptParsed", ok, token);
}
o = JObject.FromObject (new {
expression = MonoCommands.CLEAR_ALL_BREAKPOINTS,
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var clear_result = await SendCommand ("Runtime.evaluate", o, token);
if (clear_result.IsErr) {
Debug ($"Failed to clear breakpoints due to {clear_result}");
}
runtime_ready = true;
foreach (var bp in breakpoints) {
if (bp.State != BreakPointState.Pending)
continue;
var res = await EnableBreakPoint (bp, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
//if we fail we just bubble that to the IDE (and let it panic over it)
if (!ret_code.HasValue) {
//FIXME figure out how to inform the IDE of that.
Info ($"FAILED TO ENABLE BP {bp.LocalId}");
bp.State = BreakPointState.Disabled;
}
}
}
async Task<bool> RemoveBreakpoint(int msg_id, JObject args, CancellationToken token) {
var bpid = args? ["breakpointId"]?.Value<string> ();
if (bpid?.StartsWith ("dotnet:") != true)
return false;
var the_id = int.Parse (bpid.Substring ("dotnet:".Length));
var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id);
if (bp == null) {
Info ($"Could not find dotnet bp with id {the_id}");
return false;
}
breakpoints.Remove (bp);
//FIXME verify result (and log?)
var res = await RemoveBreakPoint (bp, token);
return true;
}
async Task<Result> RemoveBreakPoint (Breakpoint bp, CancellationToken token)
{
var o = JObject.FromObject (new {
expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId),
objectGroup = "mono_debugger",
includeCommandLineAPI = false,
silent = false,
returnByValue = true,
});
var res = await SendCommand ("Runtime.evaluate", o, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
if (ret_code.HasValue) {
bp.RemoteId = -1;
bp.State = BreakPointState.Disabled;
}
return res;
}
async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken token)
{
var bp_loc = store.FindBestBreakpoint (req);
Info ($"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'");
if (bp_loc == null) {
Info ($"Could not resolve breakpoint request: {req}");
SendResponse (msg_id, Result.Err(JObject.FromObject (new {
code = (int)MonoErrorCodes.BpNotFound,
message = $"C# Breakpoint at {req} not found."
})), token);
return;
}
Breakpoint bp = null;
if (!runtime_ready) {
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending);
} else {
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled);
var res = await EnableBreakPoint (bp, token);
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
//if we fail we just bubble that to the IDE (and let it panic over it)
if (!ret_code.HasValue) {
SendResponse (msg_id, res, token);
return;
}
}
var locations = new List<JObject> ();
locations.Add (JObject.FromObject (new {
scriptId = bp_loc.Id.ToString (),
lineNumber = bp_loc.Line,
columnNumber = bp_loc.Column
}));
breakpoints.Add (bp);
var ok = JObject.FromObject (new {
breakpointId = $"dotnet:{bp.LocalId}",
locations = locations,
});
SendResponse (msg_id, Result.Ok (ok), token);
}
bool GetPossibleBreakpoints (int msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
{
var bps = store.FindPossibleBreakpoints (start, end);
if (bps == null)
return false;
var loc = new List<JObject> ();
foreach (var b in bps) {
loc.Add (b.ToJObject ());
}
var o = JObject.FromObject (new {
locations = loc
});
SendResponse (msg_id, Result.Ok (o), token);
return true;
}
void OnCompileDotnetScript (int msg_id, CancellationToken token)
{
var o = JObject.FromObject (new { });
SendResponse (msg_id, Result.Ok (o), token);
}
async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
{
var id = new SourceId (script_id);
var src_file = store.GetFileById (id);
var res = new StringWriter ();
//res.WriteLine ($"//{id}");
try {
var uri = new Uri (src_file.Url);
if (uri.IsFile && File.Exists(uri.LocalPath)) {
using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
await res.WriteAsync (await f.ReadToEndAsync ());
}
var o = JObject.FromObject (new {
scriptSource = res.ToString ()
});
SendResponse (msg_id, Result.Ok (o), token);
} else if(src_file.SourceLinkUri != null) {
var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
await res.WriteAsync (doc);
var o = JObject.FromObject (new {
scriptSource = res.ToString ()
});
SendResponse (msg_id, Result.Ok (o), token);
} else {
var o = JObject.FromObject (new {
scriptSource = $"// Unable to find document {src_file.SourceUri}"
});
SendResponse (msg_id, Result.Ok (o), token);
}
} catch (Exception e) {
var o = JObject.FromObject (new {
scriptSource = $"// Unable to read document ({e.Message})\n" +
$"Local path: {src_file?.SourceUri}\n" +
$"SourceLink path: {src_file?.SourceLinkUri}\n"
});
SendResponse (msg_id, Result.Ok (o), token);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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/

View File

@ -1,34 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace System.ComponentModel.DataAnnotations
{
/// <summary>
/// A <see cref="ValidationAttribute"/> that compares two properties
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ComparePropertyAttribute : CompareAttribute
{
/// <summary>
/// Initializes a new instance of <see cref="BlazorCompareAttribute"/>.
/// </summary>
/// <param name="otherProperty">The property to compare with the current property.</param>
public ComparePropertyAttribute(string otherProperty)
: base(otherProperty)
{
}
/// <inheritdoc />
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var validationResult = base.IsValid(value, validationContext);
if (validationResult == ValidationResult.Success)
{
return validationResult;
}
return new ValidationResult(validationResult.ErrorMessage, new[] { validationContext.MemberName });
}
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Provides experimental support for validation using DataAnnotations.</Description>
<IsShippingPackage>false</IsShippingPackage>
<HasReferenceAssembly>false</HasReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Forms" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests" />
</ItemGroup>
</Project>

View File

@ -1,125 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Microsoft.AspNetCore.Components.Forms
{
public class ObjectGraphDataAnnotationsValidator : ComponentBase
{
private static readonly object ValidationContextValidatorKey = new object();
private static readonly object ValidatedObjectsKey = new object();
private ValidationMessageStore _validationMessageStore;
[CascadingParameter]
internal EditContext EditContext { get; set; }
protected override void OnInitialized()
{
_validationMessageStore = new ValidationMessageStore(EditContext);
// Perform object-level validation (starting from the root model) on request
EditContext.OnValidationRequested += (sender, eventArgs) =>
{
_validationMessageStore.Clear();
ValidateObject(EditContext.Model, new HashSet<object>());
EditContext.NotifyValidationStateChanged();
};
// Perform per-field validation on each field edit
EditContext.OnFieldChanged += (sender, eventArgs) =>
ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier);
}
internal void ValidateObject(object value, HashSet<object> visited)
{
if (value is null)
{
return;
}
if (!visited.Add(value))
{
// Already visited this object.
return;
}
if (value is IEnumerable<object> enumerable)
{
var index = 0;
foreach (var item in enumerable)
{
ValidateObject(item, visited);
index++;
}
return;
}
var validationResults = new List<ValidationResult>();
ValidateObject(value, visited, validationResults);
// Transfer results to the ValidationMessageStore
foreach (var validationResult in validationResults)
{
if (!validationResult.MemberNames.Any())
{
_validationMessageStore.Add(new FieldIdentifier(value, string.Empty), validationResult.ErrorMessage);
continue;
}
foreach (var memberName in validationResult.MemberNames)
{
var fieldIdentifier = new FieldIdentifier(value, memberName);
_validationMessageStore.Add(fieldIdentifier, validationResult.ErrorMessage);
}
}
}
private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults)
{
var validationContext = new ValidationContext(value);
validationContext.Items.Add(ValidationContextValidatorKey, this);
validationContext.Items.Add(ValidatedObjectsKey, visited);
Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true);
}
internal static bool TryValidateRecursive(object value, ValidationContext validationContext)
{
if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is ObjectGraphDataAnnotationsValidator validator)
{
var visited = (HashSet<object>)validationContext.Items[ValidatedObjectsKey];
validator.ValidateObject(value, visited);
return true;
}
return false;
}
private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
{
// DataAnnotations only validates public properties, so that's all we'll look for
var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName);
if (propertyInfo != null)
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
messages.Clear(fieldIdentifier);
messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
editContext.NotifyValidationStateChanged();
}
}
}
}

View File

@ -1,30 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Components.Forms;
namespace System.ComponentModel.DataAnnotations
{
/// <summary>
/// A <see cref="ValidationAttribute"/> that indicates that the property is a complex or collection type that further needs to be validated.
/// <para>
/// By default <see cref="Validator"/> does not recurse in to complex property types during validation.
/// When used in conjunction with <see cref="ObjectGraphDataAnnotationsValidator"/>, this property allows the validation system to validate
/// complex or collection type properties.
/// </para>
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ValidateComplexTypeAttribute : ValidationAttribute
{
/// <inheritdoc />
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!ObjectGraphDataAnnotationsValidator.TryValidateRecursive(value, validationContext))
{
throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(ObjectGraphDataAnnotationsValidator)}.");
}
return ValidationResult.Success;
}
}
}

View File

@ -1,540 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;
using Xunit;
namespace Microsoft.AspNetCore.Components
{
public class ObjectGraphDataAnnotationsValidatorTest
{
public class SimpleModel
{
[Required]
public string Name { get; set; }
[Range(1, 16)]
public int Age { get; set; }
}
[Fact]
public void ValidateObject_SimpleObject()
{
var model = new SimpleModel
{
Age = 23,
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Age);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_SimpleObject_AllValid()
{
var model = new SimpleModel { Name = "Test", Age = 5 };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Age);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
public class ModelWithComplexProperty
{
[Required]
public string Property1 { get; set; }
[ValidateComplexType]
public SimpleModel SimpleModel { get; set; }
}
[Fact]
public void ValidateObject_NullComplexProperty()
{
var model = new ModelWithComplexProperty();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_ModelWithComplexProperties()
{
var model = new ModelWithComplexProperty { SimpleModel = new SimpleModel() };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ModelWithComplexProperties_SomeValid()
{
var model = new ModelWithComplexProperty
{
Property1 = "Value",
SimpleModel = new SimpleModel { Name = "Some Value" },
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Age);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.SimpleModel.Name);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
public class TestValidatableObject : IValidatableObject
{
[Required]
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Custom validation error");
}
}
public class ModelWithValidatableComplexProperty
{
[Required]
public string Property1 { get; set; }
[ValidateComplexType]
public TestValidatableObject Property2 { get; set; } = new TestValidatableObject();
}
[Fact]
public void ValidateObject_ValidatableComplexProperty()
{
var model = new ModelWithValidatableComplexProperty();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Property2);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Property2.Name);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ValidatableComplexProperty_ValidatesIValidatableProperty()
{
var model = new ModelWithValidatableComplexProperty
{
Property2 = new TestValidatableObject { Name = "test" },
};
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Property1);
Assert.Single(messages);
messages = editContext.GetValidationMessages(new FieldIdentifier(model.Property2, string.Empty));
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Property2.Name);
Assert.Empty(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_ModelIsIValidatable_PropertyHasError()
{
var model = new TestValidatableObject();
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_ModelIsIValidatable_ModelHasError()
{
var model = new TestValidatableObject { Name = "test" };
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty));
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Name);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateObject_CollectionModel()
{
var model = new List<SimpleModel>
{
new SimpleModel(),
new SimpleModel { Name = "test", },
};
var editContext = Validate(model);
var item = model[0];
var messages = editContext.GetValidationMessages(new FieldIdentifier(model, "0"));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => item.Age);
Assert.Single(messages);
item = model[1];
messages = editContext.GetValidationMessages(new FieldIdentifier(model, "1"));
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => item.Age);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_CollectionValidatableModel()
{
var model = new List<TestValidatableObject>
{
new TestValidatableObject(),
new TestValidatableObject { Name = "test", },
};
var editContext = Validate(model);
var item = model[0];
var messages = editContext.GetValidationMessages(() => item.Name);
Assert.Single(messages);
item = model[1];
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => item.Name);
Assert.Empty(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
private class Level1Validation
{
[ValidateComplexType]
public Level2Validation Level2 { get; set; }
}
public class Level2Validation
{
[ValidateComplexType]
public SimpleModel Level3 { get; set; }
}
[Fact]
public void ValidateObject_ManyLevels()
{
var model = new Level1Validation
{
Level2 = new Level2Validation
{
Level3 = new SimpleModel
{
Age = 47,
}
}
};
var editContext = Validate(model);
var level3 = model.Level2.Level3;
var messages = editContext.GetValidationMessages(() => level3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => level3.Age);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
private class Person
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Person Related { get; set; }
}
[Fact]
public void ValidateObject_RecursiveRelation()
{
var model = new Person { Related = new Person() };
model.Related.Related = model;
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(() => model.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => model.Related.Name);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_RecursiveRelation_OverManySteps()
{
var person1 = new Person();
var person2 = new Person { Name = "Valid name" };
var person3 = new Person();
var person4 = new Person();
person1.Related = person2;
person2.Related = person3;
person3.Related = person4;
person4.Related = person1;
var editContext = Validate(person1);
var messages = editContext.GetValidationMessages(() => person1.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person2.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => person3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person4.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
private class Node
{
[Required]
public string Id { get; set; }
[ValidateComplexType]
public List<Node> Related { get; set; } = new List<Node>();
}
[Fact]
public void ValidateObject_RecursiveRelation_ViaCollection()
{
var node1 = new Node();
var node2 = new Node { Id = "Valid Id" };
var node3 = new Node();
node1.Related.Add(node2);
node2.Related.Add(node3);
node3.Related.Add(node1);
var editContext = Validate(node1);
var messages = editContext.GetValidationMessages(() => node1.Id);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => node2.Id);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => node3.Id);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateObject_RecursiveRelation_InCollection()
{
var person1 = new Person();
var person2 = new Person { Name = "Valid name" };
var person3 = new Person();
var person4 = new Person();
person1.Related = person2;
person2.Related = person3;
person3.Related = person4;
person4.Related = person1;
var editContext = Validate(person1);
var messages = editContext.GetValidationMessages(() => person1.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person2.Name);
Assert.Empty(messages);
messages = editContext.GetValidationMessages(() => person3.Name);
Assert.Single(messages);
messages = editContext.GetValidationMessages(() => person4.Name);
Assert.Single(messages);
Assert.Equal(3, editContext.GetValidationMessages().Count());
}
[Fact]
public void ValidateField_PropertyValid()
{
var model = new SimpleModel { Age = 1 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_PropertyInvalid()
{
var model = new SimpleModel { Age = 42 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_AfterSubmitValidation()
{
var model = new SimpleModel { Age = 42 };
var fieldIdentifier = FieldIdentifier.Create(() => model.Age);
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Equal(2, editContext.GetValidationMessages().Count());
model.Age = 4;
editContext.NotifyFieldChanged(fieldIdentifier);
messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_ModelWithComplexProperty()
{
var model = new ModelWithComplexProperty
{
SimpleModel = new SimpleModel { Age = 1 },
};
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Name);
var editContext = ValidateField(model, fieldIdentifier);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
Assert.Single(editContext.GetValidationMessages());
}
[Fact]
public void ValidateField_ModelWithComplexProperty_AfterSubmitValidation()
{
var model = new ModelWithComplexProperty
{
Property1 = "test",
SimpleModel = new SimpleModel { Age = 29, Name = "Test" },
};
var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Age);
var editContext = Validate(model);
var messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Single(messages);
model.SimpleModel.Age = 9;
editContext.NotifyFieldChanged(fieldIdentifier);
messages = editContext.GetValidationMessages(fieldIdentifier);
Assert.Empty(messages);
Assert.Empty(editContext.GetValidationMessages());
}
private static EditContext Validate(object model)
{
var editContext = new EditContext(model);
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
validator.OnInitialized();
editContext.Validate();
return editContext;
}
private static EditContext ValidateField(object model, in FieldIdentifier field)
{
var editContext = new EditContext(model);
var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, };
validator.OnInitialized();
editContext.NotifyFieldChanged(field);
return editContext;
}
private class TestObjectGraphDataAnnotationsValidator : ObjectGraphDataAnnotationsValidator
{
public new void OnInitialized() => base.OnInitialized();
}
}
}

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Exe</OutputType>
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
</ItemGroup>
</Project>

View File

@ -1,19 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Hosting;
namespace HostedInAspNet.Client
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace HostedInAspNet.Client
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IComponentsApplicationBuilder app)
{
app.AddComponent<Home>("app");
}
}
}

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Sample Blazor app</title>
</head>
<body>
<app>Loading...</app>
<script src="customJsFileForTests.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -1,19 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Hosting;
namespace StandaloneApp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
}

View File

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
</ItemGroup>
</Project>

View File

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace StandaloneApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IComponentsApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
}

View File

@ -9,31 +9,31 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Analyzers.Tests", "Analyzers\test\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "{F000C49D-3857-42A4-918D-DA4C08691FE2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor", "Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly", "WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Tests", "Blazor\Blazor\test\Microsoft.AspNetCore.Blazor.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Tests", "WebAssembly\WebAssembly\test\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build", "Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build", "WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build.Tests", "Blazor\Build\test\Microsoft.AspNetCore.Blazor.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.Tests", "WebAssembly\Build\test\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DevServer", "WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server", "WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{FD37F740-A654-4117-BFB6-9112CE4C1D3B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "WebAssembly\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{FD37F740-A654-4117-BFB6-9112CE4C1D3B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "Blazor\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{C1E2C117-BE47-4E29-94B3-753262D97A5C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "WebAssembly\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{C1E2C117-BE47-4E29-94B3-753262D97A5C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "Blazor\testassets\MonoSanity\MonoSanity.csproj", "{F16C1A7C-A2BD-4EB1-8BC8-23B1375F3B9E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "WebAssembly\testassets\MonoSanity\MonoSanity.csproj", "{F16C1A7C-A2BD-4EB1-8BC8-23B1375F3B9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "Blazor\testassets\MonoSanityClient\MonoSanityClient.csproj", "{1C4BF2D3-44A8-4A71-B031-15B983663CB0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "WebAssembly\testassets\MonoSanityClient\MonoSanityClient.csproj", "{1C4BF2D3-44A8-4A71-B031-15B983663CB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "Blazor\testassets\StandaloneApp\StandaloneApp.csproj", "{C0FFB29E-4696-4875-9039-E5FA1AC5A42A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj", "{C0FFB29E-4696-4875-9039-E5FA1AC5A42A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A27FF193-195B-4474-8E6C-840B2E339373}"
EndProject
@ -206,10 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Connections.Client", "..\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj", "{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient", "Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj", "{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient.Tests", "Blazor\Http\test\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", "{E4C01A3F-D3C1-4639-A6A9-930E918843DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Tests", "Web\test\Microsoft.AspNetCore.Components.Web.Tests.csproj", "{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{08791FEE-761D-40EF-B701-1D31FD1E6E53}"
@ -236,23 +232,41 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop", "Mono.WebAssembly.Interop", "{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{346EC9B8-BF36-4A5E-A1A3-77879931713A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA95650-E56E-470A-82A3-BC6572E4D6CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server.Tests", "WebAssembly\Server\test\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "{3D0ED658-9DAC-4066-A587-795321FA1C98}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Authentication.WebAssembly.Msal", "WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj", "{4B4E4247-7BBF-444E-9737-407D34821D70}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression", "WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj", "{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DebugProxy", "WebAssembly\DebugProxy\src\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj", "{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevServer", "DevServer", "{C6B58D53-04E2-4D65-B445-B510A3CB7569}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.WebAssembly", "WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj", "{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication", "WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "{5CD61479-5181-4A5B-B90F-9F34316248B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", "WebAssembly\WebAssembly.Authentication\test\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", "{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{81250121-9B43-40B1-BF11-CE4458F2676C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.HttpHandler", "WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -1344,30 +1358,6 @@ Global
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x64.Build.0 = Release|Any CPU
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.ActiveCfg = Release|Any CPU
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.ActiveCfg = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.Build.0 = Debug|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.Build.0 = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.ActiveCfg = Release|Any CPU
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.Build.0 = Debug|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.Build.0 = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.ActiveCfg = Release|Any CPU
{E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.Build.0 = Release|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -1476,42 +1466,6 @@ Global
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.ActiveCfg = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.Build.0 = Debug|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -1548,6 +1502,102 @@ Global
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.ActiveCfg = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.Build.0 = Debug|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.Build.0 = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.ActiveCfg = Release|Any CPU
{3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.Build.0 = Debug|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.Build.0 = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.ActiveCfg = Release|Any CPU
{4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.Build.0 = Debug|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.Build.0 = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.ActiveCfg = Release|Any CPU
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.ActiveCfg = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.Build.0 = Debug|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.Build.0 = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.ActiveCfg = Release|Any CPU
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.ActiveCfg = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.Build.0 = Debug|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.Build.0 = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.ActiveCfg = Release|Any CPU
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.ActiveCfg = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.Build.0 = Debug|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.Build.0 = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.ActiveCfg = Release|Any CPU
{5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.Build.0 = Debug|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.Build.0 = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.ActiveCfg = Release|Any CPU
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.ActiveCfg = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.Build.0 = Debug|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.Build.0 = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.ActiveCfg = Release|Any CPU
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1555,12 +1605,12 @@ Global
GlobalSection(NestedProjects) = preSolution
{ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
{F000C49D-3857-42A4-918D-DA4C08691FE2} = {E059A46B-56E3-41E2-83F4-B5D180056F3B}
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{641922CD-E6F5-41E7-A085-EE07C2A7328D} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
{958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {346EC9B8-BF36-4A5E-A1A3-77879931713A}
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
{C1E2C117-BE47-4E29-94B3-753262D97A5C} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
@ -1645,8 +1695,6 @@ Global
{DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E4C01A3F-D3C1-4639-A6A9-930E918843DD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373}
{956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
{B13CDE69-ED22-4664-AAD7-686ED8CD5E88} = {08791FEE-761D-40EF-B701-1D31FD1E6E53}
@ -1656,14 +1704,23 @@ Global
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
{346EC9B8-BF36-4A5E-A1A3-77879931713A} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{1FA95650-E56E-470A-82A3-BC6572E4D6CD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{3D0ED658-9DAC-4066-A587-795321FA1C98} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}
{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{4B4E4247-7BBF-444E-9737-407D34821D70} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD}
{B118AE2F-8D1D-413F-BC5D-060DF7CB707D} = {C6B58D53-04E2-4D65-B445-B510A3CB7569}
{C6B58D53-04E2-4D65-B445-B510A3CB7569} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{8FDD9F2E-B940-4A5F-83FD-5486D0853D76} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{5CD61479-5181-4A5B-B90F-9F34316248B3} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{6B0D6C08-FC30-4822-9464-4D24FF4CDC17} = {81250121-9B43-40B1-BF11-CE4458F2676C}
{81250121-9B43-40B1-BF11-CE4458F2676C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{031AD67E-DDDE-4A20-874A-8D0A791C6F4C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

View File

@ -6,22 +6,6 @@
"Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj",
"Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj",
"Authorization\\test\\Microsoft.AspNetCore.Components.Authorization.Tests.csproj",
"Blazor\\Blazor\\src\\Microsoft.AspNetCore.Blazor.csproj",
"Blazor\\Blazor\\test\\Microsoft.AspNetCore.Blazor.Tests.csproj",
"Blazor\\Build\\src\\Microsoft.AspNetCore.Blazor.Build.csproj",
"Blazor\\Build\\test\\Microsoft.AspNetCore.Blazor.Build.Tests.csproj",
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
"Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
"Blazor\\testassets\\MonoSanity\\MonoSanity.csproj",
"Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj",
"Components\\perf\\Microsoft.AspNetCore.Components.Performance.csproj",
"Components\\src\\Microsoft.AspNetCore.Components.csproj",
"Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj",
@ -32,12 +16,32 @@
"Samples\\BlazorServerApp\\BlazorServerApp.csproj",
"Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
"WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
"WebAssembly\\Build\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj",
"WebAssembly\\Build\\test\\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj",
"WebAssembly\\Compression\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj",
"WebAssembly\\DebugProxy\\src\\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj",
"WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj",
"WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
"WebAssembly\\Server\\src\\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj",
"WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj",
"WebAssembly\\WebAssembly.Authentication\\src\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj",
"WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
"WebAssembly\\WebAssemblyHttpHandler\\src\\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj",
"WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj",
"WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj",
"WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"WebAssembly\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"WebAssembly\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
"WebAssembly\\testassets\\MonoSanity\\MonoSanity.csproj",
"WebAssembly\\testassets\\StandaloneApp\\StandaloneApp.csproj",
"Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
"test\\testassets\\TestServer\\Components.TestServer.csproj"
]

View File

@ -10,11 +10,11 @@
</PropertyGroup>
<PropertyGroup>
<PackageTags>aspnetcore;components</PackageTags>
<EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget>
</PropertyGroup>
<!-- This property points to the latest released Microsoft.AspNetCore.App version it needs to be updated to
target the latest patch before a preview release. -->
<LatestAspNetCoreReferenceVersion>3.1.0</LatestAspNetCoreReferenceVersion>
<PropertyGroup>
<PackageTags>aspnetcore;components</PackageTags>
<ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot>

View File

@ -3,26 +3,6 @@
<GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile>
</PropertyGroup>
<!-- We need to do this because our build config interferes with the FrameworkReference definition.
This is a way to add the framework defition to the projects that need it (like Blazor Server and
Blazor Dev Server) -->
<Target Name="_AddAspNetCoreFrameworkReference" BeforeTargets="ProcessFrameworkReferences" Condition="'$(UseLatestAspNetCoreReference)' == 'true' ">
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" Version="$(LatestAspNetCoreReferenceVersion)" />
<KnownFrameworkReference Include="Microsoft.AspNetCore.App">
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeFrameworkName>Microsoft.AspNetCore.App</RuntimeFrameworkName>
<DefaultRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</DefaultRuntimeFrameworkVersion>
<LatestRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</LatestRuntimeFrameworkVersion>
<TargetingPackName>Microsoft.AspNetCore.App.Ref</TargetingPackName>
<TargetingPackVersion>$(LatestAspNetCoreReferenceVersion)</TargetingPackVersion>
<RuntimePackNamePatterns>Microsoft.AspNetCore.App.Runtime.**RID**</RuntimePackNamePatterns>
<RuntimePackRuntimeIdentifiers>linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86</RuntimePackRuntimeIdentifiers>
<IsTrimmable>true</IsTrimmable>
</KnownFrameworkReference>
</ItemGroup>
</Target>
<ItemGroup>
<!-- Add a project dependency without reference output assemblies to enforce build order -->
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->
@ -35,7 +15,29 @@
Private="false" />
</ItemGroup>
<Import Project="Blazor\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
<Import Project="WebAssembly\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" />
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
<ItemGroup Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'">
<ProjectReference
Include="$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj"
CopyLocal="false" />
</ItemGroup>
<Target Name="_FixupReferenceToWebAssemblyHttpHandler"
Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'"
AfterTargets="_ResolveBlazorInputs"
BeforeTargets="_ResolveBlazorOutputs">
<!--
ProjectReference doesn't really play well with IncludeAssets behavior you get when referencing
the package with IncludeAssets="compile".
-->
<ItemGroup>
<_HttpHandlerAssembly Include="@(_BlazorUserRuntimeAssembly)"
Condition="%(_BlazorUserRuntimeAssembly.ProjectReferenceOriginalItemSpec) == '$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj'" />
<_BlazorUserRuntimeAssembly Remove="@(_HttpHandlerAssembly)" />
</ItemGroup>
</Target>
</Project>

View File

@ -12,7 +12,6 @@
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" />
</ItemGroup>
</Project>

View File

@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Components.Web
public static readonly string EnableNavigationInterception = Prefix + "enableNavigationInterception";
public static readonly string GetLocationHref = Prefix + "getLocationHref";
public static readonly string GetLocationHref = Prefix + "getUnmarshalledLocationHref";
public static readonly string GetBaseUri = Prefix + "getBaseURI";
public static readonly string GetBaseUri = Prefix + "getUnmarshalledBaseURI";
public static readonly string NavigateTo = Prefix + "navigateTo";
}

View File

@ -26,8 +26,6 @@
Private="false" />
</ItemGroup>
<Target Name="GetTargetPath" />
<!-- Workaround strange issues with something calling these targets. -->
<Target Name="GetTargetFramework" />
<Target Name="GetCopyToPublishDirectoryItems" />

View File

@ -17,19 +17,19 @@
"@microsoft/signalr": "link:../../SignalR/clients/ts/signalr",
"@microsoft/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack",
"@microsoft/dotnet-js-interop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@microsoft/dotnet-js-interop/-/@microsoft/dotnet-js-interop-5.0.0-alpha1.19572.2.tgz",
"@types/emscripten": "0.0.31",
"@types/jest": "^24.0.6",
"@types/emscripten": "^1.39.3",
"@types/jest": "^24.9.1",
"@types/jsdom": "11.0.6",
"@typescript-eslint/eslint-plugin": "^1.5.0",
"@typescript-eslint/parser": "^1.5.0",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"eslint": "^5.16.0",
"jest": "^24.8.0",
"rimraf": "^2.6.2",
"ts-jest": "^24.0.0",
"jest": "^24.9.0",
"rimraf": "^2.7.1",
"ts-jest": "^24.3.0",
"ts-loader": "^4.4.1",
"typescript": "^3.5.3",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6"
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
"resolutions": {
"**/set-value": "^2.0.1"

View File

@ -0,0 +1,47 @@
const uint64HighPartShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
export function readInt32LE(buffer: Uint8Array, position: number): any {
return (buffer[position])
| (buffer[position + 1] << 8)
| (buffer[position + 2] << 16)
| (buffer[position + 3] << 24);
}
export function readUint32LE(buffer: Uint8Array, position: number): any {
return (buffer[position])
+ (buffer[position + 1] << 8)
+ (buffer[position + 2] << 16)
+ ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
}
export function readUint64LE(buffer: Uint8Array, position: number): any {
// This cannot be done using bit-shift operators in JavaScript, because
// those all implicitly convert to int32
const highPart = readUint32LE(buffer, position + 4);
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}
return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
}
export function readLEB128(buffer: Uint8Array, position: number) {
let result = 0;
let shift = 0;
for (let index = 0; index < 4; index++) {
const byte = buffer[position + index];
result |= (byte & 127) << shift;
if (byte < 128) {
break;
}
shift += 7;
}
return result;
}
export function numLEB128Bytes(value: number) {
return value < 128 ? 1
: value < 16384 ? 2
: value < 2097152 ? 3 : 4;
}

Some files were not shown because too many files have changed in this diff Show More