Merge branch 'release/3.1' into merge/release/3.1-preview2-to-release/3.1
This commit is contained in:
commit
fc05a91ef4
|
|
@ -25,23 +25,3 @@ jobs:
|
||||||
- name: Helix_logs
|
- name: Helix_logs
|
||||||
path: artifacts/log/
|
path: artifacts/log/
|
||||||
publishOnError: true
|
publishOnError: true
|
||||||
|
|
||||||
# Build Helix ARM64
|
|
||||||
- template: jobs/default-build.yml
|
|
||||||
parameters:
|
|
||||||
jobName: Helix_arm64
|
|
||||||
jobDisplayName: "Tests: Helix ARM64"
|
|
||||||
agentOs: Linux
|
|
||||||
timeoutInMinutes: 240
|
|
||||||
steps:
|
|
||||||
- script: ./restore.sh -ci
|
|
||||||
displayName: Restore
|
|
||||||
- script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl
|
|
||||||
displayName: Run build.sh helix arm64 target
|
|
||||||
env:
|
|
||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops
|
|
||||||
installNodeJs: false
|
|
||||||
artifacts:
|
|
||||||
- name: Helix_arm64_logs
|
|
||||||
path: artifacts/logs/
|
|
||||||
publishOnError: true
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<AspNetCoreMajorVersion>3</AspNetCoreMajorVersion>
|
<AspNetCoreMajorVersion>3</AspNetCoreMajorVersion>
|
||||||
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
|
<AspNetCoreMinorVersion>1</AspNetCoreMinorVersion>
|
||||||
<AspNetCorePatchVersion>0</AspNetCorePatchVersion>
|
<AspNetCorePatchVersion>0</AspNetCorePatchVersion>
|
||||||
<PreReleasePreviewNumber>2</PreReleasePreviewNumber>
|
<PreReleasePreviewNumber>3</PreReleasePreviewNumber>
|
||||||
<!--
|
<!--
|
||||||
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
|
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
|
||||||
-->
|
-->
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -348,7 +348,6 @@ namespace Ignitor
|
||||||
});
|
});
|
||||||
|
|
||||||
_hubConnection = builder.Build();
|
_hubConnection = builder.Build();
|
||||||
await HubConnection.StartAsync(CancellationToken);
|
|
||||||
|
|
||||||
HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent);
|
HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent);
|
||||||
HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS);
|
HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS);
|
||||||
|
|
@ -357,6 +356,8 @@ namespace Ignitor
|
||||||
HubConnection.On<string>("JS.Error", OnError);
|
HubConnection.On<string>("JS.Error", OnError);
|
||||||
HubConnection.Closed += OnClosedAsync;
|
HubConnection.Closed += OnClosedAsync;
|
||||||
|
|
||||||
|
await HubConnection.StartAsync(CancellationToken);
|
||||||
|
|
||||||
if (!connectAutomatically)
|
if (!connectAutomatically)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 &&
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
|
|
||||||
private bool EqualsHrefExactlyOrIfTrailingSlashAdded(string currentUriAbsolute)
|
private bool EqualsHrefExactlyOrIfTrailingSlashAdded(string currentUriAbsolute)
|
||||||
{
|
{
|
||||||
if (string.Equals(currentUriAbsolute, _hrefAbsolute, StringComparison.Ordinal))
|
if (string.Equals(currentUriAbsolute, _hrefAbsolute, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
// for http://host/vdir as they do for host://host/vdir/ as it's no
|
// for http://host/vdir as they do for host://host/vdir/ as it's no
|
||||||
// good to display a blank page in that case.
|
// good to display a blank page in that case.
|
||||||
if (_hrefAbsolute[_hrefAbsolute.Length - 1] == '/'
|
if (_hrefAbsolute[_hrefAbsolute.Length - 1] == '/'
|
||||||
&& _hrefAbsolute.StartsWith(currentUriAbsolute, StringComparison.Ordinal))
|
&& _hrefAbsolute.StartsWith(currentUriAbsolute, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,4 +57,10 @@
|
||||||
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentMarker.cs" />
|
<Compile Include="$(RepoRoot)src\Shared\Components\ServerComponentMarker.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="xunit.runner.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TestServer;
|
using TestServer;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,14 @@ namespace Microsoft.AspNetCore.Components
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task DisposeAsync()
|
protected async virtual Task DisposeAsync()
|
||||||
{
|
{
|
||||||
if (TestSink != null)
|
if (TestSink != null)
|
||||||
{
|
{
|
||||||
TestSink.MessageLogged -= TestSink_MessageLogged;
|
TestSink.MessageLogged -= TestSink_MessageLogged;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
await Client.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TestSink_MessageLogged(WriteContext context)
|
private void TestSink_MessageLogged(WriteContext context)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
// This is set to -1 to allow the usage of an
|
||||||
|
// unlimited ammount of threads.
|
||||||
|
"maxParallelThreads": -1,
|
||||||
|
"diagnosticMessages": true,
|
||||||
|
"longRunningTestSeconds": 30
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<li><NavLink href="Other" Match=NavLinkMatch.All>Other with base-relative URL (matches all)</NavLink></li>
|
<li><NavLink href="Other" Match=NavLinkMatch.All>Other with base-relative URL (matches all)</NavLink></li>
|
||||||
<li><NavLink href="/subdir/other?abc=123">Other with query</NavLink></li>
|
<li><NavLink href="/subdir/other?abc=123">Other with query</NavLink></li>
|
||||||
<li><NavLink href="/subdir/Other#blah">Other with hash</NavLink></li>
|
<li><NavLink href="/subdir/Other#blah">Other with hash</NavLink></li>
|
||||||
<li><NavLink href="/subdir/WithParameters/Name/Abc">With parameters</NavLink></li>
|
<li><NavLink href="/subdir/WithParameters/name/Abc">With parameters</NavLink></li>
|
||||||
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
|
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
|
||||||
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
|
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
|
||||||
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
|
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:AuthenticationManager.ChallengeAsync"/>.
|
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChallengeResult : ActionResult
|
public class ChallengeResult : ActionResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:AuthenticationManager.ForbidAsync"/>.
|
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ForbidAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ForbidResult : ActionResult
|
public class ForbidResult : ActionResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:AuthenticationManager.SignInAsync"/>.
|
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.SignInAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SignInResult : ActionResult
|
public class SignInResult : ActionResult
|
||||||
{
|
{
|
||||||
|
|
@ -35,8 +35,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// <param name="principal">The claims principal containing the user claims.</param>
|
/// <param name="principal">The claims principal containing the user claims.</param>
|
||||||
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
|
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
|
||||||
public SignInResult(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
|
public SignInResult(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
|
||||||
{
|
{
|
||||||
AuthenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme));
|
AuthenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme));
|
||||||
Principal = principal ?? throw new ArgumentNullException(nameof(principal));
|
Principal = principal ?? throw new ArgumentNullException(nameof(principal));
|
||||||
Properties = properties;
|
Properties = properties;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:AuthenticationManager.SignOutAsync"/>.
|
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.SignOutAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SignOutResult : ActionResult
|
public class SignOutResult : ActionResult
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// <param name="authenticationSchemes">The authentication scheme to use when signing out the user.</param>
|
/// <param name="authenticationSchemes">The authentication scheme to use when signing out the user.</param>
|
||||||
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
|
/// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
|
||||||
public SignOutResult(IList<string> authenticationSchemes, AuthenticationProperties properties)
|
public SignOutResult(IList<string> authenticationSchemes, AuthenticationProperties properties)
|
||||||
{
|
{
|
||||||
AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
|
AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
|
||||||
Properties = properties;
|
Properties = properties;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 { } }
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")]
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
window.exampleJsFunctions = {
|
||||||
|
showPrompt: function (message) {
|
||||||
|
return prompt(message, 'Type anything here')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Company.WebApplication1
|
namespace Company.WebApplication1
|
||||||
|
|
@ -13,11 +13,14 @@ namespace Company.WebApplication1
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
CreateWebHostBuilder(args).Build().Run();
|
CreateHostBuilder(args).Build().Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
WebHost.CreateDefaultBuilder(args)
|
Host.CreateDefaultBuilder(args)
|
||||||
.UseStartup<Startup>();
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ export class AuthorizeService {
|
||||||
throw new Error(`Found an invalid number of subscriptions ${subscriptionIndex.length}`);
|
throw new Error(`Found an invalid number of subscriptions ${subscriptionIndex.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._callbacks = this._callbacks.splice(subscriptionIndex[0].index, 1);
|
this._callbacks.splice(subscriptionIndex[0].index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifySubscribers() {
|
notifySubscribers() {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Company.WebApplication1
|
namespace Company.WebApplication1
|
||||||
|
|
@ -13,11 +13,14 @@ namespace Company.WebApplication1
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
CreateWebHostBuilder(args).Build().Run();
|
CreateHostBuilder(args).Build().Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
WebHost.CreateDefaultBuilder(args)
|
Host.CreateDefaultBuilder(args)
|
||||||
.UseStartup<Startup>();
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
{
|
{
|
||||||
"parser": "typescript-eslint-parser"
|
"parser": "@typescript-eslint/parser",
|
||||||
}
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Company.WebApplication1
|
namespace Company.WebApplication1
|
||||||
|
|
@ -14,11 +13,14 @@ namespace Company.WebApplication1
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
CreateWebHostBuilder(args).Build().Run();
|
CreateHostBuilder(args).Build().Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
WebHost.CreateDefaultBuilder(args)
|
Host.CreateDefaultBuilder(args)
|
||||||
.UseStartup<Startup>();
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
|
|
||||||
if (TransportType == HttpTransportType.WebSockets)
|
if (TransportType == HttpTransportType.WebSockets)
|
||||||
{
|
{
|
||||||
// The websocket transport will close the application output automatically when reading is cancelled
|
// The websocket transport will close the application output automatically when reading is canceled
|
||||||
Cancellation?.Cancel();
|
Cancellation?.Cancel();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -443,6 +443,45 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<bool> CancelPreviousPoll(HttpContext context)
|
||||||
|
{
|
||||||
|
CancellationTokenSource cts;
|
||||||
|
lock (_stateLock)
|
||||||
|
{
|
||||||
|
// Need to sync cts access with DisposeAsync as that will dispose the cts
|
||||||
|
if (Status == HttpConnectionStatus.Disposed)
|
||||||
|
{
|
||||||
|
cts = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cts = Cancellation;
|
||||||
|
Cancellation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (cts)
|
||||||
|
{
|
||||||
|
// Cancel the previous request
|
||||||
|
cts?.Cancel();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Wait for the previous request to drain
|
||||||
|
await PreviousPollTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Previous poll canceled due to connection closing, close this poll too
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void MarkInactive()
|
public void MarkInactive()
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
lock (_stateLock)
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
|
|
||||||
Log.EstablishedConnection(_logger);
|
Log.EstablishedConnection(_logger);
|
||||||
|
|
||||||
// Allow the reads to be cancelled
|
// Allow the reads to be canceled
|
||||||
connection.Cancellation = new CancellationTokenSource();
|
connection.Cancellation = new CancellationTokenSource();
|
||||||
|
|
||||||
var ws = new WebSocketsServerTransport(options.WebSockets, connection.Application, connection, _loggerFactory);
|
var ws = new WebSocketsServerTransport(options.WebSockets, connection.Application, connection, _loggerFactory);
|
||||||
|
|
@ -189,28 +189,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!await connection.CancelPreviousPoll(context))
|
||||||
|
{
|
||||||
|
// Connection closed. It's already set the response status code.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
|
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
|
||||||
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
using (connection.Cancellation)
|
|
||||||
{
|
|
||||||
// Cancel the previous request
|
|
||||||
connection.Cancellation?.Cancel();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Wait for the previous request to drain
|
|
||||||
await connection.PreviousPollTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Previous poll canceled due to connection closing, close this poll too
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection.TryActivateLongPollingConnection(
|
if (!connection.TryActivateLongPollingConnection(
|
||||||
connectionDelegate, context, options.LongPolling.PollTimeout,
|
connectionDelegate, context, options.LongPolling.PollTimeout,
|
||||||
currentRequestTcs.Task, _loggerFactory, _logger))
|
currentRequestTcs.Task, _loggerFactory, _logger))
|
||||||
|
|
|
||||||
|
|
@ -1148,9 +1148,22 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
Assert.True(request1.IsCompleted);
|
Assert.True(request1.IsCompleted);
|
||||||
|
|
||||||
request1 = dispatcher.ExecuteAsync(context1, options, app);
|
request1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
|
var count = 0;
|
||||||
|
// Wait until the request has started internally
|
||||||
|
while (connection.TransportTask.IsCompleted && count < 50)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
await Task.Delay(15);
|
||||||
|
}
|
||||||
|
if (count == 50)
|
||||||
|
{
|
||||||
|
Assert.True(false, "Poll took too long to start");
|
||||||
|
}
|
||||||
|
|
||||||
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
||||||
|
|
||||||
await request1;
|
// Wait for poll to be canceled
|
||||||
|
await request1.OrTimeout();
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode);
|
Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode);
|
||||||
Assert.Equal(HttpConnectionStatus.Active, connection.Status);
|
Assert.Equal(HttpConnectionStatus.Active, connection.Status);
|
||||||
|
|
@ -1164,7 +1177,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2040", "All")]
|
|
||||||
public async Task MultipleRequestsToActiveConnectionId409ForLongPolling()
|
public async Task MultipleRequestsToActiveConnectionId409ForLongPolling()
|
||||||
{
|
{
|
||||||
using (StartVerifiableLog())
|
using (StartVerifiableLog())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue