Merge branch 'release/3.1'

This commit is contained in:
Brennan Conroy 2019-10-31 09:47:59 -07:00
commit 5a7cd976c9
67 changed files with 8835 additions and 5228 deletions

View File

@ -65,7 +65,10 @@ variables:
valule: test valule: test
- name: _PublishArgs - name: _PublishArgs
value: '' value: ''
# used for post-build phases, internal builds only
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- group: DotNet-AspNet-SDLValidation-Params
stages: stages:
- stage: build - stage: build
displayName: Build displayName: Build
@ -654,3 +657,17 @@ stages:
enableSymbolValidation: false enableSymbolValidation: false
enableSigningValidation: false enableSigningValidation: false
publishInstallersAndChecksums: true publishInstallersAndChecksums: true
# This is to enable SDL runs part of Post-Build Validation Stage
SDLValidationParameters:
enable: true
continueOnError: false
params: ' -SourceToolsList @("policheck","credscan")
-TsaInstanceURL $(_TsaInstanceURL)
-TsaProjectName $(_TsaProjectName)
-TsaNotificationEmail $(_TsaNotificationEmail)
-TsaCodebaseAdmin $(_TsaCodebaseAdmin)
-TsaBugAreaPath $(_TsaBugAreaPath)
-TsaIterationPath $(_TsaIterationPath)
-TsaRepositoryName "AspNetCore"
-TsaCodebaseName "AspNetCore"
-TsaPublish $True'

View File

@ -404,6 +404,7 @@
</ProductDependencies> </ProductDependencies>
<ToolsetDependencies> <ToolsetDependencies>
<!-- Listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 --> <!-- Listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
<<<<<<< HEAD
<Dependency Name="Microsoft.NETCore.Platforms" Version="5.0.0-alpha1.19516.16" CoherentParentDependency="Microsoft.NETCore.App.Runtime.win-x64"> <Dependency Name="Microsoft.NETCore.Platforms" Version="5.0.0-alpha1.19516.16" CoherentParentDependency="Microsoft.NETCore.App.Runtime.win-x64">
<Uri>https://github.com/dotnet/corefx</Uri> <Uri>https://github.com/dotnet/corefx</Uri>
<Sha>b9186cfa3566ee24e5f16e45c542a3078e128dc6</Sha> <Sha>b9186cfa3566ee24e5f16e45c542a3078e128dc6</Sha>
@ -411,6 +412,15 @@
<Dependency Name="Internal.AspNetCore.Analyzers" Version="5.0.0-alpha1.19517.7" CoherentParentDependency="Microsoft.EntityFrameworkCore"> <Dependency Name="Internal.AspNetCore.Analyzers" Version="5.0.0-alpha1.19517.7" CoherentParentDependency="Microsoft.EntityFrameworkCore">
<Uri>https://github.com/aspnet/Extensions</Uri> <Uri>https://github.com/aspnet/Extensions</Uri>
<Sha>f15f3278c73c72a6546d7cb2c1bd54a541ffc83e</Sha> <Sha>f15f3278c73c72a6546d7cb2c1bd54a541ffc83e</Sha>
=======
<Dependency Name="Microsoft.NETCore.Platforms" Version="3.1.0-preview3.19530.2" CoherentParentDependency="Microsoft.NETCore.App.Runtime.win-x64">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>d29d1bf34dea385124d076405ccb66ccfd173a86</Sha>
</Dependency>
<Dependency Name="Internal.AspNetCore.Analyzers" Version="3.1.0-preview3.19531.1" CoherentParentDependency="Microsoft.EntityFrameworkCore">
<Uri>https://github.com/aspnet/Extensions</Uri>
<Sha>e43eaf0709fab3d8305449e3ae933420a8b913bc</Sha>
>>>>>>> release/3.1
</Dependency> </Dependency>
<Dependency Name="Microsoft.DotNet.GenAPI" Version="1.0.0-beta.19517.3"> <Dependency Name="Microsoft.DotNet.GenAPI" Version="1.0.0-beta.19517.3">
<Uri>https://github.com/dotnet/arcade</Uri> <Uri>https://github.com/dotnet/arcade</Uri>
@ -424,6 +434,7 @@
<Uri>https://github.com/dotnet/arcade</Uri> <Uri>https://github.com/dotnet/arcade</Uri>
<Sha>a42a124635ce1a218309ecb31ec59d559cacb886</Sha> <Sha>a42a124635ce1a218309ecb31ec59d559cacb886</Sha>
</Dependency> </Dependency>
<<<<<<< HEAD
<Dependency Name="Microsoft.AspNetCore.Testing" Version="5.0.0-alpha1.19517.7" CoherentParentDependency="Microsoft.EntityFrameworkCore"> <Dependency Name="Microsoft.AspNetCore.Testing" Version="5.0.0-alpha1.19517.7" CoherentParentDependency="Microsoft.EntityFrameworkCore">
<Uri>https://github.com/aspnet/Extensions</Uri> <Uri>https://github.com/aspnet/Extensions</Uri>
<Sha>f15f3278c73c72a6546d7cb2c1bd54a541ffc83e</Sha> <Sha>f15f3278c73c72a6546d7cb2c1bd54a541ffc83e</Sha>
@ -431,6 +442,15 @@
<Dependency Name="Microsoft.Net.Compilers.Toolset" Version="3.4.0-beta1-19456-03" CoherentParentDependency="Microsoft.Extensions.Logging"> <Dependency Name="Microsoft.Net.Compilers.Toolset" Version="3.4.0-beta1-19456-03" CoherentParentDependency="Microsoft.Extensions.Logging">
<Uri>https://github.com/dotnet/roslyn</Uri> <Uri>https://github.com/dotnet/roslyn</Uri>
<Sha>3c865821f2864393a0ff7fe22c92ded6d51a546c</Sha> <Sha>3c865821f2864393a0ff7fe22c92ded6d51a546c</Sha>
=======
<Dependency Name="Microsoft.AspNetCore.Testing" Version="3.1.0-preview3.19531.1" CoherentParentDependency="Microsoft.EntityFrameworkCore">
<Uri>https://github.com/aspnet/Extensions</Uri>
<Sha>e43eaf0709fab3d8305449e3ae933420a8b913bc</Sha>
</Dependency>
<Dependency Name="Microsoft.Net.Compilers.Toolset" Version="3.5.0-beta1-19531-01" CoherentParentDependency="Microsoft.Extensions.Logging">
<Uri>https://github.com/dotnet/roslyn</Uri>
<Sha>d54455f982927b219209b63ba09c21b3f059be1d</Sha>
>>>>>>> release/3.1
</Dependency> </Dependency>
</ToolsetDependencies> </ToolsetDependencies>
</Dependencies> </Dependencies>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
@ -27,6 +28,7 @@ namespace Microsoft.AspNetCore.Analyzers
{ {
MiddlewareItem? useAuthorizationItem = default; MiddlewareItem? useAuthorizationItem = default;
MiddlewareItem? useRoutingItem = default; MiddlewareItem? useRoutingItem = default;
MiddlewareItem? useEndpoint = default;
var length = middlewareAnalysis.Middleware.Length; var length = middlewareAnalysis.Middleware.Length;
for (var i = length - 1; i >= 0; i-- ) for (var i = length - 1; i >= 0; i-- )
@ -70,9 +72,24 @@ namespace Microsoft.AspNetCore.Analyzers
useAuthorizationItem.Operation.Syntax.GetLocation(), useAuthorizationItem.Operation.Syntax.GetLocation(),
middlewareItem.UseMethod.Name)); middlewareItem.UseMethod.Name));
} }
useEndpoint = middlewareItem;
} }
else if (middleware == "UseRouting") else if (middleware == "UseRouting")
{ {
if (useEndpoint is null)
{
// We're likely here because the middleware uses an expression chain e.g.
// app.UseRouting()
// .UseAuthorization()
// .UseEndpoints(..));
// This analyzer expects MiddlewareItem instances to appear in the order in which they appear in source
// which unfortunately isn't true for chained calls (the operations appear in reverse order).
// We'll avoid doing any analysis in this event and rely on the runtime guardrails.
// We'll use https://github.com/aspnet/AspNetCore/issues/16648 to track addressing this in a future milestone
return;
}
useRoutingItem = middlewareItem; useRoutingItem = middlewareItem;
} }
} }

View File

@ -244,6 +244,22 @@ namespace Microsoft.AspNetCore.Analyzers
Assert.Empty(diagnostics); Assert.Empty(diagnostics);
} }
[Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredAsAChain_ReportsNoDiagnostics()
{
// Regression test for https://github.com/aspnet/AspNetCore/issues/15203
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectlyChained));
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Empty(diagnostics);
}
[Fact] [Fact]
public async Task StartupAnalyzer_UseAuthorizationInvokedMultipleTimesInEndpointRoutingBlock_ReportsNoDiagnostics() public async Task StartupAnalyzer_UseAuthorizationInvokedMultipleTimesInEndpointRoutingBlock_ReportsNoDiagnostics()
{ {
@ -279,6 +295,23 @@ namespace Microsoft.AspNetCore.Analyzers
}); });
} }
[Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRoutingChained_ReportsDiagnostics()
{
// This one asserts a false negative for https://github.com/aspnet/AspNetCore/issues/15203.
// We don't correctly identify chained calls, this test verifies the behavior.
// Arrange
var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRoutingChained));
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.NotEmpty(middlewareAnalysis.Middleware);
Assert.Empty(diagnostics);
}
[Fact] [Fact]
public async Task StartupAnalyzer_UseAuthorizationConfiguredAfterUseEndpoints_ReportsDiagnostics() public async Task StartupAnalyzer_UseAuthorizationConfiguredAfterUseEndpoints_ReportsDiagnostics()
{ {

View File

@ -0,0 +1,17 @@
// Copyright (c) .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.Builder;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest {
public class UseAuthBeforeUseRoutingChained
{
public void Configure(IApplicationBuilder app)
{
app.UseFileServer()
.UseAuthorization()
.UseRouting()
.UseEndpoints(r => { });
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .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.Builder;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest {
public class UseAuthConfiguredCorrectlyChained
{
public void Configure(IApplicationBuilder app)
{
app.UseRouting()
.UseAuthorization()
.UseEndpoints(r => { });
}
}
}

View File

@ -54,13 +54,9 @@ namespace Microsoft.AspNetCore.Blazor.Http
SameOrigin = 1, SameOrigin = 1,
Include = 2, Include = 2,
} }
public partial class WebAssemblyHttpMessageHandler : System.Net.Http.HttpMessageHandler public static partial class WebAssemblyHttpMessageHandlerOptions
{ {
public const string FetchArgs = "WebAssemblyHttpMessageHandler.FetchArgs"; public static Microsoft.AspNetCore.Blazor.Http.FetchCredentialsOption DefaultCredentials { get { throw null; } set { } }
public WebAssemblyHttpMessageHandler() { }
public static Microsoft.AspNetCore.Blazor.Http.FetchCredentialsOption DefaultCredentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[System.Diagnostics.DebuggerStepThroughAttribute]
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
} }
} }
namespace Microsoft.AspNetCore.Blazor.Rendering namespace Microsoft.AspNetCore.Blazor.Rendering

View File

@ -2,11 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Net.Http;
using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Http;
using Microsoft.AspNetCore.Blazor.Rendering; using Microsoft.AspNetCore.Blazor.Rendering;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@ -30,16 +27,6 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
public Task StartAsync(CancellationToken cancellationToken = default) public Task StartAsync(CancellationToken cancellationToken = default)
{ {
// We need to do this as early as possible, it eliminates a bunch of problems. Note that what we do
// is a bit fragile. If you see things breaking because JSRuntime.Current isn't set, then it's likely
// that something on the startup path went wrong.
//
// We want to the JSRuntime created here to be the 'ambient' runtime when JS calls back into .NET. When
// this happens in the browser it will be a direct call from Mono. We effectively needs to set the
// JSRuntime in the 'root' execution context which implies that we want to do as part of a direct
// call from Program.Main, and before any 'awaits'.
SetBrowserHttpMessageHandlerAsDefault();
return StartAsyncAwaited(); return StartAsyncAwaited();
} }
@ -102,35 +89,5 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
{ {
(Services as IDisposable)?.Dispose(); (Services as IDisposable)?.Dispose();
} }
private static void SetBrowserHttpMessageHandlerAsDefault()
{
// Within the Mono WebAssembly BCL, this is a special private static field
// that can be assigned to override the default handler
const string getHttpMessageHandlerFieldName = "GetHttpMessageHandler";
var getHttpMessageHandlerField = typeof(HttpClient).GetField(
getHttpMessageHandlerFieldName,
BindingFlags.Static | BindingFlags.NonPublic);
// getHttpMessageHandlerField will be null in tests, but nonnull when actually
// running under Mono WebAssembly
if (getHttpMessageHandlerField != null)
{
// Just in case you're not actually using HttpClient, defer the construction
// of the WebAssemblyHttpMessageHandler
var handlerSingleton = new Lazy<HttpMessageHandler>(
() => new WebAssemblyHttpMessageHandler());
Func<HttpMessageHandler> handlerFactory = () => handlerSingleton.Value;
getHttpMessageHandlerField.SetValue(null, handlerFactory);
}
else
{
// We log a warning in case this ever happens at runtime (even though there's
// no obvious way it could be possible), but don't actually throw because that
// would break unit tests
Console.WriteLine("WARNING: Could not set default HttpMessageHandler because " +
$"'{getHttpMessageHandlerFieldName}' was not found on '{typeof(HttpClient).FullName}'.");
}
}
} }
} }

View File

@ -1,204 +0,0 @@
// Copyright (c) .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 System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Components;
namespace Microsoft.AspNetCore.Blazor.Http
{
/// <summary>
/// A browser-compatible implementation of <see cref="HttpMessageHandler"/>
/// </summary>
public class WebAssemblyHttpMessageHandler : HttpMessageHandler
{
/// <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; set; }
= FetchCredentialsOption.SameOrigin;
private static readonly object _idLock = new object();
private static readonly IDictionary<int, TaskCompletionSource<HttpResponseMessage>> _pendingRequests
= new Dictionary<int, TaskCompletionSource<HttpResponseMessage>>();
private static int _nextRequestId = 0;
/// <summary>
/// The name of a well-known property that can be added to <see cref="HttpRequestMessage.Properties"/>
/// to control the arguments passed to the underlying JavaScript <code>fetch</code> API.
/// </summary>
public const string FetchArgs = "WebAssemblyHttpMessageHandler.FetchArgs";
/// <inheritdoc />
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
cancellationToken.Register(() => tcs.TrySetCanceled());
int id;
lock (_idLock)
{
id = _nextRequestId++;
_pendingRequests.Add(id, tcs);
}
var options = new FetchOptions();
if (request.Properties.TryGetValue(FetchArgs, out var fetchArgs))
{
options.RequestInitOverrides = fetchArgs;
}
options.RequestInit = new RequestInit
{
Credentials = GetDefaultCredentialsString(),
Headers = GetHeaders(request),
Method = request.Method.Method
};
options.RequestUri = request.RequestUri.ToString();
WebAssemblyJSRuntime.Instance.InvokeUnmarshalled<int, byte[], string, object>(
"Blazor._internal.http.sendAsync",
id,
request.Content == null ? null : await request.Content.ReadAsByteArrayAsync(),
JsonSerializer.Serialize(options, JsonSerializerOptionsProvider.Options));
return await tcs.Task;
}
/// <remarks>
/// While it may be tempting to remove this method because it appears to be unused,
/// this method is referenced by client code and must persist.
/// </remarks>
#pragma warning disable IDE0051 // Remove unused private members
private static void ReceiveResponse(
#pragma warning restore IDE0051 // Remove unused private members
string id,
string responseDescriptorJson,
byte[] responseBodyData,
string errorText)
{
TaskCompletionSource<HttpResponseMessage> tcs;
var idVal = int.Parse(id);
lock (_idLock)
{
tcs = _pendingRequests[idVal];
_pendingRequests.Remove(idVal);
}
if (errorText != null)
{
tcs.SetException(new HttpRequestException(errorText));
}
else
{
var responseDescriptor = JsonSerializer.Deserialize<ResponseDescriptor>(responseDescriptorJson, JsonSerializerOptionsProvider.Options);
var responseContent = responseBodyData == null ? null : new ByteArrayContent(responseBodyData);
var responseMessage = responseDescriptor.ToResponseMessage(responseContent);
tcs.SetResult(responseMessage);
}
}
/// <remarks>
/// While it may be tempting to remove this method because it appears to be unused,
/// this method is referenced by client code and must persist.
/// </remarks>
#pragma warning disable IDE0051 // Remove unused private members
private static byte[] AllocateArray(string length) => new byte[int.Parse(length)];
#pragma warning restore IDE0051 // Remove unused private members
private static IReadOnlyList<Header> GetHeaders(HttpRequestMessage request)
{
var requestHeaders = request.Headers.AsEnumerable();
if (request.Content?.Headers != null)
{
requestHeaders = requestHeaders.Concat(request.Content.Headers);
}
var headers = new List<Header>();
foreach (var item in requestHeaders)
{
foreach (var headerValue in item.Value)
{
headers.Add(new Header { Name = item.Key, Value = headerValue });
}
}
return headers;
}
private static string GetDefaultCredentialsString()
{
// See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials for
// standard values and meanings
switch (DefaultCredentials)
{
case FetchCredentialsOption.Omit:
return "omit";
case FetchCredentialsOption.SameOrigin:
return "same-origin";
case FetchCredentialsOption.Include:
return "include";
default:
throw new ArgumentException($"Unknown credentials option '{DefaultCredentials}'.");
}
}
// Keep these in sync with TypeScript class in Http.ts
private class FetchOptions
{
public string RequestUri { get; set; }
public RequestInit RequestInit { get; set; }
public object RequestInitOverrides { get; set; }
}
private class RequestInit
{
public string Credentials { get; set; }
public IReadOnlyList<Header> Headers { get; set; }
public string Method { get; set; }
}
private class ResponseDescriptor
{
#pragma warning disable 0649
public int StatusCode { get; set; }
public string StatusText { get; set; }
public IReadOnlyList<Header> Headers { get; set; }
#pragma warning restore 0649
public HttpResponseMessage ToResponseMessage(HttpContent content)
{
var result = new HttpResponseMessage((HttpStatusCode)StatusCode);
result.ReasonPhrase = StatusText;
result.Content = content;
var headers = result.Headers;
var contentHeaders = result.Content?.Headers;
foreach (var pair in Headers)
{
if (!headers.TryAddWithoutValidation(pair.Name, pair.Value))
{
contentHeaders?.TryAddWithoutValidation(pair.Name, pair.Value);
}
}
return result;
}
}
private class Header
{
public string Name { get; set; }
public string Value { get; set; }
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .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

@ -422,7 +422,7 @@
4) Add the file we just created to the list of file writes, to support incremental builds. 4) Add the file we just created to the list of file writes, to support incremental builds.
--> -->
<ItemGroup> <ItemGroup>
<_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath)" /> <_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath);$(MonoWasmFrameworkPath)" />
<_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a &quot;%(Identity)&quot;')" /> <_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a &quot;%(Identity)&quot;')" />
<_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a &quot;%(FullPath)&quot;')" /> <_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a &quot;%(FullPath)&quot;')" />
<_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d &quot;%(Identity)&quot;')" /> <_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d &quot;%(Identity)&quot;')" />
@ -528,7 +528,7 @@
<PropertyGroup> <PropertyGroup>
<_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references &quot;$(BlazorResolveDependenciesFilePath)&quot;</_ReferencesArg> <_ReferencesArg Condition="'@(_BlazorDependencyInput)' != ''">--references &quot;$(BlazorResolveDependenciesFilePath)&quot;</_ReferencesArg>
<_BclParameter>--base-class-library &quot;$(MonoBaseClassLibraryPath)&quot; --base-class-library &quot;$(MonoBaseClassLibraryFacadesPath)&quot;</_BclParameter> <_BclParameter>--base-class-library &quot;$(MonoBaseClassLibraryPath)&quot; --base-class-library &quot;$(MonoBaseClassLibraryFacadesPath)&quot; --base-class-library &quot;$(MonoWasmFrameworkPath)&quot;</_BclParameter>
</PropertyGroup> </PropertyGroup>
<WriteLinesToFile <WriteLinesToFile

View File

@ -18,4 +18,10 @@
<type fullname="System.ComponentModel.GuidConverter" /> <type fullname="System.ComponentModel.GuidConverter" />
<type fullname="System.ComponentModel.TimeSpanConverter" /> <type fullname="System.ComponentModel.TimeSpanConverter" />
</assembly> </assembly>
<assembly fullname="WebAssembly.Net.Http">
<!-- Without this, the setter for DefaultCredentials would be removed, but we need it -->
<type fullname="WebAssembly.Net.Http.HttpClient.FetchCredentialsOption" />
<type fullname="WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler" />
</assembly>
</linker> </linker>

View File

@ -40,6 +40,7 @@
<ItemGroup> <ItemGroup>
<_BclDirectory Include="$(MonoBaseClassLibraryPath)" /> <_BclDirectory Include="$(MonoBaseClassLibraryPath)" />
<_BclDirectory Include="$(MonoBaseClassLibraryFacadesPath)" /> <_BclDirectory Include="$(MonoBaseClassLibraryFacadesPath)" />
<_BclDirectory Include="$(MonoWasmFrameworkPath)" />
</ItemGroup> </ItemGroup>
<WriteLinesToFile Lines="@(ReferencePath)" File="$(TargetDir)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" /> <WriteLinesToFile Lines="@(ReferencePath)" File="$(TargetDir)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />

View File

@ -488,7 +488,7 @@ namespace WsProxy {
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null, // results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case. // so skip returning variable values in that case.
while (values != null && i < var_ids.Length && i < values.Length) { while (values != null && i < vars.Length && i < values.Length) {
var value = values [i] ["value"]; var value = values [i] ["value"];
if (((string)value ["description"]) == null) if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString(); value ["description"] = value ["value"]?.ToString();

View File

@ -20,7 +20,9 @@
'mscorlib.dll', 'mscorlib.dll',
'System.dll', 'System.dll',
'System.Core.dll', 'System.Core.dll',
'System.Net.Http.dll' 'System.Net.Http.dll',
'WebAssembly.Bindings.dll',
'WebAssembly.Net.Http.dll'
]); ]);
// For these tests we're using Mono's built-in mono_load_runtime_and_bcl util. // For these tests we're using Mono's built-in mono_load_runtime_and_bcl util.

View File

@ -11,14 +11,6 @@ namespace MonoSanityClient
{ {
public static class Examples public static class Examples
{ {
static Examples()
{
// We have to populate GetHttpMessageHandler with something (like the real
// Blazor web assembly host does), otherwise HttpClientHandler's constructor
// gets into an infinite loop.
FakeHttpMessageHandler.Attach();
}
public static string AddNumbers(int a, int b) public static string AddNumbers(int a, int b)
=> (a + b).ToString(); => (a + b).ToString();

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;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace MonoSanityClient
{
class FakeHttpMessageHandler : HttpMessageHandler
{
public static void Attach()
{
var getHttpMessageHandlerField = typeof(HttpClient).GetField(
"GetHttpMessageHandler",
BindingFlags.Static | BindingFlags.NonPublic);
Func<HttpMessageHandler> handlerFactory = () => new FakeHttpMessageHandler();
getHttpMessageHandlerField.SetValue(null, handlerFactory);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
=> throw new NotImplementedException($"{nameof(FakeHttpMessageHandler)} cannot {nameof(SendAsync)}.");
}
}

View File

@ -581,6 +581,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param> /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
public void OpenRegion(int sequence) public void OpenRegion(int sequence)
{ {
// We are entering a new scope, since we track the "duplicate attributes" per
// element/component we might need to clean them up now.
if (_hasSeenAddMultipleAttributes)
{
var indexOfLastElementOrComponent = _openElementIndices.Peek();
ProcessDuplicateAttributes(first: indexOfLastElementOrComponent + 1);
}
_openElementIndices.Push(_entries.Count); _openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.Region(sequence)); Append(RenderTreeFrame.Region(sequence));
} }

View File

@ -298,6 +298,39 @@ namespace Microsoft.AspNetCore.Components.Rendering
frame => AssertFrame.Attribute(frame, "attribute7", "the end")); frame => AssertFrame.Attribute(frame, "attribute7", "the end"));
} }
[Fact]
public void CanAddMultipleAttributes_WithChildRegion()
{
// This represents bug https://github.com/aspnet/AspNetCore/issues/16570
// If a sequence of attributes is terminated by a call to builder.OpenRegion,
// then the attribute deduplication logic wasn't working correctly
// Arrange
var builder = new RenderTreeBuilder();
// Act
builder.OpenElement(0, "myelement");
builder.AddAttribute(0, "attribute1", "value1");
builder.AddMultipleAttributes(1, new Dictionary<string, object>()
{
{ "attribute1", "value2" },
});
builder.OpenRegion(2);
builder.OpenElement(3, "child");
builder.CloseElement();
builder.CloseRegion();
builder.CloseElement();
// Assert
var frames = builder.GetFrames().AsEnumerable().ToArray();
Assert.Collection(
frames,
frame => AssertFrame.Element(frame, "myelement", 4),
frame => AssertFrame.Attribute(frame, "attribute1", "value2"),
frame => AssertFrame.Region(frame, 2, 2),
frame => AssertFrame.Element(frame, "child", 1, 3));
}
[Fact] [Fact]
public void CanAddMultipleAttributes_DictionaryObject() public void CanAddMultipleAttributes_DictionaryObject()
{ {

View File

@ -122,6 +122,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
// Report errors asynchronously. InitializeAsync is designed not to throw. // Report errors asynchronously. InitializeAsync is designed not to throw.
Log.InitializationFailed(_logger, ex); Log.InitializationFailed(_logger, ex);
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex), ex);
} }
}); });
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -153,7 +154,9 @@ namespace Microsoft.AspNetCore.Components.Server
string unprotected; string unprotected;
try try
{ {
unprotected = _dataProtector.Unprotect(record.Descriptor); var payload = Convert.FromBase64String(record.Descriptor);
var unprotectedBytes = _dataProtector.Unprotect(payload);
unprotected = Encoding.UTF8.GetString(unprotectedBytes);
} }
catch (Exception e) catch (Exception e)
{ {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ import { getAssemblyNameFromUrl } from './Platform/Url';
import { renderBatch } from './Rendering/Renderer'; import { renderBatch } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { Pointer } from './Platform/Platform'; import { Pointer } from './Platform/Platform';
import { fetchBootConfigAsync, loadEmbeddedResourcesAsync, shouldAutoStart } from './BootCommon'; import { shouldAutoStart } from './BootCommon';
import { setEventDispatcher } from './Rendering/RendererEventDispatcher'; import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
let started = false; let started = false;
@ -64,6 +64,46 @@ async function boot(options?: any): Promise<void> {
platform.callEntryPoint(mainAssemblyName, bootConfig.entryPoint, []); platform.callEntryPoint(mainAssemblyName, bootConfig.entryPoint, []);
} }
async function fetchBootConfigAsync() {
// Later we might make the location of this configurable (e.g., as an attribute on the <script>
// element that's importing this file), but currently there isn't a use case for that.
const bootConfigResponse = await fetch('_framework/blazor.boot.json', { method: 'Get', credentials: 'include' });
return bootConfigResponse.json() as Promise<BootJsonData>;
}
function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise<any> {
const cssLoadingPromises = bootConfig.cssReferences.map(cssReference => {
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = cssReference;
return loadResourceFromElement(linkElement);
});
const jsLoadingPromises = bootConfig.jsReferences.map(jsReference => {
const scriptElement = document.createElement('script');
scriptElement.src = jsReference;
return loadResourceFromElement(scriptElement);
});
return Promise.all(cssLoadingPromises.concat(jsLoadingPromises));
}
function loadResourceFromElement(element: HTMLElement) {
return new Promise((resolve, reject) => {
element.onload = resolve;
element.onerror = reject;
document.head!.appendChild(element);
});
}
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
interface BootJsonData {
main: string;
entryPoint: string;
assemblyReferences: string[];
cssReferences: string[];
jsReferences: string[];
linkerEnabled: boolean;
}
window['Blazor'].start = boot; window['Blazor'].start = boot;
if (shouldAutoStart()) { if (shouldAutoStart()) {
boot(); boot();

View File

@ -1,43 +1,3 @@
export async function fetchBootConfigAsync() {
// Later we might make the location of this configurable (e.g., as an attribute on the <script>
// element that's importing this file), but currently there isn't a use case for that.
const bootConfigResponse = await fetch('_framework/blazor.boot.json', { method: 'Get', credentials: 'include' });
return bootConfigResponse.json() as Promise<BootJsonData>;
}
export function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise<any> {
const cssLoadingPromises = bootConfig.cssReferences.map(cssReference => {
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = cssReference;
return loadResourceFromElement(linkElement);
});
const jsLoadingPromises = bootConfig.jsReferences.map(jsReference => {
const scriptElement = document.createElement('script');
scriptElement.src = jsReference;
return loadResourceFromElement(scriptElement);
});
return Promise.all(cssLoadingPromises.concat(jsLoadingPromises));
}
function loadResourceFromElement(element: HTMLElement) {
return new Promise((resolve, reject) => {
element.onload = resolve;
element.onerror = reject;
document.head!.appendChild(element);
});
}
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
interface BootJsonData {
main: string;
entryPoint: string;
assemblyReferences: string[];
cssReferences: string[];
jsReferences: string[];
linkerEnabled: boolean;
}
// Tells you if the script was added without <script src="..." autostart="false"></script> // Tells you if the script was added without <script src="..." autostart="false"></script>
export function shouldAutoStart() { export function shouldAutoStart() {
return !!(document && return !!(document &&

View File

@ -1,5 +1,4 @@
import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager'; import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
import { internalFunctions as httpInternalFunctions } from './Services/Http';
import { attachRootComponentToElement } from './Rendering/Renderer'; import { attachRootComponentToElement } from './Rendering/Renderer';
// Make the following APIs available in global scope for invocation from JS // Make the following APIs available in global scope for invocation from JS
@ -8,7 +7,6 @@ window['Blazor'] = {
_internal: { _internal: {
attachRootComponentToElement, attachRootComponentToElement,
http: httpInternalFunctions,
navigationManager: navigationManagerInternalFunctions, navigationManager: navigationManagerInternalFunctions,
}, },
}; };

View File

@ -21,6 +21,8 @@ const nonBubblingEvents = toLookup([
'DOMNodeRemovedFromDocument', 'DOMNodeRemovedFromDocument',
]); ]);
const disableableEventNames = toLookup(['click', 'dblclick', 'mousedown', 'mousemove', 'mouseup']);
export interface OnEventCallback { export interface OnEventCallback {
(event: Event, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>, eventFieldInfo: EventFieldInfo | null): void; (event: Event, eventHandlerId: number, eventArgs: EventForDotNet<UIEventArgs>, eventFieldInfo: EventFieldInfo | null): void;
} }
@ -107,7 +109,7 @@ export class EventDelegator {
const handlerInfos = this.getEventHandlerInfosForElement(candidateElement, false); const handlerInfos = this.getEventHandlerInfosForElement(candidateElement, false);
if (handlerInfos) { if (handlerInfos) {
const handlerInfo = handlerInfos.getHandler(evt.type); const handlerInfo = handlerInfos.getHandler(evt.type);
if (handlerInfo) { if (handlerInfo && !eventIsDisabledOnElement(candidateElement, evt.type)) {
// We are going to raise an event for this element, so prepare info needed by the .NET code // We are going to raise an event for this element, so prepare info needed by the .NET code
if (!eventArgs) { if (!eventArgs) {
eventArgs = EventForDotNet.fromDOMEvent(evt); eventArgs = EventForDotNet.fromDOMEvent(evt);
@ -269,3 +271,11 @@ function toLookup(items: string[]): { [key: string]: boolean } {
}); });
return result; return result;
} }
function eventIsDisabledOnElement(element: Element, eventName: string): boolean {
// We want to replicate the normal DOM event behavior that, for 'interactive' elements
// with a 'disabled' attribute, certain mouse events are suppressed
return (element instanceof HTMLButtonElement || element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
&& disableableEventNames.hasOwnProperty(eventName)
&& element.disabled;
}

View File

@ -1,132 +0,0 @@
import { platform } from '../Environment';
import { MethodHandle, System_String, System_Array } from '../Platform/Platform';
const httpClientAssembly = 'Microsoft.AspNetCore.Blazor';
const httpClientNamespace = `${httpClientAssembly}.Http`;
const httpClientTypeName = 'WebAssemblyHttpMessageHandler';
let receiveResponseMethod: MethodHandle;
let allocateArrayMethod: MethodHandle;
// These are the functions we're making available for invocation from .NET
export const internalFunctions = {
sendAsync,
};
async function sendAsync(id: number, body: System_Array<any>, jsonFetchArgs: System_String) {
let response: Response;
let responseData: ArrayBuffer;
const fetchOptions: FetchOptions = JSON.parse(platform.toJavaScriptString(jsonFetchArgs));
const requestInit: RequestInit = Object.assign(convertToRequestInit(fetchOptions.requestInit), fetchOptions.requestInitOverrides);
if (body) {
requestInit.body = platform.toUint8Array(body);
}
try {
response = await fetch(fetchOptions.requestUri, requestInit);
responseData = await response.arrayBuffer();
} catch (ex) {
dispatchErrorResponse(id, ex.toString());
return;
}
dispatchSuccessResponse(id, response, responseData);
}
function convertToRequestInit(blazorRequestInit: BlazorRequestInit) {
return {
credentials: blazorRequestInit.credentials,
method: blazorRequestInit.method,
headers: blazorRequestInit.headers.map(item => [item.name, item.value])
};
}
function dispatchSuccessResponse(id: number, response: Response, responseData: ArrayBuffer) {
const responseDescriptor: ResponseDescriptor = {
statusCode: response.status,
statusText: response.statusText,
headers: [],
};
response.headers.forEach((value, name) => {
responseDescriptor.headers.push({ name: name, value: value });
});
if (!allocateArrayMethod) {
allocateArrayMethod = platform.findMethod(
httpClientAssembly,
httpClientNamespace,
httpClientTypeName,
'AllocateArray'
);
}
// allocate a managed byte[] of the right size
const dotNetArray = platform.callMethod(allocateArrayMethod, null, [platform.toDotNetString(responseData.byteLength.toString())]) as System_Array<any>;
// get an Uint8Array view of it
const array = platform.toUint8Array(dotNetArray);
// copy the responseData to our managed byte[]
array.set(new Uint8Array(responseData));
dispatchResponse(
id,
platform.toDotNetString(JSON.stringify(responseDescriptor)),
dotNetArray,
/* errorMessage */ null
);
}
function dispatchErrorResponse(id: number, errorMessage: string) {
dispatchResponse(
id,
/* responseDescriptor */ null,
/* responseText */ null,
platform.toDotNetString(errorMessage)
);
}
function dispatchResponse(id: number, responseDescriptor: System_String | null, responseData: System_Array<any> | null, errorMessage: System_String | null) {
if (!receiveResponseMethod) {
receiveResponseMethod = platform.findMethod(
httpClientAssembly,
httpClientNamespace,
httpClientTypeName,
'ReceiveResponse'
);
}
platform.callMethod(receiveResponseMethod, null, [
platform.toDotNetString(id.toString()),
responseDescriptor,
responseData,
errorMessage,
]);
}
// Keep these in sync with the .NET equivalent in WebAssemblyHttpMessageHandler.cs
interface FetchOptions {
requestUri: string;
requestInit: BlazorRequestInit;
requestInitOverrides: RequestInit;
}
interface BlazorRequestInit {
credentials: string;
headers: Header[];
method: string;
}
interface ResponseDescriptor {
// We don't have BodyText in here because if we did, then in the JSON-response case (which
// is the most common case), we'd be double-encoding it, since the entire ResponseDescriptor
// also gets JSON encoded. It would work but is twice the amount of string processing.
statusCode: number;
statusText: string;
headers: Header[];
}
interface Header {
name: string;
value: string;
}

View File

@ -207,6 +207,37 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Browser.Equal("abcdefghijklmn", () => output.Text); Browser.Equal("abcdefghijklmn", () => output.Text);
} }
[Fact]
public void NonInteractiveElementWithDisabledAttributeDoesRespondToMouseEvents()
{
Browser.MountTestComponent<EventDisablingComponent>();
var element = Browser.FindElement(By.Id("disabled-div"));
var eventLog = Browser.FindElement(By.Id("event-log"));
Browser.Equal(string.Empty, () => eventLog.GetAttribute("value"));
element.Click();
Browser.Equal("Got event on div", () => eventLog.GetAttribute("value"));
}
[Theory]
[InlineData("#disabled-button")]
[InlineData("#disabled-button span")]
[InlineData("#disabled-textarea")]
public void InteractiveElementWithDisabledAttributeDoesNotRespondToMouseEvents(string elementSelector)
{
Browser.MountTestComponent<EventDisablingComponent>();
var element = Browser.FindElement(By.CssSelector(elementSelector));
var eventLog = Browser.FindElement(By.Id("event-log"));
Browser.Equal(string.Empty, () => eventLog.GetAttribute("value"));
element.Click();
// It's no use observing that the log is still empty, since maybe the UI just hasn't updated yet
// To be sure that the preceding action has no effect, we need to trigger a different action that does have an effect
Browser.FindElement(By.Id("enabled-button")).Click();
Browser.Equal("Got event on enabled button", () => eventLog.GetAttribute("value"));
}
void SendKeysSequentially(IWebElement target, string text) void SendKeysSequentially(IWebElement target, string text)
{ {
// Calling it for each character works around some chars being skipped // Calling it for each character works around some chars being skipped

View File

@ -89,11 +89,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void CanSendRequestHeaders() public void CanSendRequestHeaders()
{ {
AddRequestHeader("TestHeader", "Value from test"); AddRequestHeader("testheader", "Value from test");
AddRequestHeader("another-header", "Another value"); AddRequestHeader("another-header", "Another value");
IssueRequest("DELETE", "/subdir/api/person"); IssueRequest("DELETE", "/subdir/api/person");
Assert.Equal("OK", _responseStatus.Text); Assert.Equal("OK", _responseStatus.Text);
Assert.Contains("TestHeader: Value from test", _responseBody.Text); Assert.Contains("testheader: Value from test", _responseBody.Text);
Assert.Contains("another-header: Another value", _responseBody.Text); Assert.Contains("another-header: Another value", _responseBody.Text);
} }
@ -107,15 +107,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Assert.Equal("{\"id\":123,\"name\":\"Bert\"}", _responseBody.Text); Assert.Equal("{\"id\":123,\"name\":\"Bert\"}", _responseBody.Text);
} }
[Fact]
public void CanSetRequestReferer()
{
SetValue("request-referrer", "/test-referrer");
IssueRequest("GET", "/subdir/api/person/referrer");
Assert.Equal("OK", _responseStatus.Text);
Assert.EndsWith("/test-referrer", _responseBody.Text);
}
[Fact] [Fact]
public void CanSendAndReceiveCookies() public void CanSendAndReceiveCookies()
{ {

View File

@ -0,0 +1,36 @@
<h3 id="event-disabling">Disabling events</h3>
<p>
<div @onclick="@(() => LogEvent("Got event on div"))" id="disabled-div" disabled style="border: 1px dashed red">Even though this div has a 'disabled' attribute, it still responds to mouse events, because you can't disable a div</div>
</p>
<p>
<button @onclick="@(() => LogEvent("Got event on disabled button"))" id="disabled-button" disabled>Since this button is disabled, it doesn't react to mouse events <span style="border: 1px dashed green">even on its children</span></button>
</p>
<p>
<textarea @onclick="@(() => LogEvent("Got event on disabled textarea"))" id="disabled-textarea" disabled>Since this textarea is disabled, it doesn't react to mouse events</textarea>
</p>
<p>
<button @onclick="@(() => LogEvent("Got event on enabled button"))" id="enabled-button">Since this button is enabled, it does react to mouse events <span style="border: 1px dashed green">and so do its children</span></button>
</p>
<h3>Event log</h3>
<textarea id="event-log" readonly @bind="logValue"></textarea>
<button id="clear-log" @onclick="@(() => { logValue = string.Empty; })">Clear log</button>
@code {
string logValue = string.Empty;
void LogEvent(string message)
{
if (logValue != string.Empty)
{
logValue += Environment.NewLine;
}
logValue += message;
}
}

View File

@ -1,6 +1,5 @@
@using System.Net @using System.Net
@using System.Net.Http @using System.Net.Http
@using Microsoft.AspNetCore.Blazor.Http
@inject HttpClient Http @inject HttpClient Http
<h1>HTTP request tester</h1> <h1>HTTP request tester</h1>
@ -38,11 +37,6 @@
<button id="add-header" @onclick="AddHeader">Add</button> <button id="add-header" @onclick="AddHeader">Add</button>
</p> </p>
<p>
<div>Request referrer:</div>
<input id="request-referrer" type="text" @bind=requestReferrer />
</p>
<button id="send-request" @onclick="DoRequest">Request</button> <button id="send-request" @onclick="DoRequest">Request</button>
@if (responseStatusCode.HasValue) @if (responseStatusCode.HasValue)
@ -65,7 +59,6 @@
string method = "GET"; string method = "GET";
string requestBody = ""; string requestBody = "";
List<RequestHeader> requestHeaders = new List<RequestHeader>(); List<RequestHeader> requestHeaders = new List<RequestHeader>();
string requestReferrer = "";
HttpStatusCode? responseStatusCode; HttpStatusCode? responseStatusCode;
string responseBody; string responseBody;
@ -103,14 +96,6 @@
} }
} }
if (!string.IsNullOrEmpty(requestReferrer))
{
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
referrer = requestReferrer
};
}
var response = await Http.SendAsync(requestMessage); var response = await Http.SendAsync(requestMessage);
responseStatusCode = response.StatusCode; responseStatusCode = response.StatusCode;
responseBody = await response.Content.ReadAsStringAsync(); responseBody = await response.Content.ReadAsStringAsync();

View File

@ -24,6 +24,7 @@
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option> <option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option> <option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
<option value="BasicTestApp.EventCasesComponent">Event cases</option> <option value="BasicTestApp.EventCasesComponent">Event cases</option>
<option value="BasicTestApp.EventDisablingComponent">Event disabling</option>
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option> <option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
<option value="BasicTestApp.ExternalContentPackage">External content package</option> <option value="BasicTestApp.ExternalContentPackage">External content package</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</option> <option value="BasicTestApp.FocusEventComponent">Focus events</option>

View File

@ -30,7 +30,7 @@ namespace BasicTestApp
{ {
// Needed because the test server runs on a different port than the client app, // Needed because the test server runs on a different port than the client app,
// and we want to test sending/receiving cookies underling this config // and we want to test sending/receiving cookies underling this config
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include; WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
} }
app.AddComponent<Index>("root"); app.AddComponent<Index>("root");

View File

@ -2,6 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot) public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot)
{ {
BasePath = new PathString(pathPrefix.StartsWith("/") ? pathPrefix : "/" + pathPrefix); BasePath = NormalizePath(pathPrefix);
InnerProvider = new PhysicalFileProvider(contentRoot); InnerProvider = new PhysicalFileProvider(contentRoot);
} }
@ -40,20 +44,30 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
/// <inheritdoc /> /// <inheritdoc />
public IDirectoryContents GetDirectoryContents(string subpath) public IDirectoryContents GetDirectoryContents(string subpath)
{ {
if (!StartsWithBasePath(subpath, out var physicalPath)) var modifiedSub = NormalizePath(subpath);
{
return NotFoundDirectoryContents.Singleton; if (StartsWithBasePath(modifiedSub, out var physicalPath))
}
else
{ {
return InnerProvider.GetDirectoryContents(physicalPath.Value); return InnerProvider.GetDirectoryContents(physicalPath.Value);
} }
else if (string.Equals(subpath, string.Empty) || string.Equals(modifiedSub, "/"))
{
return new StaticWebAssetsDirectoryRoot(BasePath);
}
else if (BasePath.StartsWithSegments(modifiedSub, FilePathComparison, out var remaining))
{
return new StaticWebAssetsDirectoryRoot(remaining);
}
return NotFoundDirectoryContents.Singleton;
} }
/// <inheritdoc /> /// <inheritdoc />
public IFileInfo GetFileInfo(string subpath) public IFileInfo GetFileInfo(string subpath)
{ {
if (!StartsWithBasePath(subpath, out var physicalPath)) var modifiedSub = NormalizePath(subpath);
if (!StartsWithBasePath(modifiedSub, out var physicalPath))
{ {
return new NotFoundFileInfo(subpath); return new NotFoundFileInfo(subpath);
} }
@ -69,9 +83,69 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
return InnerProvider.Watch(filter); return InnerProvider.Watch(filter);
} }
private static string NormalizePath(string path)
{
path = path.Replace('\\', '/');
return path != null && path.StartsWith("/") ? path : "/" + path;
}
private bool StartsWithBasePath(string subpath, out PathString rest) private bool StartsWithBasePath(string subpath, out PathString rest)
{ {
return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest); return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest);
} }
private class StaticWebAssetsDirectoryRoot : IDirectoryContents
{
private readonly string _nextSegment;
public StaticWebAssetsDirectoryRoot(PathString remainingPath)
{
// We MUST use the Value property here because it is unescaped.
_nextSegment = remainingPath.Value.Split("/", StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
public bool Exists => true;
public IEnumerator<IFileInfo> GetEnumerator()
{
return GenerateEnum();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GenerateEnum();
}
private IEnumerator<IFileInfo> GenerateEnum()
{
return new[] { new StaticWebAssetsFileInfo(_nextSegment) }
.Cast<IFileInfo>().GetEnumerator();
}
private class StaticWebAssetsFileInfo : IFileInfo
{
public StaticWebAssetsFileInfo(string name)
{
Name = name;
}
public bool Exists => true;
public long Length => throw new NotImplementedException();
public string PhysicalPath => throw new NotImplementedException();
public DateTimeOffset LastModified => throw new NotImplementedException();
public bool IsDirectory => true;
public string Name { get; }
public Stream CreateReadStream()
{
throw new NotImplementedException();
}
}
}
} }
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -9,7 +9,6 @@
<Compile Include="$(SharedSourceRoot)EventSource.Testing\TestEventListener.cs" /> <Compile Include="$(SharedSourceRoot)EventSource.Testing\TestEventListener.cs" />
<Compile Include="$(SharedSourceRoot)EventSource.Testing\TestCounterListener.cs" /> <Compile Include="$(SharedSourceRoot)EventSource.Testing\TestCounterListener.cs" />
<Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> <Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
<None Remove="testroot\wwwroot\Static Web Assets.txt" />
<Content Include="Microsoft.AspNetCore.Hosting.StaticWebAssets.xml" CopyToOutputDirectory="PreserveNewest" /> <Content Include="Microsoft.AspNetCore.Hosting.StaticWebAssets.xml" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup> </ItemGroup>

View File

@ -37,6 +37,75 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
Assert.Equal("/_content", provider.BasePath); Assert.Equal("/_content", provider.BasePath);
} }
[Theory]
[InlineData("\\", "_content")]
[InlineData("\\_content\\RazorClassLib\\Dir", "Castle.Core.dll")]
[InlineData("", "_content")]
[InlineData("/", "_content")]
[InlineData("/_content", "RazorClassLib")]
[InlineData("/_content/RazorClassLib", "Dir")]
[InlineData("/_content/RazorClassLib/Dir", "Microsoft.AspNetCore.Hosting.Tests.dll")]
[InlineData("/_content/RazorClassLib/Dir/testroot/", "TextFile.txt")]
[InlineData("/_content/RazorClassLib/Dir/testroot/wwwroot/", "README")]
public void GetDirectoryContents_WalksUpContentRoot(string searchDir, string expected)
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/Dir", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents(searchDir);
// Assert
Assert.NotEmpty(directory);
Assert.Contains(directory, file => string.Equals(file.Name, expected));
}
[Fact]
public void GetDirectoryContents_DoesNotFindNonExistentFiles()
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents("/_content/RazorClassLib/False");
// Assert
Assert.Empty(directory);
}
[Theory]
[InlineData("/False/_content/RazorClassLib/")]
[InlineData("/_content/RazorClass")]
public void GetDirectoryContents_PartialMatchFails(string requestedUrl)
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents(requestedUrl);
// Assert
Assert.Empty(directory);
}
[Fact]
public void GetDirectoryContents_HandlesWhitespaceInBase()
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/Static Web Assets",
Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
// Act
var directory = provider.GetDirectoryContents("/_content/Static Web Assets/Static Web/");
// Assert
Assert.Collection(directory,
file =>
{
Assert.Equal("Static Web.txt", file.Name);
});
}
[Fact] [Fact]
public void StaticWebAssetsFileProvider_FindsFileWithSpaces() public void StaticWebAssetsFileProvider_FindsFileWithSpaces()
{ {

View File

@ -489,6 +489,55 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never()); testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never());
} }
[Fact]
public async Task ReadAsync_WithEnableBufferingWorks()
{
// Arrange
var formatter = GetInputFormatter();
var content = "{\"name\": \"Test\"}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
httpContext.Request.EnableBuffering();
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
var userModel = Assert.IsType<ComplexModel>(result.Model);
Assert.Equal("Test", userModel.Name);
var requestBody = httpContext.Request.Body;
requestBody.Position = 0;
Assert.Equal(content, new StreamReader(requestBody).ReadToEnd());
}
[Fact]
public async Task ReadAsync_WithEnableBufferingWorks_WithInputStreamAtOffset()
{
// Arrange
var formatter = GetInputFormatter();
var content = "abc{\"name\": \"Test\"}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
httpContext.Request.EnableBuffering();
var requestBody = httpContext.Request.Body;
requestBody.Position = 3;
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
var userModel = Assert.IsType<ComplexModel>(result.Model);
Assert.Equal("Test", userModel.Name);
requestBody.Position = 0;
Assert.Equal(content, new StreamReader(requestBody).ReadToEnd());
}
internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; } internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; }
internal abstract string JsonFormatter_EscapedKeys_Expected { get; } internal abstract string JsonFormatter_EscapedKeys_Expected { get; }

View File

@ -120,7 +120,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Stream readStream = new NonDisposableStream(request.Body); Stream readStream = new NonDisposableStream(request.Body);
var disposeReadStream = false; var disposeReadStream = false;
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering) if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!_options.SuppressInputFormatterBuffering)
{ {
// XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning. // read everything into a buffer, and then seek back to the beginning.

View File

@ -101,7 +101,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Stream readStream = new NonDisposableStream(request.Body); Stream readStream = new NonDisposableStream(request.Body);
var disposeReadStream = false; var disposeReadStream = false;
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering) if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!_options.SuppressInputFormatterBuffering)
{ {
// XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning. // read everything into a buffer, and then seek back to the beginning.

View File

@ -130,7 +130,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var readStream = request.Body; var readStream = request.Body;
var disposeReadStream = false; var disposeReadStream = false;
if (!request.Body.CanSeek && !suppressInputFormatterBuffering) if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!suppressInputFormatterBuffering)
{ {
// JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning. // read everything into a buffer, and then seek back to the beginning.

View File

@ -65,6 +65,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
for (var i = 0; i < candidates.Count; i++) for (var i = 0; i < candidates.Count; i++)
{ {
if (!candidates.IsValidCandidate(i))
{
continue;
}
ref var candidate = ref candidates[i]; ref var candidate = ref candidates[i];
var endpoint = candidate.Endpoint; var endpoint = candidate.Endpoint;

View File

@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("params", DictionaryAttributePrefix="param-")] [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("params", DictionaryAttributePrefix="param-")]
public System.Collections.Generic.IDictionary<string, object> Parameters { get { throw null; } set { } } public System.Collections.Generic.IDictionary<string, object> Parameters { get { throw null; } set { } }
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("render-mode")] [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("render-mode")]
public Microsoft.AspNetCore.Mvc.Rendering.RenderMode RenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Mvc.Rendering.RenderMode RenderMode { get { throw null; } set { } }
[Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute] [Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute] [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute]
public Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }

View File

@ -14,14 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// <summary> /// <summary>
/// A <see cref="TagHelper"/> that renders a Razor component. /// A <see cref="TagHelper"/> that renders a Razor component.
/// </summary> /// </summary>
[HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement(TagHelperName, Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)]
public sealed class ComponentTagHelper : TagHelper public sealed class ComponentTagHelper : TagHelper
{ {
private const string TagHelperName = "component";
private const string ComponentParameterName = "params"; private const string ComponentParameterName = "params";
private const string ComponentParameterPrefix = "param-"; private const string ComponentParameterPrefix = "param-";
private const string ComponentTypeName = "type"; private const string ComponentTypeName = "type";
private const string RenderModeName = "render-mode"; private const string RenderModeName = "render-mode";
private IDictionary<string, object> _parameters; private IDictionary<string, object> _parameters;
private RenderMode? _renderMode;
/// <summary> /// <summary>
/// Gets or sets the <see cref="Rendering.ViewContext"/> for the current request. /// Gets or sets the <see cref="Rendering.ViewContext"/> for the current request.
@ -54,7 +56,29 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// Gets or sets the <see cref="Rendering.RenderMode"/> /// Gets or sets the <see cref="Rendering.RenderMode"/>
/// </summary> /// </summary>
[HtmlAttributeName(RenderModeName)] [HtmlAttributeName(RenderModeName)]
public RenderMode RenderMode { get; set; } public RenderMode RenderMode
{
get => _renderMode ?? default;
set
{
switch (value)
{
case RenderMode.Server:
case RenderMode.ServerPrerendered:
case RenderMode.Static:
_renderMode = value;
break;
default:
throw new ArgumentException(
message: Resources.FormatInvalidEnumArgument(
nameof(value),
value,
typeof(RenderMode).FullName),
paramName: nameof(value));
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -69,6 +93,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
throw new ArgumentNullException(nameof(output)); throw new ArgumentNullException(nameof(output));
} }
if (_renderMode is null)
{
throw new ArgumentException(Resources.FormatAttributeIsRequired(RenderModeName, TagHelperName), nameof(RenderMode));
}
var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>(); var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, _parameters); var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, _parameters);

View File

@ -162,4 +162,7 @@
<data name="ViewEngine_FallbackViewNotFound" xml:space="preserve"> <data name="ViewEngine_FallbackViewNotFound" xml:space="preserve">
<value>The fallback partial view '{0}' was not found. The following locations were searched:{1}</value> <value>The fallback partial view '{0}' was not found. The following locations were searched:{1}</value>
</data> </data>
<data name="AttributeIsRequired" xml:space="preserve">
<value>A value for the '{0}' attribute must be supplied to the '{1}' tag helper.</value>
</data>
</root> </root>

View File

@ -3,13 +3,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipes;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var tagHelper = new ComponentTagHelper var tagHelper = new ComponentTagHelper
{ {
ViewContext = GetViewContext(), ViewContext = GetViewContext(),
RenderMode = RenderMode.Static,
}; };
var context = GetTagHelperContext(); var context = GetTagHelperContext();
var output = GetTagHelperOutput(); var output = GetTagHelperOutput();
@ -38,6 +39,24 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Null(output.TagName); Assert.Null(output.TagName);
} }
[Fact]
public async Task ProcessAsync_WithoutSpecifyingRenderMode_ThrowsError()
{
// Arrange
var tagHelper = new ComponentTagHelper
{
ViewContext = GetViewContext(),
};
var context = GetTagHelperContext();
var output = GetTagHelperOutput();
// Act & Assert
await ExceptionAssert.ThrowsArgumentAsync(
() => tagHelper.ProcessAsync(context, output),
nameof(RenderMode),
"A value for the 'render-mode' attribute must be supplied to the 'component' tag helper.");
}
private static TagHelperContext GetTagHelperContext() private static TagHelperContext GetTagHelperContext()
{ {
return new TagHelperContext( return new TagHelperContext(

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
@ -42,8 +43,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
values, values,
invocationId.Value); invocationId.Value);
var serializedServerComponent = JsonSerializer.Serialize(serverComponent, ServerComponentSerializationSettings.JsonSerializationOptions); var serializedServerComponentBytes = JsonSerializer.SerializeToUtf8Bytes(serverComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
return (serverComponent.Sequence, _dataProtector.Protect(serializedServerComponent, ServerComponentSerializationSettings.DataExpiration)); var protectedBytes = _dataProtector.Protect(serializedServerComponentBytes, ServerComponentSerializationSettings.DataExpiration);
return (serverComponent.Sequence, Convert.ToBase64String(protectedBytes));
} }
internal IEnumerable<string> GetPreamble(ServerComponentMarker record) internal IEnumerable<string> GetPreamble(ServerComponentMarker record)

View File

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="..\..\Mvc.Formatters.Xml\test\XmlAssert.cs" /> <Compile Include="..\..\Mvc.Formatters.Xml\test\XmlAssert.cs" />
<EmbeddedResource Include="compiler\resources\**\*" /> <EmbeddedResource Include="compiler\resources\**\*" />
<None Remove="compiler\resources\TagHelpersWebSite.Home.GlobbingTagHelpers.html" />
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" /> <None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup> </ItemGroup>

View File

@ -6,6 +6,8 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using RoutingWebSite;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@ -14,12 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{ {
public RoutingDynamicTest(MvcTestFixture<RoutingWebSite.StartupForDynamic> fixture) public RoutingDynamicTest(MvcTestFixture<RoutingWebSite.StartupForDynamic> fixture)
{ {
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = factory.CreateDefaultClient(); Client = Factory.CreateDefaultClient();
} }
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => builder.UseStartup<RoutingWebSite.StartupForDynamic>(); private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => builder.UseStartup<RoutingWebSite.StartupForDynamic>();
public WebApplicationFactory<StartupForDynamic> Factory { get; }
public HttpClient Client { get; } public HttpClient Client { get; }
[Fact] [Fact]
@ -99,5 +102,39 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello from dynamic page: /DynamicPage", content); Assert.Equal("Hello from dynamic page: /DynamicPage", content);
} }
[Fact]
public async Task AppWithDynamicRouteAndMapRazorPages_CanRouteToRazorPage()
{
// Regression test for https://github.com/aspnet/AspNetCore/issues/13996
// Arrange
var client = Factory.WithWebHostBuilder(b => b.UseStartup<StartupForDynamicAndRazorPages>()).CreateDefaultClient();
var url = "/PageWithLinks";
// Act
var response = await client.GetAsync(url);
// Assert
var document = await response.GetHtmlDocumentAsync();
var editLink = document.RequiredQuerySelector("#editlink");
Assert.Equal("/Edit/10", editLink.GetAttribute("href"));
}
[Fact]
public async Task AppWithDynamicRouteAndMapRazorPages_CanRouteToDynamicController()
{
// Regression test for https://github.com/aspnet/AspNetCore/issues/13996
// Arrange
var client = Factory.WithWebHostBuilder(b => b.UseStartup<StartupForDynamicAndRazorPages>()).CreateDefaultClient();
var url = "/de/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
// Act
var response = await client.GetAsync(url);
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
Assert.StartsWith("Hello from dynamic controller", content);
}
} }
} }

View File

@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public HttpClient EncodedClient { get; } public HttpClient EncodedClient { get; }
[Theory] [Theory]
[InlineData("GlobbingTagHelpers")]
[InlineData("Index")] [InlineData("Index")]
[InlineData("About")] [InlineData("About")]
[InlineData("Help")] [InlineData("Help")]

View File

@ -0,0 +1,39 @@

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Globbing Page - My ASP.NET Application</title>
</head>
<body>
<h1 style="font-family: cursive;">ASP.NET vNext - Globbing Page</h1>
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
<div>
Globbing
<hr />
<footer>
<!-- File wildcard -->
<script src="/js/dist/dashboardHome.js"></script>
<!-- RazorClassLib folder wildcard -->
<script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- RazorClassLib deep wildcard -->
<script src="/js/dist/dashboardHome.js"></script><script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- RazorClassLib Exclude local -->
<script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- local Exclude lib-->
<script src="/js/dist/dashboardHome.js"></script>
</footer>
</div>
</body>
</html>

View File

@ -5,7 +5,7 @@
<IsTestAssetProject>true</IsTestAssetProject> <IsTestAssetProject>true</IsTestAssetProject>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc> <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" /> <Reference Include="Microsoft.AspNetCore.Mvc" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,5 @@
window.exampleJsFunctions = {
showPrompt: function (message) {
return prompt(message, 'Type anything here')
}
};

View File

@ -0,0 +1,62 @@
// Copyright (c) .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 Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace RoutingWebSite
{
// For by tests for a mix of dynamic routing + Razor Pages
public class StartupForDynamicAndRazorPages
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services.AddSingleton<Transformer>();
// Used by some controllers defined in this project.
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapDynamicControllerRoute<Transformer>("{language}/{**slug}");
});
}
private class Transformer : DynamicRouteValueTransformer
{
// Turns a format like `controller=Home,action=Index` into an RVD
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (!(values["slug"] is string slug))
{
return new ValueTask<RouteValueDictionary>(values);
}
var kvps = slug.Split(",");
var results = new RouteValueDictionary();
foreach (var kvp in kvps)
{
var split = kvp.Split("=");
results[split[0]] = split[1];
}
return new ValueTask<RouteValueDictionary>(results);
}
}
}
}

View File

@ -25,6 +25,11 @@ namespace TagHelpersWebSite.Controllers
return View(); return View();
} }
public IActionResult GlobbingTagHelpers()
{
return View();
}
public IActionResult Help() public IActionResult Help()
{ {
return View(); return View();

View File

@ -21,6 +21,7 @@ namespace TagHelpersWebSite
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
app.UseRouting(); app.UseRouting();
app.UseStaticFiles();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapDefaultControllerRoute(); endpoints.MapDefaultControllerRoute();
@ -38,6 +39,7 @@ namespace TagHelpersWebSite
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
new WebHostBuilder() new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStaticWebAssets()
.UseStartup<Startup>() .UseStartup<Startup>()
.UseKestrel() .UseKestrel()
.UseIISIntegration(); .UseIISIntegration();

View File

@ -6,6 +6,10 @@
<IsTestAssetProject>true</IsTestAssetProject> <IsTestAssetProject>true</IsTestAssetProject>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" /> <Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" /> <Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />

View File

@ -0,0 +1,19 @@
@{
ViewData["Title"] = "Globbing Page";
}
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@section footerContent {
<!-- File wildcard -->
<script asp-src-include="/js/dist/dashboard*.js"></script>
<!-- RazorClassLib folder wildcard -->
<script asp-src-include="/_content/RazorPagesClassLibrary/**/file.js"></script>
<!-- RazorClassLib deep wildcard -->
<script asp-src-include="/**/*.js"></script>
<!-- RazorClassLib Exclude local -->
<script asp-src-exclude="/js/dist/*.js" asp-src-include="**/*.js"></script>
<!-- local Exclude lib-->
<script asp-src-exclude="/_content/**/*.js" asp-src-include="**/*.js"></script>
}
Globbing

View File

@ -0,0 +1 @@


View File

@ -1,6 +1,9 @@
@page "/" @page "/"
@namespace BlazorServerWeb_CSharp.Pages @namespace BlazorServerWeb_CSharp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">

View File

@ -12,47 +12,47 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "8.0.0", "@angular/animations": "8.2.12",
"@angular/common": "8.0.0", "@angular/common": "8.2.12",
"@angular/compiler": "8.0.0", "@angular/compiler": "8.2.12",
"@angular/core": "8.0.0", "@angular/core": "8.2.12",
"@angular/forms": "8.0.0", "@angular/forms": "8.2.12",
"@angular/platform-browser": "8.0.0", "@angular/platform-browser": "8.2.12",
"@angular/platform-browser-dynamic": "8.0.0", "@angular/platform-browser-dynamic": "8.2.12",
"@angular/platform-server": "8.0.0", "@angular/platform-server": "8.2.12",
"@angular/router": "8.0.0", "@angular/router": "8.2.12",
"@nguniversal/module-map-ngfactory-loader": "8.0.0-rc.1", "@nguniversal/module-map-ngfactory-loader": "8.1.1",
"aspnet-prerendering": "^3.0.1", "aspnet-prerendering": "^3.0.1",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"core-js": "^2.6.5", "core-js": "^3.3.3",
"jquery": "3.4.1", "jquery": "3.4.1",
"oidc-client": "^1.9.0", "oidc-client": "^1.9.1",
"popper.js": "^1.14.3", "popper.js": "^1.16.0",
"rxjs": "^6.4.0", "rxjs": "^6.5.3",
"zone.js": "~0.9.1" "zone.js": "0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^0.800.6", "@angular-devkit/build-angular": "^0.803.14",
"@angular/cli": "8.0.6", "@angular/cli": "8.3.14",
"@angular/compiler-cli": "8.0.0", "@angular/compiler-cli": "8.2.12",
"@angular/language-service": "8.0.0", "@angular/language-service": "8.2.12",
"@types/jasmine": "~3.3.9", "@types/jasmine": "~3.4.4",
"@types/jasminewd2": "~2.0.6", "@types/jasminewd2": "~2.0.8",
"@types/node": "~11.10.5", "@types/node": "~12.11.6",
"codelyzer": "^5.0.1", "codelyzer": "^5.2.0",
"jasmine-core": "~3.3.0", "jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "^4.0.0", "karma": "^4.4.1",
"karma-chrome-launcher": "~2.2.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.0.5", "karma-coverage-istanbul-reporter": "~2.1.0",
"karma-jasmine": "~2.0.1", "karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0", "karma-jasmine-html-reporter": "^1.4.2",
"typescript": "3.4.5" "typescript": "3.5.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"node-sass": "^4.9.3", "node-sass": "^4.12.0",
"protractor": "~5.4.0", "protractor": "~5.4.2",
"ts-node": "~5.0.1", "ts-node": "~8.4.1",
"tslint": "~5.9.1" "tslint": "~5.20.0"
} }
} }

View File

@ -1,3 +1,7 @@
{ {
"parser": "typescript-eslint-parser" "parser": "@typescript-eslint/parser",
} "parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
}
}

View File

@ -4,39 +4,40 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"connected-react-router": "6.2.1", "connected-react-router": "6.5.2",
"history": "4.7.2", "history": "4.10.1",
"jquery": "^3.4.1", "jquery": "^3.4.1",
"merge": "1.2.1", "merge": "1.2.1",
"popper.js": "^1.14.7", "popper.js": "^1.16.0",
"react": "16.7.0", "react": "16.11.0",
"react-dom": "16.7.0", "react-dom": "16.11.0",
"react-redux": "6.0.0", "react-redux": "7.1.1",
"react-router": "4.3.1", "react-router": "5.1.2",
"react-router-dom": "4.3.1", "react-router-dom": "5.1.2",
"react-scripts": "^3.0.1", "react-scripts": "^3.2.0",
"reactstrap": "7.0.2", "reactstrap": "8.1.1",
"redux": "4.0.1", "redux": "4.0.4",
"redux-thunk": "2.3.0", "redux-thunk": "2.3.0",
"typescript": "3.2.2" "svgo": "1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/history": "4.7.2", "@types/history": "4.7.3",
"@types/jest": "23.3.11", "@types/jest": "24.0.19",
"@types/node": "10.12.18", "@types/node": "12.11.6",
"@types/react": "16.7.18", "@types/react": "16.9.9",
"@types/react-dom": "16.0.11", "@types/react-dom": "16.9.2",
"@types/react-redux": "6.0.11", "@types/react-redux": "7.1.5",
"@types/react-router": "4.4.3", "@types/react-router": "5.1.2",
"@types/react-router-dom": "4.3.1", "@types/react-router-dom": "5.1.0",
"@types/reactstrap": "6.4.3", "@types/reactstrap": "8.0.6",
"cross-env": "5.2.0", "@typescript-eslint/parser": "^2.5.0",
"eslint": "^5.16.0", "cross-env": "6.0.3",
"eslint-plugin-flowtype": "2.50.3", "eslint": "^6.5.1",
"eslint-plugin-import": "2.14.0", "eslint-plugin-flowtype": "^3.13.0",
"eslint-plugin-jsx-a11y": "6.1.2", "eslint-plugin-import": "2.18.2",
"eslint-plugin-react": "7.11.1", "eslint-plugin-jsx-a11y": "6.2.3",
"typescript-eslint-parser": "21.0.2" "eslint-plugin-react": "7.16.0",
"typescript": "3.6.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -104,11 +104,13 @@ namespace Templates.Test.Helpers
public async Task ContainsLinks(Page page) public async Task ContainsLinks(Page page)
{ {
var request = new HttpRequestMessage( var response = await RequestWithRetries(client =>
HttpMethod.Get, {
new Uri(ListeningUri, page.Url)); var request = new HttpRequestMessage(
HttpMethod.Get,
var response = await RequestWithRetries(client => client.SendAsync(request), _httpClient); new Uri(ListeningUri, page.Url));
return client.SendAsync(request);
}, _httpClient);
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var parser = new HtmlParser(); var parser = new HtmlParser();
@ -235,16 +237,18 @@ namespace Templates.Test.Helpers
public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null) public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
{ {
var request = new HttpRequestMessage( var response = await RequestWithRetries(client => {
HttpMethod.Get, var request = new HttpRequestMessage(
new Uri(ListeningUri, requestUrl)); HttpMethod.Get,
new Uri(ListeningUri, requestUrl));
if (!string.IsNullOrEmpty(acceptContentType)) if (!string.IsNullOrEmpty(acceptContentType))
{ {
request.Headers.Add("Accept", acceptContentType); request.Headers.Add("Accept", acceptContentType);
} }
var response = await RequestWithRetries(client => client.SendAsync(request), _httpClient); return client.SendAsync(request);
}, _httpClient);
Assert.True(statusCode == response.StatusCode, $"Expected {requestUrl} to have status '{statusCode}' but it was '{response.StatusCode}'."); Assert.True(statusCode == response.StatusCode, $"Expected {requestUrl} to have status '{statusCode}' but it was '{response.StatusCode}'.");
} }