Merge branch 'release/3.1' -> 'master'
This commit is contained in:
commit
0dedfa95d4
|
|
@ -229,7 +229,7 @@
|
|||
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
|
||||
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
|
||||
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>
|
||||
<GrpcAspNetCorePackageVersion>2.23.1</GrpcAspNetCorePackageVersion>
|
||||
<GrpcAspNetCorePackageVersion>2.23.2</GrpcAspNetCorePackageVersion>
|
||||
<IdentityServer4AspNetIdentityPackageVersion>3.0.0</IdentityServer4AspNetIdentityPackageVersion>
|
||||
<IdentityServer4EntityFrameworkPackageVersion>3.0.0</IdentityServer4EntityFrameworkPackageVersion>
|
||||
<IdentityServer4PackageVersion>3.0.0</IdentityServer4PackageVersion>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
<PropertyGroup>
|
||||
<!-- Working around https://github.com/NuGet/Home/issues/8467 -->
|
||||
<NoWarn>$(NoWarn);NU5131</NoWarn>
|
||||
<!-- Workaround until https://github.com/aspnet/AspNetCore-Internal/issues/3103 is resolved -->
|
||||
<NoWarn>$(NoWarn);NU5048</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Workaround https://github.com/dotnet/roslyn/issues/27975 -->
|
||||
|
|
|
|||
|
|
@ -96,6 +96,25 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
|
@ -8,9 +9,16 @@
|
|||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<!-- Intentionally targeting netcoreapp3.0 because we do not want to update the Benchmark client to use 3.1 -->
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@page "/"
|
||||
@page "/"
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@
|
|||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<app>
|
||||
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
|
||||
</app>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
|
|
@ -12,4 +12,10 @@
|
|||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a class="reload">Reload</a>
|
||||
<a class="dismiss">X</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,23 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
position: fixed;
|
||||
border: "1px solid";
|
||||
border-color: black;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@ import '@dotnet/jsinterop';
|
|||
import './GlobalExports';
|
||||
import * as signalR from '@aspnet/signalr';
|
||||
import { MessagePackHubProtocol } from '@aspnet/signalr-protocol-msgpack';
|
||||
import { showErrorNotification } from './BootErrors';
|
||||
import { shouldAutoStart } from './BootCommon';
|
||||
import { RenderQueue } from './Platform/Circuits/RenderQueue';
|
||||
import { ConsoleLogger } from './Platform/Logging/Loggers';
|
||||
|
|
@ -106,6 +107,7 @@ async function initializeConnection(options: BlazorOptions, logger: Logger, circ
|
|||
connection.on('JS.Error', error => {
|
||||
renderingFailed = true;
|
||||
unhandledError(connection, error, logger);
|
||||
showErrorNotification();
|
||||
});
|
||||
|
||||
window['Blazor']._internal.forceCloseConnection = () => connection.stop();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
let hasFailed = false;
|
||||
|
||||
export async function showErrorNotification() {
|
||||
let errorUi = document.querySelector('#blazor-error-ui') as HTMLElement;
|
||||
if (errorUi) {
|
||||
errorUi.style.display = 'block';
|
||||
}
|
||||
|
||||
if (!hasFailed) {
|
||||
hasFailed = true;
|
||||
const errorUiReloads = document.querySelectorAll<HTMLElement>('#blazor-error-ui .reload');
|
||||
errorUiReloads.forEach(reload => {
|
||||
reload.onclick = function (e) {
|
||||
location.reload();
|
||||
e.preventDefault();
|
||||
};
|
||||
});
|
||||
|
||||
let errorUiDismiss = document.querySelectorAll<HTMLElement>('#blazor-error-ui .dismiss');
|
||||
errorUiDismiss.forEach(dismiss => {
|
||||
dismiss.onclick = function (e) {
|
||||
const errorUi = document.querySelector<HTMLElement>('#blazor-error-ui');
|
||||
if (errorUi) {
|
||||
errorUi.style.display = 'none';
|
||||
}
|
||||
e.preventDefault();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { getFileNameFromUrl } from '../Url';
|
||||
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||
import { showErrorNotification } from '../../BootErrors';
|
||||
|
||||
const assemblyHandleCache: { [assemblyName: string]: number } = {};
|
||||
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
|
||||
|
|
@ -232,7 +233,11 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
const suppressMessages = ['DEBUGGING ENABLED'];
|
||||
|
||||
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
|
||||
module.printErr = line => console.error(`WASM: ${line}`);
|
||||
|
||||
module.printErr = line => {
|
||||
console.error(`WASM: ${line}`);
|
||||
showErrorNotification();
|
||||
};
|
||||
module.preRun = [];
|
||||
module.postRun = [];
|
||||
module.preloadPlugins = [];
|
||||
|
|
|
|||
|
|
@ -341,9 +341,18 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null) {
|
||||
private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null): boolean {
|
||||
// Certain elements have built-in behaviour for their 'value' property
|
||||
const frameReader = batch.frameReader;
|
||||
|
||||
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'time' && !element.getAttribute('step')) {
|
||||
const timeValue = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
|
||||
if (timeValue) {
|
||||
element['value'] = timeValue.substring(0, 5);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (element.tagName) {
|
||||
case 'INPUT':
|
||||
case 'SELECT':
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
export class EventForDotNet<TData extends UIEventArgs> {
|
||||
constructor(public readonly type: EventArgsType, public readonly data: TData) {
|
||||
public constructor(public readonly type: EventArgsType, public readonly data: TData) {
|
||||
}
|
||||
|
||||
static fromDOMEvent(event: Event): EventForDotNet<UIEventArgs> {
|
||||
public static fromDOMEvent(event: Event): EventForDotNet<UIEventArgs> {
|
||||
const element = event.target as Element;
|
||||
switch (event.type) {
|
||||
|
||||
case 'input':
|
||||
case 'change': {
|
||||
|
||||
if (isTimeBasedInput(element)) {
|
||||
const normalizedValue = normalizeTimeBasedValue(element);
|
||||
return new EventForDotNet<UIChangeEventArgs>('change', { type: event.type, value: normalizedValue });
|
||||
}
|
||||
|
||||
const targetIsCheckbox = isCheckbox(element);
|
||||
const newValue = targetIsCheckbox ? !!element['checked'] : element['value'];
|
||||
return new EventForDotNet<UIChangeEventArgs>('change', { type: event.type, value: newValue });
|
||||
|
|
@ -36,7 +42,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'keydown':
|
||||
case 'keyup':
|
||||
case 'keypress':
|
||||
return new EventForDotNet<UIKeyboardEventArgs>('keyboard', parseKeyboardEvent(<KeyboardEvent>event));
|
||||
return new EventForDotNet<UIKeyboardEventArgs>('keyboard', parseKeyboardEvent(event as KeyboardEvent));
|
||||
|
||||
case 'contextmenu':
|
||||
case 'click':
|
||||
|
|
@ -46,10 +52,10 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'mousedown':
|
||||
case 'mouseup':
|
||||
case 'dblclick':
|
||||
return new EventForDotNet<UIMouseEventArgs>('mouse', parseMouseEvent(<MouseEvent>event));
|
||||
return new EventForDotNet<UIMouseEventArgs>('mouse', parseMouseEvent(event as MouseEvent));
|
||||
|
||||
case 'error':
|
||||
return new EventForDotNet<UIErrorEventArgs>('error', parseErrorEvent(<ErrorEvent>event));
|
||||
return new EventForDotNet<UIErrorEventArgs>('error', parseErrorEvent(event as ErrorEvent));
|
||||
|
||||
case 'loadstart':
|
||||
case 'timeout':
|
||||
|
|
@ -57,7 +63,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'load':
|
||||
case 'loadend':
|
||||
case 'progress':
|
||||
return new EventForDotNet<UIProgressEventArgs>('progress', parseProgressEvent(<ProgressEvent>event));
|
||||
return new EventForDotNet<UIProgressEventArgs>('progress', parseProgressEvent(event as ProgressEvent));
|
||||
|
||||
case 'touchcancel':
|
||||
case 'touchend':
|
||||
|
|
@ -65,7 +71,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'touchenter':
|
||||
case 'touchleave':
|
||||
case 'touchstart':
|
||||
return new EventForDotNet<UITouchEventArgs>('touch', parseTouchEvent(<TouchEvent>event));
|
||||
return new EventForDotNet<UITouchEventArgs>('touch', parseTouchEvent(event as TouchEvent));
|
||||
|
||||
case 'gotpointercapture':
|
||||
case 'lostpointercapture':
|
||||
|
|
@ -77,11 +83,11 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'pointerout':
|
||||
case 'pointerover':
|
||||
case 'pointerup':
|
||||
return new EventForDotNet<UIPointerEventArgs>('pointer', parsePointerEvent(<PointerEvent>event));
|
||||
return new EventForDotNet<UIPointerEventArgs>('pointer', parsePointerEvent(event as PointerEvent));
|
||||
|
||||
case 'wheel':
|
||||
case 'mousewheel':
|
||||
return new EventForDotNet<UIWheelEventArgs>('wheel', parseWheelEvent(<WheelEvent>event));
|
||||
return new EventForDotNet<UIWheelEventArgs>('wheel', parseWheelEvent(event as WheelEvent));
|
||||
|
||||
default:
|
||||
return new EventForDotNet<UIEventArgs>('unknown', { type: event.type });
|
||||
|
|
@ -204,8 +210,38 @@ function parseMouseEvent(event: MouseEvent) {
|
|||
};
|
||||
}
|
||||
|
||||
function isCheckbox(element: Element | null) {
|
||||
return element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||
function isCheckbox(element: Element | null): boolean {
|
||||
return !!element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||
}
|
||||
|
||||
const timeBasedInputs = [
|
||||
'date',
|
||||
'datetime-local',
|
||||
'month',
|
||||
'time',
|
||||
'week',
|
||||
];
|
||||
|
||||
function isTimeBasedInput(element: Element): element is HTMLInputElement {
|
||||
return timeBasedInputs.indexOf(element.getAttribute('type')!) !== -1;
|
||||
}
|
||||
|
||||
function normalizeTimeBasedValue(element: HTMLInputElement): string {
|
||||
const value = element.value;
|
||||
const type = element.type;
|
||||
switch (type) {
|
||||
case 'date':
|
||||
case 'datetime-local':
|
||||
case 'month':
|
||||
return value;
|
||||
case 'time':
|
||||
return value.length === 5 ? value + ':00' : value; // Convert hh:mm to hh:mm:00
|
||||
case 'week':
|
||||
// For now we are not going to normalize input type week as it is not trivial
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid element type '${type}'.`);
|
||||
}
|
||||
|
||||
// The following interfaces must be kept in sync with the UIEventArgs C# classes
|
||||
|
|
|
|||
|
|
@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
|
||||
public static partial class BindAttributes
|
||||
|
|
|
|||
|
|
@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
|
||||
public static partial class BindAttributes
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.Forms
|
|||
// of it for us. We will only get asked to parse the T for nonempty inputs.
|
||||
var targetType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
|
||||
if (targetType == typeof(int) ||
|
||||
targetType == typeof(long) ||
|
||||
targetType == typeof(float) ||
|
||||
targetType == typeof(double) ||
|
||||
targetType == typeof(decimal))
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[BindInputElement("date", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
|
||||
[BindInputElement("date", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
|
||||
|
||||
// type="datetime-local" is invariant culture with a specific format.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
|
||||
[BindInputElement("datetime-local", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
|
||||
[BindInputElement("datetime-local", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
|
||||
|
||||
// type="month" is invariant culture with a specific format.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
|
||||
[BindInputElement("month", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
|
||||
[BindInputElement("month", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
|
||||
|
||||
// type="time" is invariant culture with a specific format.
|
||||
[BindInputElement("time", null, "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
|
||||
[BindInputElement("time", "value", "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
|
||||
|
||||
[BindElement("select", null, "value", "onchange")]
|
||||
[BindElement("textarea", null, "value", "onchange")]
|
||||
public static class BindAttributes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using TestServer;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||
{
|
||||
public class ComponentWithParametersTest : ServerTestBase<BasicTestAppServerSiteFixture<PrerenderedStartup>>
|
||||
{
|
||||
public ComponentWithParametersTest(
|
||||
BrowserFixture browserFixture,
|
||||
BasicTestAppServerSiteFixture<PrerenderedStartup> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PassingParametersToComponentsFromThePageWorks()
|
||||
{
|
||||
Navigate("/prerendered/componentwithparameters?QueryValue=testQueryValue");
|
||||
|
||||
BeginInteractivity();
|
||||
|
||||
Browser.Exists(By.CssSelector(".interactive"));
|
||||
|
||||
var parameter1 = Browser.FindElement(By.CssSelector(".Param1"));
|
||||
Assert.Equal(100, parameter1.FindElements(By.CssSelector("li")).Count);
|
||||
Assert.Equal("99 99", parameter1.FindElement(By.CssSelector("li:last-child")).Text);
|
||||
|
||||
// The assigned value is of a more derived type than the declared model type. This check
|
||||
// verifies we use the actual model type during round tripping.
|
||||
var parameter2 = Browser.FindElement(By.CssSelector(".Param2"));
|
||||
Assert.Equal("Value Derived-Value", parameter2.Text);
|
||||
|
||||
// This check verifies CaptureUnmatchedValues works
|
||||
var parameter3 = Browser.FindElements(By.CssSelector(".Param3 li"));
|
||||
Assert.Collection(
|
||||
parameter3,
|
||||
p => Assert.Equal("key1 testQueryValue", p.Text),
|
||||
p => Assert.Equal("key2 43", p.Text));
|
||||
}
|
||||
|
||||
private void BeginInteractivity()
|
||||
{
|
||||
Browser.FindElement(By.Id("load-boot-script")).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -174,6 +174,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Browser.Equal(9000.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(9000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
// long
|
||||
input = Browser.FindElement(By.Id("inputnumber_long"));
|
||||
display = Browser.FindElement(By.Id("inputnumber_long_value"));
|
||||
Browser.Equal(4200.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(4200.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
input.Clear();
|
||||
input.SendKeys(90000000000.ToString(CultureInfo.InvariantCulture));
|
||||
input.SendKeys("\t");
|
||||
Browser.Equal(90000000000.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(90000000000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
// decimal
|
||||
input = Browser.FindElement(By.Id("inputnumber_decimal"));
|
||||
display = Browser.FindElement(By.Id("inputnumber_decimal_value"));
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Moq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
|
|
@ -1018,5 +1020,263 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
|
||||
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindDateTimeLocalTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("datetime-local-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-mirror"));
|
||||
var expected = new DateTime(1985, 3, 4);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 01/01/0001 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#datetime-local-textbox-datetime", "2000-01-02T04:05:06");
|
||||
expected = new DateTime(2000, 1, 2, 04, 05, 06);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindDateTimeLocalTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#datetime-local-textbox-nullable-datetime", "2000-01-02T04:05:06");
|
||||
var expected = new DateTime(2000, 1, 2, 04, 05, 06);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindMonthTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("month-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("month-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("month-textbox-datetime-mirror"));
|
||||
var expected = new DateTime(1985, 3, 1);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
// When the value gets displayed the first time it gets truncated to the 1st day,
|
||||
// until there is no change the bound value doesn't get updated.
|
||||
Assert.Equal(expected.AddDays(3), DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected.AddDays(3), DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 01/01/0001 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#month-textbox-datetime", "2000-02");
|
||||
expected = new DateTime(2000, 2, 1);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindMonthTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("month-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#month-textbox-nullable-datetime", "2000-02");
|
||||
var expected = new DateTime(2000, 2, 1);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-textbox-datetime-mirror"));
|
||||
var expected = DateTime.Now.Date.AddHours(8).AddMinutes(5);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 00:00 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(default, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-textbox-datetime", "04:05");
|
||||
expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-textbox-nullable-datetime", "05:06");
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeStepTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-step-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-step-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-datetime-mirror"));
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(8, 5, 30));
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 00:00 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(default, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-step-textbox-datetime", "04:05:06");
|
||||
expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 6));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeStepTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-step-textbox-nullable-datetime", "05:06");
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// Applies an input through javascript to datetime-local/month/time controls.
|
||||
private void ApplyInputValue(string cssSelector, string value)
|
||||
{
|
||||
// It's very difficult to enter an invalid value into an <input type=date>, because
|
||||
// most combinations of keystrokes get normalized to something valid. Additionally,
|
||||
// using Selenium's SendKeys interacts unpredictably with this normalization logic,
|
||||
// most likely based on timings. As a workaround, use JS to apply the values. This
|
||||
// should only be used when strictly necessary, as it doesn't represent actual user
|
||||
// interaction as authentically as SendKeys in other cases.
|
||||
var javascript = (IJavaScriptExecutor)Browser;
|
||||
javascript.ExecuteScript(
|
||||
$"var elem = document.querySelector('{cssSelector}');"
|
||||
+ $"elem.value = '{value}';"
|
||||
+ "elem.dispatchEvent(new KeyboardEvent('change'));");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
|
||||
public class ErrorNotificationClientSideTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
|
||||
{
|
||||
public ErrorNotificationClientSideTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
Browser.MountTestComponent<ErrorComponent>();
|
||||
Browser.Exists(By.Id("blazor-error-ui"));
|
||||
Browser.Exists(By.TagName("button"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowsErrorNotification_OnError_Dismiss()
|
||||
{
|
||||
var errorUi = Browser.FindElement(By.Id("blazor-error-ui"));
|
||||
Assert.Equal("none", errorUi.GetCssValue("display"));
|
||||
|
||||
var causeErrorButton = Browser.FindElement(By.TagName("button"));
|
||||
causeErrorButton.Click();
|
||||
|
||||
Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
|
||||
|
||||
var reload = Browser.FindElement(By.ClassName("reload"));
|
||||
reload.Click();
|
||||
|
||||
Browser.DoesNotExist(By.TagName("button"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowsErrorNotification_OnError_Reload()
|
||||
{
|
||||
var causeErrorButton = Browser.Exists(By.TagName("button"));
|
||||
var errorUi = Browser.FindElement(By.Id("blazor-error-ui"));
|
||||
Assert.Equal("none", errorUi.GetCssValue("display"));
|
||||
|
||||
causeErrorButton.Click();
|
||||
Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"));
|
||||
|
||||
var dismiss = Browser.FindElement(By.ClassName("dismiss"));
|
||||
dismiss.Click();
|
||||
Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: none;']"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
|
||||
public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest
|
||||
{
|
||||
public ErrorNotificationServerSideTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture.WithServerExecution(), output)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ using OpenQA.Selenium;
|
|||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
|
|
@ -68,6 +69,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["testDtoAsync"] = "Same",
|
||||
["returnPrimitiveAsync"] = "123",
|
||||
["returnArrayAsync"] = "first,second",
|
||||
["syncGenericInstanceMethod"] = @"""Initial value""",
|
||||
["asyncGenericInstanceMethod"] = @"""Updated value 1""",
|
||||
};
|
||||
|
||||
var expectedSyncValues = new Dictionary<string, string>
|
||||
|
|
@ -102,6 +105,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
["testDtoSync"] = "Same",
|
||||
["returnPrimitive"] = "123",
|
||||
["returnArray"] = "first,second",
|
||||
["genericInstanceMethod"] = @"""Updated value 2""",
|
||||
};
|
||||
|
||||
// Include the sync assertions only when running under WebAssembly
|
||||
|
|
@ -132,13 +136,17 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Assert
|
||||
foreach (var expectedValue in expectedValues)
|
||||
{
|
||||
var actualValue = actualValues[expectedValue.Key];
|
||||
if (expectedValue.Key.Contains("Exception"))
|
||||
{
|
||||
Assert.StartsWith(expectedValue.Value, actualValues[expectedValue.Key]);
|
||||
Assert.StartsWith(expectedValue.Value, actualValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedValue.Value, actualValues[expectedValue.Key]);
|
||||
if (expectedValue.Value != actualValue)
|
||||
{
|
||||
throw new AssertActualExpectedException(expectedValue.Value, actualValue, $"Scenario '{expectedValue.Key}' failed. Expected '{expectedValue.Value}, Actual {actualValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,63 @@
|
|||
<span id="select-markup-box-value">@selectMarkupValue</span>
|
||||
</p>
|
||||
|
||||
<h2>Time related inputs (datetime-local, month, time)</h2>
|
||||
<h3>datetime-local</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="datetime-local-textbox-datetime" @bind="dateTimeLocalTextboxDateTimeValue" type="datetime-local" />
|
||||
<span id="datetime-local-textbox-datetime-value">@dateTimeLocalTextboxDateTimeValue</span>
|
||||
<input id="datetime-local-textbox-datetime-mirror" @bind="dateTimeLocalTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="datetime-local-textbox-nullable-datetime" @bind-value="dateTimeLocalTextboxNullableDateTimeValue" type="datetime-local" />
|
||||
<span id="datetime-local-textbox-nullable-datetime-value">@dateTimeLocalTextboxNullableDateTimeValue</span>
|
||||
<input id="datetime-local-textbox-nullable-datetime-mirror" @bind="dateTimeLocalTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>month</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="month-textbox-datetime" @bind="monthTextboxDateTimeValue" type="month" />
|
||||
<span id="month-textbox-datetime-value">@monthTextboxDateTimeValue</span>
|
||||
<input id="month-textbox-datetime-mirror" @bind="monthTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="month-textbox-nullable-datetime" @bind-value="monthTextboxNullableDateTimeValue" type="month" />
|
||||
<span id="month-textbox-nullable-datetime-value">@monthTextboxNullableDateTimeValue</span>
|
||||
<input id="month-textbox-nullable-datetime-mirror" @bind="monthTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>time</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="time-textbox-datetime" @bind="timeTextboxDateTimeValue" type="time" />
|
||||
<span id="time-textbox-datetime-value">@timeTextboxDateTimeValue</span>
|
||||
<input id="time-textbox-datetime-mirror" @bind="timeTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="time-textbox-nullable-datetime" @bind-value="timeTextboxNullableDateTimeValue" type="time" />
|
||||
<span id="time-textbox-nullable-datetime-value">@timeTextboxNullableDateTimeValue</span>
|
||||
<input id="time-textbox-nullable-datetime-mirror" @bind="timeTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>time with step (supports seconds)</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="time-step-textbox-datetime" @bind="timeStepTextboxDateTimeValue" type="time" step="1" />
|
||||
<span id="time-step-textbox-datetime-value">@timeStepTextboxDateTimeValue</span>
|
||||
<input id="time-step-textbox-datetime-mirror" @bind="timeStepTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="time-step-textbox-nullable-datetime" @bind-value="timeStepTextboxNullableDateTimeValue" type="time" step="1" />
|
||||
<span id="time-step-textbox-nullable-datetime-value">@timeStepTextboxNullableDateTimeValue</span>
|
||||
<input id="time-step-textbox-nullable-datetime-mirror" @bind="timeStepTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
@code {
|
||||
string textboxInitiallyBlankValue = null;
|
||||
string textboxInitiallyPopulatedValue = "Hello";
|
||||
|
|
@ -297,6 +354,18 @@
|
|||
DateTime textboxDateTimeFormatInvalidValue = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset? textboxNullableDateTimeOffsetFormatInvalidValue = null;
|
||||
|
||||
DateTime dateTimeLocalTextboxDateTimeValue = new DateTime(1985, 3, 4);
|
||||
DateTime? dateTimeLocalTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime monthTextboxDateTimeValue = new DateTime(1985, 3, 4);
|
||||
DateTime? monthTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime timeTextboxDateTimeValue = DateTime.Now.Date.Add(new TimeSpan(8, 5, 0));
|
||||
DateTime? timeTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime timeStepTextboxDateTimeValue = DateTime.Now.Date.Add(new TimeSpan(8, 5, 30));
|
||||
DateTime? timeStepTextboxNullableDateTimeValue = null;
|
||||
|
||||
bool includeFourthOption = false;
|
||||
enum SelectableValue { First, Second, Third, Fourth }
|
||||
SelectableValue selectValue = SelectableValue.Second;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<h3 class="interactive">Component With Parameters</h3>
|
||||
|
||||
<ul class="Param1">
|
||||
@foreach (var value in Param1)
|
||||
{
|
||||
<li>@value.StringProperty @value.IntProperty</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@* Making sure polymorphism works *@
|
||||
<div class="Param2">@DerivedParam2.StringProperty @DerivedParam2.DerivedProperty</div>
|
||||
|
||||
@* Making sure CaptureUnmatchedValues works *@
|
||||
|
||||
<ul class="Param3">
|
||||
@foreach (var value in Param3.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
<li>@value.Key @value.Value</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public List<TestModel> Param1 { get; set; }
|
||||
|
||||
[Parameter] public TestModel Param2 { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> Param3 { get; set; }
|
||||
|
||||
private DerivedModel DerivedParam2 => (DerivedModel)Param2;
|
||||
|
||||
public static List<TestModel> TestModelValues => Enumerable.Range(0, 100).Select(c => new TestModel { StringProperty = c.ToString(), IntProperty = c }).ToList();
|
||||
|
||||
public static DerivedModel DerivedModelValue = new DerivedModel { StringProperty = "Value", DerivedProperty = "Derived-Value" };
|
||||
|
||||
public class TestModel
|
||||
{
|
||||
|
||||
public string StringProperty { get; set; }
|
||||
|
||||
public int IntProperty { get; set; }
|
||||
}
|
||||
|
||||
public class DerivedModel : TestModel
|
||||
{
|
||||
public string DerivedProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<div>
|
||||
<h2>Error throwing button</h2>
|
||||
<p>
|
||||
<button @onclick="@(IncrementCount)">Click me</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
int currentCount = 0;
|
||||
|
||||
void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
throw new NotImplementedException("Doing crazy things!");
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,10 @@
|
|||
int: <InputNumber id="inputnumber_int" @bind-Value="inputNumberInt" />
|
||||
<span id="inputnumber_int_value">@inputNumberInt</span>
|
||||
</div>
|
||||
<div>
|
||||
long: <InputNumber id="inputnumber_long" @bind-Value="inputNumberLong" />
|
||||
<span id="inputnumber_long_value">@inputNumberLong</span>
|
||||
</div>
|
||||
<div>
|
||||
decimal: <InputNumber id="inputnumber_decimal" @bind-Value="inputNumberDecimal" />
|
||||
<span id="inputnumber_decimal_value">@inputNumberDecimal</span>
|
||||
|
|
@ -99,8 +103,18 @@
|
|||
DateTimeOffset inputTypeDateDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
int inputNumberInt = 42;
|
||||
long inputNumberLong = 4200;
|
||||
decimal inputNumberDecimal = 4.2m;
|
||||
|
||||
DateTime inputDateDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputDateDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeDateTimeLocalDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeDateTimeLocalDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeMonthDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeMonthDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeTimeDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeTimeDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
<div id="test-selector">
|
||||
Select test:
|
||||
Select test:
|
||||
<select id="test-selector-select" @bind=SelectedComponentTypeName>
|
||||
<option value="none">Choose...</option>
|
||||
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
||||
<option value="BasicTestApp.DuplicateAttributesComponent">Duplicate attributes</option>
|
||||
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
||||
<option value="BasicTestApp.ErrorComponent">Error throwing</option>
|
||||
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
||||
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
||||
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
|
||||
|
|
@ -82,6 +83,12 @@
|
|||
@((RenderFragment)RenderSelectedComponent)
|
||||
</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href class='reload'>Reload</a>
|
||||
<a class='dismiss' style="cursor: pointer;">🗙</a>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
string SelectedComponentTypeName { get; set; } = "none";
|
||||
|
||||
|
|
|
|||
|
|
@ -70,13 +70,15 @@
|
|||
var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"));
|
||||
var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123);
|
||||
var instanceMethodsTarget = new JavaScriptInterop();
|
||||
var genericType = new JavaScriptInterop.GenericType<string> { Value = "Initial value" };
|
||||
|
||||
Console.WriteLine("Starting interop invocations.");
|
||||
await JSRuntime.InvokeVoidAsync(
|
||||
"jsInteropTests.invokeDotNetInteropMethodsAsync",
|
||||
shouldSupportSyncInterop,
|
||||
DotNetObjectReference.Create(testDTOTOPassByRef),
|
||||
DotNetObjectReference.Create(instanceMethodsTarget));
|
||||
DotNetObjectReference.Create(instanceMethodsTarget),
|
||||
DotNetObjectReference.Create(genericType));
|
||||
|
||||
if (shouldSupportSyncInterop)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -462,5 +462,25 @@ namespace BasicTestApp.InteropTest
|
|||
|
||||
public DotNetObjectReference<TestDTO> OutgoingByRef { get; set; }
|
||||
}
|
||||
|
||||
public class GenericType<TValue>
|
||||
{
|
||||
public TValue Value { get; set; }
|
||||
|
||||
[JSInvokable]
|
||||
public TValue Update(TValue newValue)
|
||||
{
|
||||
var oldValue = Value;
|
||||
Value = newValue;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task<TValue> UpdateAsync(TValue newValue)
|
||||
{
|
||||
await Task.Yield();
|
||||
return Update(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Basic test app</title>
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
<!-- Used by ExternalContentPackage -->
|
||||
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<root>Loading...</root>
|
||||
|
||||
|
|
@ -31,4 +33,5 @@
|
|||
<!-- Used by ExternalContentPackage -->
|
||||
<script src="_content/TestContentPackage/prompt.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
var results = {};
|
||||
var assemblyName = 'BasicTestApp';
|
||||
|
||||
function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectByRef, instanceMethodsTarget) {
|
||||
async function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectByRef, instanceMethodsTarget, genericDotNetObjectByRef) {
|
||||
if (shouldSupportSyncInterop) {
|
||||
console.log('Invoking void sync methods.');
|
||||
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
|
||||
|
|
@ -15,6 +15,7 @@ function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectB
|
|||
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
|
||||
|
||||
|
||||
console.log('Invoking returning sync methods.');
|
||||
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
|
||||
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
|
||||
|
|
@ -40,75 +41,73 @@ function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectB
|
|||
}
|
||||
|
||||
console.log('Invoking void async methods.');
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||
.then(() => {
|
||||
console.log('Invoking returning async methods.');
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync')
|
||||
.then(r => results['result1Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef)))
|
||||
.then(r => results['result2Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||
.then(r => results['result3Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef)))
|
||||
.then(r => results['result4Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||
.then(r => results['result5Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef)))
|
||||
.then(r => results['result6Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||
.then(r => results['result7Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef)))
|
||||
.then(r => results['result8Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||
.then(r => results['result9Async'] = r)
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync'))
|
||||
.then(r => DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']))
|
||||
.then(r => {
|
||||
results['resultReturnDotNetObjectByRefAsync'] = r;
|
||||
})
|
||||
.then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
|
||||
stringValue: 'My string',
|
||||
dtoByRef: dotNetObjectByRef
|
||||
}))
|
||||
.then(r => {
|
||||
results['instanceMethodThisTypeNameAsync'] = r.thisTypeName;
|
||||
results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper;
|
||||
results['instanceMethodIncomingByRefAsync'] = r.incomingByRef;
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef);
|
||||
}).then(r => {
|
||||
results['instanceMethodOutgoingByRefAsync'] = r;
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Invoking methods that throw exceptions');
|
||||
try {
|
||||
shouldSupportSyncInterop && DotNet.invokeMethod(assemblyName, 'ThrowException');
|
||||
} catch (e) {
|
||||
results['ThrowException'] = e.message;
|
||||
}
|
||||
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowSyncException')
|
||||
.catch(e => {
|
||||
results['AsyncThrowSyncException'] = e.message;
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync');
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef));
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef));
|
||||
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowAsyncException');
|
||||
}).catch(e => {
|
||||
results['AsyncThrowAsyncException'] = e.message;
|
||||
console.log('Invoking returning async methods.');
|
||||
results['result1Async'] = await DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync');
|
||||
results['result2Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef));
|
||||
results['result3Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef));
|
||||
results['result4Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef));
|
||||
results['result5Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef));
|
||||
results['result6Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef));
|
||||
results['result7Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef));
|
||||
results['result8Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef));
|
||||
results['result9Async'] = await DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef));
|
||||
|
||||
console.log('Done invoking interop methods');
|
||||
});
|
||||
});
|
||||
const returnDotNetObjectByRefAsync = await DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync');
|
||||
results['resultReturnDotNetObjectByRefAsync'] = await DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefAsync['Some async instance']);
|
||||
|
||||
const instanceMethodAsync = await instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
|
||||
stringValue: 'My string',
|
||||
dtoByRef: dotNetObjectByRef
|
||||
});
|
||||
|
||||
results['instanceMethodThisTypeNameAsync'] = instanceMethodAsync.thisTypeName;
|
||||
results['instanceMethodStringValueUpperAsync'] = instanceMethodAsync.stringValueUpper;
|
||||
results['instanceMethodIncomingByRefAsync'] = instanceMethodAsync.incomingByRef;
|
||||
results['instanceMethodOutgoingByRefAsync'] = await DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', instanceMethodAsync.outgoingByRef);
|
||||
|
||||
console.log('Invoking generic type instance methods.');
|
||||
|
||||
results['syncGenericInstanceMethod'] = await genericDotNetObjectByRef.invokeMethodAsync('Update', 'Updated value 1');
|
||||
results['asyncGenericInstanceMethod'] = await genericDotNetObjectByRef.invokeMethodAsync('UpdateAsync', 'Updated value 2');
|
||||
|
||||
if (shouldSupportSyncInterop) {
|
||||
results['genericInstanceMethod'] = genericDotNetObjectByRef.invokeMethod('Update', 'Updated Value 3');
|
||||
}
|
||||
|
||||
console.log('Invoking methods that throw exceptions');
|
||||
try {
|
||||
shouldSupportSyncInterop && DotNet.invokeMethod(assemblyName, 'ThrowException');
|
||||
} catch (e) {
|
||||
results['ThrowException'] = e.message;
|
||||
}
|
||||
|
||||
try {
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowSyncException');
|
||||
} catch (e) {
|
||||
results['AsyncThrowSyncException'] = e.message;
|
||||
}
|
||||
|
||||
try {
|
||||
await DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowAsyncException');
|
||||
} catch (e) {
|
||||
results['AsyncThrowAsyncException'] = e.message;
|
||||
}
|
||||
|
||||
console.log('Done invoking interop methods');
|
||||
}
|
||||
|
||||
function createArgumentList(argumentNumber, dotNetObjectByRef){
|
||||
function createArgumentList(argumentNumber, dotNetObjectByRef) {
|
||||
const array = new Array(argumentNumber);
|
||||
if (argumentNumber === 0) {
|
||||
return [];
|
||||
|
|
@ -149,7 +148,7 @@ function createArgumentList(argumentNumber, dotNetObjectByRef){
|
|||
source: `Some random text with at least ${i} characters`,
|
||||
start: argumentNumber + 1,
|
||||
length: argumentNumber + 1
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
console.log(i);
|
||||
|
|
@ -169,7 +168,7 @@ window.jsInteropTests = {
|
|||
returnPrimitive: returnPrimitive,
|
||||
returnPrimitiveAsync: returnPrimitiveAsync,
|
||||
receiveDotNetObjectByRef: receiveDotNetObjectByRef,
|
||||
receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync,
|
||||
receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync
|
||||
};
|
||||
|
||||
function returnPrimitive() {
|
||||
|
|
@ -211,9 +210,9 @@ function asyncFunctionThrowsAsyncException() {
|
|||
}
|
||||
|
||||
function asyncFunctionTakesLongerThanDefaultTimeoutToResolve() {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(undefined), 5000);
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(undefined), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function collectInteropResults() {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#blazor-error-ui dismiss {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<app>@(await Html.RenderComponentAsync<App>(RenderMode.Server))</app>
|
||||
<component type="typeof(App)" render-mode="Server" />
|
||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||
<script>
|
||||
Blazor.start({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
@page
|
||||
|
||||
<component type="typeof(ComponentWithParameters)"
|
||||
render-mode="ServerPrerendered"
|
||||
param-Param1="ComponentWithParameters.TestModelValues"
|
||||
param-Param2="ComponentWithParameters.DerivedModelValue"
|
||||
param-key1="QueryValue"
|
||||
param-key2="43" />
|
||||
|
||||
@*
|
||||
So that E2E tests can make assertions about both the prerendered and
|
||||
interactive states, we only load the .js file when told to.
|
||||
*@
|
||||
<hr />
|
||||
|
||||
<button id="load-boot-script" onclick="start()">Load boot script</button>
|
||||
|
||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||
<script>
|
||||
// Used by InteropOnInitializationComponent
|
||||
function setElementValue(element, newValue) {
|
||||
element.value = newValue;
|
||||
return element.value;
|
||||
}
|
||||
|
||||
function start() {
|
||||
Blazor.start({
|
||||
logLevel: 1 // LogLevel.Debug
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@functions
|
||||
{
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string QueryValue { get; set; }
|
||||
}
|
||||
|
|
@ -12,24 +12,24 @@
|
|||
<div id="test-container">
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Static, new { Name = "John" }))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
||||
<component type="typeof(GreeterComponent)" render-mode="Static" param-name='"John"' />
|
||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
||||
<div id="container">
|
||||
<p>Some content before</p>
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
||||
<p>Some content between</p>
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
|
||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
|
||||
<p>Some content after</p>
|
||||
<div id="nested-an-extra-level">
|
||||
<p>Some content before</p>
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered))
|
||||
<component type="typeof(GreeterComponent)" render-mode="Server"/>
|
||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered"/>
|
||||
<p>Some content after</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="container">
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server, new { Name = "Albert" }))
|
||||
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered, new { Name = "Abraham" }))
|
||||
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
|
||||
<component type="typeof(GreeterComponent)" render-mode="ServerPrerendered" param-name='"Abraham"' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@page
|
||||
@using BasicTestApp.RouterTest
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -7,7 +8,7 @@
|
|||
<base href="~/" />
|
||||
</head>
|
||||
<body>
|
||||
<app>@(await Html.RenderComponentAsync<TestRouter>(RenderMode.ServerPrerendered))</app>
|
||||
<app><component type="typeof(TestRouter)" render-mode="ServerPrerendered" /></app>
|
||||
|
||||
@*
|
||||
So that E2E tests can make assertions about both the prerendered and
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@page ""
|
||||
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<root>@(await Html.RenderComponentAsync<BasicTestApp.Index>(RenderMode.Server))</root>
|
||||
<root><component type="typeof(BasicTestApp.Index)" render-mode="Server" /></root>
|
||||
|
||||
<!-- Used for testing interop scenarios between JS and .NET -->
|
||||
<script src="js/jsinteroptests.js"></script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BasicTestApp
|
||||
|
||||
|
|
@ -3221,6 +3221,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
|||
public partial class ValidationVisitor
|
||||
{
|
||||
public ValidationVisitor(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider validatorProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidatorCache validatorCache, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider metadataProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary validationState) { }
|
||||
[System.ObsoleteAttribute("This property is deprecated and is no longer used by the runtime.")]
|
||||
public bool AllowShortCircuitingValidationWhenNoValidatorsArePresent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
protected Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidatorCache Cache { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected object Container { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
|
|
|
|||
|
|
@ -1894,7 +1894,10 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
detail: detail,
|
||||
instance: instance);
|
||||
|
||||
return new ObjectResult(problemDetails);
|
||||
return new ObjectResult(problemDetails)
|
||||
{
|
||||
StatusCode = problemDetails.Status
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1970,7 +1973,10 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
return new BadRequestObjectResult(validationProblem);
|
||||
}
|
||||
|
||||
return new ObjectResult(validationProblem);
|
||||
return new ObjectResult(validationProblem)
|
||||
{
|
||||
StatusCode = validationProblem.Status
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
|||
/// </summary>
|
||||
/// <value>The default value is <see langword="true"/>.</value>
|
||||
/// <remarks>This property is currently ignored.</remarks>
|
||||
[Obsolete("This property is deprecated and is no longer used by the runtime.")]
|
||||
public bool AllowShortCircuitingValidationWhenNoValidatorsArePresent { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2316,6 +2316,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
// Assert
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ValidationProblemDetails>(badRequestResult.Value);
|
||||
Assert.Equal(400, badRequestResult.StatusCode);
|
||||
Assert.Equal(400, problemDetails.Status);
|
||||
Assert.Equal("One or more validation errors occurred.", problemDetails.Title);
|
||||
Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.5.1", problemDetails.Type);
|
||||
|
|
@ -2348,6 +2349,48 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
Assert.Equal(detail, problemDetails.Detail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationProblemDetails_UsesSpecifiedStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
var options = GetApiBehaviorOptions();
|
||||
|
||||
var controller = new TestableController
|
||||
{
|
||||
ProblemDetailsFactory = new DefaultProblemDetailsFactory(Options.Create(options)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var actionResult = controller.ValidationProblem(statusCode: 405);
|
||||
|
||||
// Assert
|
||||
var objectResult = Assert.IsType<ObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ValidationProblemDetails>(objectResult.Value);
|
||||
Assert.Equal(405, objectResult.StatusCode);
|
||||
Assert.Equal(405, problemDetails.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationProblemDetails_StatusCode400_ReturnsBadRequestObjectResultFor2xCompatibility()
|
||||
{
|
||||
// Arrange
|
||||
var options = GetApiBehaviorOptions();
|
||||
|
||||
var controller = new TestableController
|
||||
{
|
||||
ProblemDetailsFactory = new DefaultProblemDetailsFactory(Options.Create(options)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var actionResult = controller.ValidationProblem(statusCode: 400);
|
||||
|
||||
// Assert
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ValidationProblemDetails>(badRequestResult.Value);
|
||||
Assert.Equal(400, badRequestResult.StatusCode);
|
||||
Assert.Equal(400, problemDetails.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProblemDetails_Works()
|
||||
{
|
||||
|
|
@ -2371,6 +2414,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
// Assert
|
||||
var badRequestResult = Assert.IsType<ObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ProblemDetails>(badRequestResult.Value);
|
||||
Assert.Equal(500, actionResult.StatusCode);
|
||||
Assert.Equal(500, problemDetails.Status);
|
||||
Assert.Equal("An error occured while processing your request.", problemDetails.Title);
|
||||
Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.6.1", problemDetails.Type);
|
||||
|
|
@ -2396,6 +2440,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
// Assert
|
||||
var badRequestResult = Assert.IsType<ObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ProblemDetails>(badRequestResult.Value);
|
||||
Assert.Equal(500, actionResult.StatusCode);
|
||||
Assert.Equal(500, problemDetails.Status);
|
||||
Assert.Equal(title, problemDetails.Title);
|
||||
Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.6.1", problemDetails.Type);
|
||||
|
|
@ -2419,6 +2464,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
// Assert
|
||||
var badRequestResult = Assert.IsType<ObjectResult>(actionResult);
|
||||
var problemDetails = Assert.IsType<ProblemDetails>(badRequestResult.Value);
|
||||
Assert.Equal(422, actionResult.StatusCode);
|
||||
Assert.Equal(422, problemDetails.Status);
|
||||
Assert.Equal("Unprocessable entity.", problemDetails.Title);
|
||||
Assert.Equal("https://tools.ietf.org/html/rfc4918#section-11.2", problemDetails.Type);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Newtonsoft.Json;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
|
|
@ -462,6 +464,30 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Single(formatterContext.ModelState["Person.Name"].Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_DoesNotDisposeBufferedReadStream()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var content = "{\"name\": \"Test\"}";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
var testBufferedReadStream = new Mock<FileBufferingReadStream>(httpContext.Request.Body, 1024) { CallBase = true };
|
||||
httpContext.Request.Body = testBufferedReadStream.Object;
|
||||
|
||||
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);
|
||||
|
||||
testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never());
|
||||
}
|
||||
|
||||
internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; }
|
||||
|
||||
internal abstract string JsonFormatter_EscapedKeys_Expected { get; }
|
||||
|
|
@ -517,7 +543,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
protected sealed class ComplexPoco
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Person Person{ get; set; }
|
||||
public Person Person { get; set; }
|
||||
}
|
||||
|
||||
protected sealed class Person
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
var request = context.HttpContext.Request;
|
||||
Stream readStream = new NonDisposableStream(request.Body);
|
||||
var disposeReadStream = false;
|
||||
|
||||
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
|
||||
{
|
||||
|
|
@ -135,6 +136,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
await readStream.DrainAsync(CancellationToken.None);
|
||||
readStream.Seek(0L, SeekOrigin.Begin);
|
||||
|
||||
disposeReadStream = true;
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -162,9 +165,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (readStream is FileBufferingReadStream fileBufferingReadStream)
|
||||
if (disposeReadStream)
|
||||
{
|
||||
await fileBufferingReadStream.DisposeAsync();
|
||||
await readStream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
var request = context.HttpContext.Request;
|
||||
Stream readStream = new NonDisposableStream(request.Body);
|
||||
var disposeReadStream = false;
|
||||
|
||||
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
|
||||
{
|
||||
// XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
|
||||
|
|
@ -115,6 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
await readStream.DrainAsync(CancellationToken.None);
|
||||
readStream.Seek(0L, SeekOrigin.Begin);
|
||||
disposeReadStream = true;
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -155,9 +158,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (readStream is FileBufferingReadStream fileBufferingReadStream)
|
||||
if (disposeReadStream)
|
||||
{
|
||||
await fileBufferingReadStream.DisposeAsync();
|
||||
await readStream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Xml;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -166,6 +167,39 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
Assert.Equal(expectedString, model.sampleString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_DoesNotDisposeBufferedStreamIfItDidNotCreateIt()
|
||||
{
|
||||
// Arrange
|
||||
var expectedInt = 10;
|
||||
var expectedString = "TestString";
|
||||
|
||||
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||
"<TestLevelOne><SampleInt>" + expectedInt + "</SampleInt>" +
|
||||
"<sampleString>" + expectedString + "</sampleString></TestLevelOne>";
|
||||
|
||||
var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions());
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(input);
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var testBufferedReadStream = new Mock<FileBufferingReadStream>(new MemoryStream(contentBytes), 1024) { CallBase = true };
|
||||
httpContext.Request.Body = testBufferedReadStream.Object;
|
||||
var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne));
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(result.Model);
|
||||
|
||||
Assert.Equal(expectedInt, model.SampleInt);
|
||||
Assert.Equal(expectedString, model.sampleString);
|
||||
|
||||
testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -622,6 +623,39 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), model.SampleDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_DoesNotDisposeBufferedStreamIfItDidNotCreateIt()
|
||||
{
|
||||
// Arrange
|
||||
var expectedInt = 10;
|
||||
var expectedString = "TestString";
|
||||
|
||||
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||
"<TestLevelOne><SampleInt>" + expectedInt + "</SampleInt>" +
|
||||
"<sampleString>" + expectedString + "</sampleString></TestLevelOne>";
|
||||
|
||||
var formatter = new XmlSerializerInputFormatter(new MvcOptions());
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(input);
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var testBufferedReadStream = new Mock<FileBufferingReadStream>(new MemoryStream(contentBytes), 1024) { CallBase = true };
|
||||
httpContext.Request.Body = testBufferedReadStream.Object;
|
||||
var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne));
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(result.Model);
|
||||
|
||||
Assert.Equal(expectedInt, model.SampleInt);
|
||||
Assert.Equal(expectedString, model.sampleString);
|
||||
|
||||
testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never());
|
||||
}
|
||||
|
||||
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
|
||||
{
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;
|
||||
|
||||
var readStream = request.Body;
|
||||
var disposeReadStream = false;
|
||||
if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
|
||||
{
|
||||
// JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
|
||||
|
|
@ -145,6 +146,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
await readStream.DrainAsync(CancellationToken.None);
|
||||
readStream.Seek(0L, SeekOrigin.Begin);
|
||||
|
||||
disposeReadStream = true;
|
||||
}
|
||||
|
||||
var successful = true;
|
||||
|
|
@ -170,9 +173,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
jsonSerializer.Error -= ErrorHandler;
|
||||
ReleaseJsonSerializer(jsonSerializer);
|
||||
|
||||
if (readStream is FileBufferingReadStream fileBufferingReadStream)
|
||||
if (disposeReadStream)
|
||||
{
|
||||
await fileBufferingReadStream.DisposeAsync();
|
||||
await readStream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="build\$(DefaultNetCoreTargetFramework)\*" Pack="true" PackagePath="build\$(DefaultNetCoreTargetFramework)" />
|
||||
<None Include="targets\$(PackageId).targets" Pack="true" PackagePath="build\$(DefaultNetCoreTargetFramework)\$(PackageId).targets" />
|
||||
<None Include="targets\$(PackageId).targets" Pack="true" PackagePath="buildTransitive\$(DefaultNetCoreTargetFramework)\$(PackageId).targets" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,22 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
public CacheTagHelperOptions() { }
|
||||
public long SizeLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
}
|
||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("component", Attributes="type", TagStructure=Microsoft.AspNetCore.Razor.TagHelpers.TagStructure.WithoutEndTag)]
|
||||
public sealed partial class ComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
||||
{
|
||||
public ComponentTagHelper() { }
|
||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("type")]
|
||||
public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("params", DictionaryAttributePrefix="param-")]
|
||||
public System.Collections.Generic.IDictionary<string, object> Parameters { get { throw null; } set { } }
|
||||
[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 { } }
|
||||
[Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute]
|
||||
public Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public override System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) { throw null; }
|
||||
}
|
||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("distributed-cache", Attributes="name")]
|
||||
public partial class DistributedCacheTagHelper : Microsoft.AspNetCore.Mvc.TagHelpers.CacheTagHelperBase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TagHelper"/> that renders a Razor component.
|
||||
/// </summary>
|
||||
[HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)]
|
||||
public sealed class ComponentTagHelper : TagHelper
|
||||
{
|
||||
private const string ComponentParameterName = "params";
|
||||
private const string ComponentParameterPrefix = "param-";
|
||||
private const string ComponentTypeName = "type";
|
||||
private const string RenderModeName = "render-mode";
|
||||
private IDictionary<string, object> _parameters;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Rendering.ViewContext"/> for the current request.
|
||||
/// </summary>
|
||||
[HtmlAttributeNotBound]
|
||||
[ViewContext]
|
||||
public ViewContext ViewContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets values for component parameters.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ComponentParameterName, DictionaryAttributePrefix = ComponentParameterPrefix)]
|
||||
public IDictionary<string, object> Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
_parameters ??= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
return _parameters;
|
||||
}
|
||||
set => _parameters = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the component type. This value is required.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ComponentTypeName)]
|
||||
public Type ComponentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Rendering.RenderMode"/>
|
||||
/// </summary>
|
||||
[HtmlAttributeName(RenderModeName)]
|
||||
public RenderMode RenderMode { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (output == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
|
||||
var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
|
||||
var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, _parameters);
|
||||
|
||||
// Reset the TagName. We don't want `component` to render.
|
||||
output.TagName = null;
|
||||
output.Content.SetHtmlContent(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
{
|
||||
public class ComponentTagHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task ProcessAsync_RendersComponent()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelper = new ComponentTagHelper
|
||||
{
|
||||
ViewContext = GetViewContext(),
|
||||
};
|
||||
var context = GetTagHelperContext();
|
||||
var output = GetTagHelperOutput();
|
||||
|
||||
// Act
|
||||
await tagHelper.ProcessAsync(context, output);
|
||||
|
||||
// Assert
|
||||
var content = HtmlContentUtilities.HtmlContentToString(output.Content);
|
||||
Assert.Equal("Hello world", content);
|
||||
Assert.Null(output.TagName);
|
||||
}
|
||||
|
||||
private static TagHelperContext GetTagHelperContext()
|
||||
{
|
||||
return new TagHelperContext(
|
||||
"component",
|
||||
new TagHelperAttributeList(),
|
||||
new Dictionary<object, object>(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
}
|
||||
|
||||
private static TagHelperOutput GetTagHelperOutput()
|
||||
{
|
||||
return new TagHelperOutput(
|
||||
"component",
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
}
|
||||
|
||||
private ViewContext GetViewContext()
|
||||
{
|
||||
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
|
||||
var renderer = Mock.Of<IComponentRenderer>(c =>
|
||||
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
|
||||
|
||||
var httpContext = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = new ServiceCollection().AddSingleton<IComponentRenderer>(renderer).BuildServiceProvider(),
|
||||
};
|
||||
|
||||
return new ViewContext
|
||||
{
|
||||
HttpContext = httpContext,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -330,8 +330,8 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
}
|
||||
public static partial class HtmlHelperComponentExtensions
|
||||
{
|
||||
public static System.Threading.Tasks.Task<Microsoft.AspNetCore.Html.IHtmlContent> RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object parameters) { throw null; }
|
||||
public static System.Threading.Tasks.Task<Microsoft.AspNetCore.Html.IHtmlContent> RenderComponentAsync<TComponent>(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public static System.Threading.Tasks.Task<Microsoft.AspNetCore.Html.IHtmlContent> RenderComponentAsync<TComponent>(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object parameters) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
|
||||
}
|
||||
public static partial class HtmlHelperDisplayExtensions
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.JSInterop;
|
||||
|
|
@ -206,8 +205,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<SaveTempDataFilter>();
|
||||
|
||||
//
|
||||
// Component prerendering
|
||||
// Component rendering
|
||||
//
|
||||
services.TryAddScoped<IComponentRenderer, ComponentRenderer>();
|
||||
services.TryAddScoped<StaticComponentRenderer>();
|
||||
services.TryAddScoped<NavigationManager, HttpNavigationManager>();
|
||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for rendering components.
|
||||
/// </summary>
|
||||
public static class HtmlHelperComponentExtensions
|
||||
{
|
||||
private static readonly object ComponentSequenceKey = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Renders the <typeparamref name="TComponent"/> <see cref="IComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||
/// <param name="renderMode">The <see cref="RenderMode"/> for the component.</param>
|
||||
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||
public static Task<IHtmlContent> RenderComponentAsync<TComponent>(this IHtmlHelper htmlHelper, RenderMode renderMode) where TComponent : IComponent
|
||||
{
|
||||
if (htmlHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(htmlHelper));
|
||||
}
|
||||
|
||||
return htmlHelper.RenderComponentAsync<TComponent>(renderMode, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the <typeparamref name="TComponent"/> <see cref="IComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||
/// <param name="parameters">An <see cref="object"/> containing the parameters to pass
|
||||
/// to the component.</param>
|
||||
/// <param name="renderMode">The <see cref="RenderMode"/> for the component.</param>
|
||||
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||
public static async Task<IHtmlContent> RenderComponentAsync<TComponent>(
|
||||
this IHtmlHelper htmlHelper,
|
||||
RenderMode renderMode,
|
||||
object parameters) where TComponent : IComponent
|
||||
{
|
||||
if (htmlHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(htmlHelper));
|
||||
}
|
||||
|
||||
var context = htmlHelper.ViewContext.HttpContext;
|
||||
return renderMode switch
|
||||
{
|
||||
RenderMode.Server => NonPrerenderedServerComponent(context, GetOrCreateInvocationId(htmlHelper.ViewContext), typeof(TComponent), GetParametersCollection(parameters)),
|
||||
RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(context, GetOrCreateInvocationId(htmlHelper.ViewContext), typeof(TComponent), GetParametersCollection(parameters)),
|
||||
RenderMode.Static => await StaticComponentAsync(context, typeof(TComponent), GetParametersCollection(parameters)),
|
||||
_ => throw new ArgumentException("Invalid render mode", nameof(renderMode)),
|
||||
};
|
||||
}
|
||||
|
||||
private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext)
|
||||
{
|
||||
if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result))
|
||||
{
|
||||
result = new ServerComponentInvocationSequence();
|
||||
viewContext.Items[ComponentSequenceKey] = result;
|
||||
}
|
||||
|
||||
return (ServerComponentInvocationSequence)result;
|
||||
}
|
||||
|
||||
private static ParameterView GetParametersCollection(object parameters) => parameters == null ?
|
||||
ParameterView.Empty :
|
||||
ParameterView.FromDictionary(HtmlHelper.ObjectToDictionary(parameters));
|
||||
|
||||
private static async Task<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var serviceProvider = context.RequestServices;
|
||||
var prerenderer = serviceProvider.GetRequiredService<StaticComponentRenderer>();
|
||||
|
||||
|
||||
var result = await prerenderer.PrerenderComponentAsync(
|
||||
parametersCollection,
|
||||
context,
|
||||
type);
|
||||
|
||||
return new ComponentHtmlContent(result);
|
||||
}
|
||||
|
||||
private static async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var serviceProvider = context.RequestServices;
|
||||
var prerenderer = serviceProvider.GetRequiredService<StaticComponentRenderer>();
|
||||
var invocationSerializer = serviceProvider.GetRequiredService<ServerComponentSerializer>();
|
||||
|
||||
var currentInvocation = invocationSerializer.SerializeInvocation(
|
||||
invocationId,
|
||||
type,
|
||||
parametersCollection,
|
||||
prerendered: true);
|
||||
|
||||
var result = await prerenderer.PrerenderComponentAsync(
|
||||
parametersCollection,
|
||||
context,
|
||||
type);
|
||||
|
||||
return new ComponentHtmlContent(
|
||||
invocationSerializer.GetPreamble(currentInvocation),
|
||||
result,
|
||||
invocationSerializer.GetEpilogue(currentInvocation));
|
||||
}
|
||||
|
||||
private static IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var serviceProvider = context.RequestServices;
|
||||
var invocationSerializer = serviceProvider.GetRequiredService<ServerComponentSerializer>();
|
||||
var currentInvocation = invocationSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false);
|
||||
|
||||
return new ComponentHtmlContent(invocationSerializer.GetPreamble(currentInvocation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal class ComponentRenderer : IComponentRenderer
|
||||
{
|
||||
private static readonly object ComponentSequenceKey = new object();
|
||||
private readonly StaticComponentRenderer _staticComponentRenderer;
|
||||
private readonly ServerComponentSerializer _serverComponentSerializer;
|
||||
|
||||
public ComponentRenderer(
|
||||
StaticComponentRenderer staticComponentRenderer,
|
||||
ServerComponentSerializer serverComponentSerializer)
|
||||
{
|
||||
_staticComponentRenderer = staticComponentRenderer;
|
||||
_serverComponentSerializer = serverComponentSerializer;
|
||||
}
|
||||
|
||||
public async Task<IHtmlContent> RenderComponentAsync(
|
||||
ViewContext viewContext,
|
||||
Type componentType,
|
||||
RenderMode renderMode,
|
||||
object parameters)
|
||||
{
|
||||
if (viewContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
if (componentType is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentType));
|
||||
}
|
||||
|
||||
if (!typeof(IComponent).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTypeMustDeriveFromType(componentType, typeof(IComponent)));
|
||||
}
|
||||
|
||||
var context = viewContext.HttpContext;
|
||||
var parameterView = parameters is null ?
|
||||
ParameterView.Empty :
|
||||
ParameterView.FromDictionary(HtmlHelper.ObjectToDictionary(parameters));
|
||||
|
||||
return renderMode switch
|
||||
{
|
||||
RenderMode.Server => NonPrerenderedServerComponent(context, GetOrCreateInvocationId(viewContext), componentType, parameterView),
|
||||
RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(context, GetOrCreateInvocationId(viewContext), componentType, parameterView),
|
||||
RenderMode.Static => await StaticComponentAsync(context, componentType, parameterView),
|
||||
_ => throw new ArgumentException(Resources.FormatUnsupportedRenderMode(renderMode), nameof(renderMode)),
|
||||
};
|
||||
}
|
||||
|
||||
private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext)
|
||||
{
|
||||
if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result))
|
||||
{
|
||||
result = new ServerComponentInvocationSequence();
|
||||
viewContext.Items[ComponentSequenceKey] = result;
|
||||
}
|
||||
|
||||
return (ServerComponentInvocationSequence)result;
|
||||
}
|
||||
|
||||
private async Task<IHtmlContent> StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var result = await _staticComponentRenderer.PrerenderComponentAsync(
|
||||
parametersCollection,
|
||||
context,
|
||||
type);
|
||||
|
||||
return new ComponentHtmlContent(result);
|
||||
}
|
||||
|
||||
private async Task<IHtmlContent> PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var currentInvocation = _serverComponentSerializer.SerializeInvocation(
|
||||
invocationId,
|
||||
type,
|
||||
parametersCollection,
|
||||
prerendered: true);
|
||||
|
||||
var result = await _staticComponentRenderer.PrerenderComponentAsync(
|
||||
parametersCollection,
|
||||
context,
|
||||
type);
|
||||
|
||||
return new ComponentHtmlContent(
|
||||
_serverComponentSerializer.GetPreamble(currentInvocation),
|
||||
result,
|
||||
_serverComponentSerializer.GetEpilogue(currentInvocation));
|
||||
}
|
||||
|
||||
private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
|
||||
{
|
||||
var serviceProvider = context.RequestServices;
|
||||
var currentInvocation = _serverComponentSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false);
|
||||
|
||||
return new ComponentHtmlContent(_serverComponentSerializer.GetPreamble(currentInvocation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal interface IComponentRenderer
|
||||
{
|
||||
Task<IHtmlContent> RenderComponentAsync(
|
||||
ViewContext viewContext,
|
||||
Type componentType,
|
||||
RenderMode renderMode,
|
||||
object parameters);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,10 @@ using Microsoft.AspNetCore.Components.Rendering;
|
|||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal class StaticComponentRenderer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for rendering components.
|
||||
/// </summary>
|
||||
public static class HtmlHelperComponentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the <typeparamref name="TComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||
/// <param name="renderMode">The <see cref="RenderMode"/> for the component.</param>
|
||||
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||
public static Task<IHtmlContent> RenderComponentAsync<TComponent>(this IHtmlHelper htmlHelper, RenderMode renderMode) where TComponent : IComponent
|
||||
=> RenderComponentAsync<TComponent>(htmlHelper, renderMode, parameters: null);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the <typeparamref name="TComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||
/// <param name="parameters">An <see cref="object"/> containing the parameters to pass
|
||||
/// to the component.</param>
|
||||
/// <param name="renderMode">The <see cref="RenderMode"/> for the component.</param>
|
||||
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||
public static Task<IHtmlContent> RenderComponentAsync<TComponent>(
|
||||
this IHtmlHelper htmlHelper,
|
||||
RenderMode renderMode,
|
||||
object parameters) where TComponent : IComponent
|
||||
=> RenderComponentAsync(htmlHelper, typeof(TComponent), renderMode, parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the specified <paramref name="componentType"/>.
|
||||
/// </summary>
|
||||
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||
/// <param name="componentType">The component type.</param>
|
||||
/// <param name="parameters">An <see cref="object"/> containing the parameters to pass
|
||||
/// to the component.</param>
|
||||
/// <param name="renderMode">The <see cref="RenderMode"/> for the component.</param>
|
||||
public static Task<IHtmlContent> RenderComponentAsync(
|
||||
this IHtmlHelper htmlHelper,
|
||||
Type componentType,
|
||||
RenderMode renderMode,
|
||||
object parameters)
|
||||
{
|
||||
if (htmlHelper is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(htmlHelper));
|
||||
}
|
||||
|
||||
if (componentType is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentType));
|
||||
}
|
||||
|
||||
var viewContext = htmlHelper.ViewContext;
|
||||
var componentRenderer = viewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
|
||||
return componentRenderer.RenderComponentAsync(viewContext, componentType, renderMode, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -295,4 +295,7 @@
|
|||
<data name="TempData_CannotDeserializeType" xml:space="preserve">
|
||||
<value>Unsupported data type '{0}'.</value>
|
||||
</data>
|
||||
<data name="UnsupportedRenderMode" xml:space="preserve">
|
||||
<value>Unsupported RenderMode '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -13,7 +13,7 @@ using Microsoft.AspNetCore.DataProtection;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
|
@ -22,24 +22,26 @@ using Microsoft.Net.Http.Headers;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
public class HtmlHelperComponentExtensionsTests
|
||||
public class ComponentRendererTest
|
||||
{
|
||||
private const string PrerenderedServerComponentPattern = "^<!--Blazor:(?<preamble>.*?)-->(?<content>.+?)<!--Blazor:(?<epilogue>.*?)-->$";
|
||||
private const string ServerComponentPattern = "^<!--Blazor:(.*?)-->$";
|
||||
|
||||
private static readonly IDataProtectionProvider _dataprotectorProvider = new EphemeralDataProtectionProvider();
|
||||
|
||||
private readonly ComponentRenderer renderer = GetComponentRenderer();
|
||||
|
||||
[Fact]
|
||||
public async Task CanRender_ParameterlessComponent()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
var writer = new StringWriter();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<TestComponent>(RenderMode.Static);
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, null);
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
|
||||
|
|
@ -51,15 +53,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRender_ParameterlessComponent_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<TestComponent>(RenderMode.Server);
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
|
|
@ -82,15 +82,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanPrerender_ParameterlessComponent_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<TestComponent>(RenderMode.ServerPrerendered);
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
// Assert
|
||||
|
|
@ -125,21 +123,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRenderMultipleServerComponents()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var firstWriter = new StringWriter();
|
||||
var secondWriter = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var firstResult = await helper.RenderComponentAsync<TestComponent>(RenderMode.ServerPrerendered);
|
||||
firstResult.WriteTo(firstWriter, HtmlEncoder.Default);
|
||||
var firstComponent = firstWriter.ToString();
|
||||
var firstResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
|
||||
var firstComponent = HtmlContentUtilities.HtmlContentToString(firstResult);
|
||||
var firstMatch = Regex.Match(firstComponent, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
var secondResult = await helper.RenderComponentAsync<TestComponent>(RenderMode.Server);
|
||||
secondResult.WriteTo(secondWriter, HtmlEncoder.Default);
|
||||
var secondComponent = secondWriter.ToString();
|
||||
var secondResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
|
||||
var secondComponent = HtmlContentUtilities.HtmlContentToString(secondResult);
|
||||
var secondMatch = Regex.Match(secondComponent, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
|
|
@ -171,20 +165,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRender_ComponentWithParametersObject()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
Name = "Steve"
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, new { Name = "Steve" });
|
||||
|
||||
// Assert
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
Assert.Equal("<p>Hello Steve!</p>", content);
|
||||
}
|
||||
|
||||
|
|
@ -192,20 +179,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRender_ComponentWithParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.Server,
|
||||
new
|
||||
{
|
||||
Name = "Daniel"
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = "Daniel" });
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
|
|
@ -237,20 +217,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRender_ComponentWithNullParameters_ServerMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.Server,
|
||||
new
|
||||
{
|
||||
Name = (string)null
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = (string)null });
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, ServerComponentPattern);
|
||||
|
||||
// Assert
|
||||
|
|
@ -274,28 +248,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
Assert.Null(parameterDefinition.TypeName);
|
||||
Assert.Null(parameterDefinition.Assembly);
|
||||
|
||||
var value = Assert.Single(serverComponent.ParameterValues);;
|
||||
var value = Assert.Single(serverComponent.ParameterValues); ;
|
||||
Assert.Null(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPrerender_ComponentWithParameters_ServerMode()
|
||||
public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.ServerPrerendered,
|
||||
new
|
||||
{
|
||||
Name = "Daniel"
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Daniel" });
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
// Assert
|
||||
|
|
@ -336,23 +304,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPrerender_ComponentWithNullParameters_ServerMode()
|
||||
public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
var writer = new StringWriter();
|
||||
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
|
||||
.ToTimeLimitedDataProtector();
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<GreetingComponent>(
|
||||
RenderMode.ServerPrerendered,
|
||||
new
|
||||
{
|
||||
Name = (string)null
|
||||
});
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = (string)null });
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline);
|
||||
|
||||
// Assert
|
||||
|
|
@ -396,39 +358,28 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task ComponentWithInvalidRenderMode_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act & Assert
|
||||
var result = await Assert.ThrowsAsync<ArgumentException>(() => helper.RenderComponentAsync<GreetingComponent>(
|
||||
default,
|
||||
new
|
||||
{
|
||||
Name = "Steve"
|
||||
}));
|
||||
Assert.Equal("renderMode", result.ParamName);
|
||||
var ex = await ExceptionAssert.ThrowsArgumentAsync(
|
||||
() => renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), default, new { Name = "Daniel" }),
|
||||
"renderMode",
|
||||
$"Unsupported RenderMode '{(RenderMode)default}'");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenderComponent_DoesNotInvokeOnAfterRenderInComponent()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act
|
||||
var state = new OnAfterRenderState();
|
||||
var result = await helper.RenderComponentAsync<OnAfterRenderComponent>(
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
State = state
|
||||
});
|
||||
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var result = await renderer.RenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, new { state });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("<p>Hello</p>", writer.ToString());
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
Assert.Equal("<p>Hello</p>", content);
|
||||
Assert.False(state.OnAfterRenderRan);
|
||||
}
|
||||
|
||||
|
|
@ -436,10 +387,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanCatch_ComponentWithSynchronousException()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => helper.RenderComponentAsync<ExceptionComponent>(
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
|
||||
viewContext,
|
||||
typeof(ExceptionComponent),
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
|
|
@ -454,10 +407,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanCatch_ComponentWithAsynchronousException()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => helper.RenderComponentAsync<ExceptionComponent>(
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
|
||||
viewContext,
|
||||
typeof(ExceptionComponent),
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
|
|
@ -472,10 +427,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task Rendering_ComponentWithJsInteropThrows()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var viewContext = GetViewContext();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => helper.RenderComponentAsync<ExceptionComponent>(
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
|
||||
viewContext,
|
||||
typeof(ExceptionComponent),
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
|
|
@ -503,11 +460,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
var responseMock = new Mock<IHttpResponseFeature>();
|
||||
responseMock.Setup(r => r.HasStarted).Returns(true);
|
||||
ctx.Features.Set(responseMock.Object);
|
||||
var helper = CreateHelper(ctx);
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext(ctx);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => helper.RenderComponentAsync<RedirectComponent>(
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
|
||||
viewContext,
|
||||
typeof(RedirectComponent),
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
|
|
@ -515,8 +473,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
}));
|
||||
|
||||
Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " +
|
||||
"Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
|
||||
"reponse and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.",
|
||||
"Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
|
||||
"reponse and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -530,10 +488,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
ctx.Request.PathBase = "/base";
|
||||
ctx.Request.Path = "/path";
|
||||
ctx.Request.QueryString = new QueryString("?query=value");
|
||||
var helper = CreateHelper(ctx);
|
||||
var viewContext = GetViewContext(ctx);
|
||||
|
||||
// Act
|
||||
await helper.RenderComponentAsync<RedirectComponent>(
|
||||
await renderer.RenderComponentAsync(
|
||||
viewContext,
|
||||
typeof(RedirectComponent),
|
||||
RenderMode.Static,
|
||||
new
|
||||
{
|
||||
|
|
@ -549,8 +509,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
public async Task CanRender_AsyncComponent()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var writer = new StringWriter();
|
||||
var viewContext = GetViewContext();
|
||||
var expectedContent = @"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -595,29 +554,29 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
</table>";
|
||||
|
||||
// Act
|
||||
var result = await helper.RenderComponentAsync<AsyncComponent>(RenderMode.Static);
|
||||
result.WriteTo(writer, HtmlEncoder.Default);
|
||||
var content = writer.ToString();
|
||||
var result = await renderer.RenderComponentAsync(viewContext,typeof(AsyncComponent), RenderMode.Static, null);
|
||||
var content = HtmlContentUtilities.HtmlContentToString(result);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedContent.Replace("\r\n", "\n"), content);
|
||||
}
|
||||
|
||||
private static IHtmlHelper CreateHelper(HttpContext ctx = null, Action<IServiceCollection> configureServices = null)
|
||||
private static ComponentRenderer GetComponentRenderer() =>
|
||||
new ComponentRenderer(
|
||||
new StaticComponentRenderer(HtmlEncoder.Default),
|
||||
new ServerComponentSerializer(_dataprotectorProvider));
|
||||
|
||||
private static ViewContext GetViewContext(HttpContext context = null, Action<IServiceCollection> configureServices = null)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(HtmlEncoder.Default);
|
||||
services.AddSingleton<ServerComponentSerializer>();
|
||||
services.AddSingleton(_dataprotectorProvider);
|
||||
services.AddSingleton<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
services.AddSingleton<NavigationManager, HttpNavigationManager>();
|
||||
services.AddSingleton<StaticComponentRenderer>();
|
||||
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
|
||||
|
||||
configureServices?.Invoke(services);
|
||||
|
||||
var helper = new Mock<IHtmlHelper>();
|
||||
var context = ctx ?? new DefaultHttpContext();
|
||||
context ??= new DefaultHttpContext();
|
||||
context.RequestServices = services.BuildServiceProvider();
|
||||
context.Request.Scheme = "http";
|
||||
context.Request.Host = new HostString("localhost");
|
||||
|
|
@ -625,12 +584,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
context.Request.Path = "/path";
|
||||
context.Request.QueryString = QueryString.FromUriComponent("?query=value");
|
||||
|
||||
helper.Setup(h => h.ViewContext)
|
||||
.Returns(new ViewContext()
|
||||
{
|
||||
HttpContext = context
|
||||
});
|
||||
return helper.Object;
|
||||
return new ViewContext { HttpContext = context };
|
||||
}
|
||||
|
||||
private class TestComponent : IComponent
|
||||
|
|
@ -6,13 +6,11 @@ using System.Collections.Generic;
|
|||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorComponents
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
{
|
||||
public class HtmlRendererTest
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||
{
|
||||
public class HtmlHelperComponentExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task RenderComponentAsync_Works()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = GetViewContext();
|
||||
var htmlHelper = Mock.Of<IHtmlHelper>(h => h.ViewContext == viewContext);
|
||||
|
||||
// Act
|
||||
var result = await HtmlHelperComponentExtensions.RenderComponentAsync<TestComponent>(htmlHelper, RenderMode.Static);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello world", HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
private static ViewContext GetViewContext()
|
||||
{
|
||||
var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world");
|
||||
var renderer = Mock.Of<IComponentRenderer>(c =>
|
||||
c.RenderComponentAsync(It.IsAny<ViewContext>(), It.IsAny<Type>(), It.IsAny<RenderMode>(), It.IsAny<object>()) == Task.FromResult<IHtmlContent>(htmlContent));
|
||||
|
||||
var httpContext = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = new ServiceCollection().AddSingleton<IComponentRenderer>(renderer).BuildServiceProvider(),
|
||||
};
|
||||
|
||||
var viewContext = new ViewContext { HttpContext = httpContext };
|
||||
return viewContext;
|
||||
}
|
||||
|
||||
private class TestComponent : IComponent
|
||||
{
|
||||
public void Attach(RenderHandle renderHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public Task SetParametersAsync(ParameterView parameters) => null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,13 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
<Router AppAssembly="@typeof(MvcSandbox.Startup).Assembly" FallbackComponent="@typeof(NotFound)" />
|
||||
@using MvcSandbox.Components.Shared
|
||||
|
||||
<Router AppAssembly="@typeof(MvcSandbox.Startup).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
@using MvcSandbox.Components.Shared
|
||||
@layout MainLayout
|
||||
<h1>Not Found</h1>
|
||||
<h2>Sorry, nothing was found.</h2>
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
@page
|
||||
@model MvcSandbox.Pages.ComponentsModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
|
@ -15,8 +14,7 @@
|
|||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<app>@(await Html.RenderComponentAsync<MvcSandbox.Components.App>(RenderMode.Static))</app>
|
||||
|
||||
<script src="_framework/components.server.js"></script>
|
||||
<component type="typeof(MvcSandbox.Components.App)" render-mode="Static" />
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MvcSandbox.Pages
|
||||
{
|
||||
public class ComponentsModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/PagesHome">Pages Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Components">Components</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<!-- We have tests that test runtime view compilation. -->
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
<IsTestAssetProject>true</IsTestAssetProject>
|
||||
|
|
@ -21,4 +20,6 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Diagnostics" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\..\Mvc.Razor.RuntimeCompilation\src\targets\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets" />
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<!--#if (IndividualLocalAuth && !UseLocalDB) -->
|
||||
<ItemGroup>
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--#endif -->
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<!--#endif -->
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--#if (IndividualB2CAuth || IndividualLocalAuth || OrganizationalAuth) -->
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<!--#endif -->
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--#if (IndividualB2CAuth || IndividualLocalAuth || OrganizationalAuth) -->
|
||||
|
|
|
|||
|
|
@ -14,9 +14,20 @@
|
|||
</head>
|
||||
<body>
|
||||
<app>
|
||||
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,25 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
option csharp_namespace = "GrpcService_CSharp";
|
||||
|
||||
package Greet;
|
||||
package greet;
|
||||
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
////#if (IndividualLocalAuth)
|
||||
// },
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
////#endif
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
////#if (IndividualLocalAuth)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
////#if (IndividualLocalAuth)
|
||||
// },
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
////#endif
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
////#if (IndividualLocalAuth)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue