Merge pull request #15355 from aspnet/prkrishn/merge-release

Merge release/3.1
This commit is contained in:
Pranav K 2019-10-24 11:03:23 -07:00 committed by GitHub
commit ec8304ae85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
143 changed files with 3781 additions and 916 deletions

2
.github/CODEOWNERS vendored
View File

@ -19,4 +19,4 @@
/src/Servers/ @tratcher @jkotalik @anurse @halter73 /src/Servers/ @tratcher @jkotalik @anurse @halter73
/src/Middleware/Rewrite @jkotalik @anurse /src/Middleware/Rewrite @jkotalik @anurse
/src/Middleware/HttpsPolicy @jkotalik @anurse /src/Middleware/HttpsPolicy @jkotalik @anurse
/src/SignalR/ @mikaelm12 @BrennanConroy @halter73 @anurse /src/SignalR/ @BrennanConroy @halter73 @anurse

View File

@ -15,6 +15,7 @@
<ProjectReferenceProvider Include="GetDocument.Insider" ProjectPath="$(RepoRoot)src\Tools\GetDocumentInsider\src\GetDocumentInsider.csproj" /> <ProjectReferenceProvider Include="GetDocument.Insider" ProjectPath="$(RepoRoot)src\Tools\GetDocumentInsider\src\GetDocumentInsider.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" />
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" /> <ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
<ProjectReferenceProvider Include="BlazorServerApp" ProjectPath="$(RepoRoot)src\Components\Samples\BlazorServerApp\BlazorServerApp.csproj" /> <ProjectReferenceProvider Include="BlazorServerApp" ProjectPath="$(RepoRoot)src\Components\Samples\BlazorServerApp\BlazorServerApp.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" />

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
@ -89,19 +91,26 @@ namespace Microsoft.AspNetCore.Builder
} }
catch (Exception ex) catch (Exception ex)
{ {
await context.Response.WriteAsync($@" await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1> <h1>Unable to find debuggable browser tab</h1>
<p> <p>
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>. Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
Ensure Chrome is running with debugging enabled. Ensure your browser is running with debugging enabled.
</p> </p>
<h2>Resolution</h2> <h2>Resolution</h2>
<p>
<h4>If you are using Google Chrome for your development, follow these instructions:</h4>
{GetLaunchChromeInstructions(appRootUrl)} {GetLaunchChromeInstructions(appRootUrl)}
<p>... then use that new tab for debugging.</p> </p>
<p>
<h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4>
{GetLaunchEdgeInstructions(appRootUrl)}
</p>
<strong>This should launch a new browser window with debugging enabled..</p>
<h2>Underlying exception:</h2> <h2>Underlying exception:</h2>
<pre>{ex}</pre> <pre>{ex}</pre>
"); ");
return; return;
} }
@ -144,20 +153,42 @@ namespace Microsoft.AspNetCore.Builder
private static string GetLaunchChromeInstructions(string appRootUrl) private static string GetLaunchChromeInstructions(string appRootUrl)
{ {
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
return $@"<p>Close all Chrome instances, then press Win+R and enter the following:</p> return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>""%programfiles(x86)%\Google\Chrome\Application\chrome.exe"" --remote-debugging-port=9222 {appRootUrl}</code></strong></p>"; <p><strong><code>chrome --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
} }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{ {
return $@"<p>Close all Chrome instances, then in a terminal window execute the following:</p> return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>google-chrome --remote-debugging-port=9222 {appRootUrl}</code></strong></p>"; <p><strong><code>google-chrome --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
} }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{ {
return $@"<p>Close all Chrome instances, then in a terminal window execute the following:</p> return $@"<p>Execute the following:</p>
<p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 {appRootUrl}</code></strong></p>"; <p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static string GetLaunchEdgeInstructions(string appRootUrl)
{
var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Press Win+R and enter the following:</p>
<p><strong><code>msedge --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>In a terminal window execute the following:</p>
<p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}</code></strong></p>";
} }
else else
{ {

View File

@ -4,7 +4,7 @@
<span class="text-nowrap"> <span class="text-nowrap">
Please take our Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2100553">brief survey</a> <a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span> </span>
and tell us what you think. and tell us what you think.
</div> </div>

View File

@ -4,6 +4,16 @@ html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
} }
a, .btn-link {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
app { app {
position: relative; position: relative;
display: flex; display: flex;
@ -21,8 +31,19 @@ app {
} }
.main .top-row { .main .top-row {
background-color: #e6e6e6; background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5; border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
}
.main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
} }
.sidebar { .sidebar {
@ -44,20 +65,20 @@ app {
top: -2px; top: -2px;
} }
.nav-item { .sidebar .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .sidebar .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .sidebar .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item a { .sidebar .nav-item a {
color: #d7d7d7; color: #d7d7d7;
border-radius: 4px; border-radius: 4px;
height: 3rem; height: 3rem;
@ -66,12 +87,12 @@ app {
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255,255,255,0.25);
color: white; color: white;
} }
.nav-item a:hover { .sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
color: white; color: white;
} }
@ -116,9 +137,17 @@ app {
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.main .top-row { .main .top-row:not(.auth) {
display: none; display: none;
} }
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,19 @@
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
html, body { html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
} }
a, .btn-link {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
app { app {
position: relative; position: relative;
display: flex; display: flex;
@ -21,8 +31,19 @@ app {
} }
.main .top-row { .main .top-row {
background-color: #e6e6e6; background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5; border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
}
.main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
} }
.sidebar { .sidebar {
@ -44,20 +65,20 @@ app {
top: -2px; top: -2px;
} }
.nav-item { .sidebar .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .sidebar .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .sidebar .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item a { .sidebar .nav-item a {
color: #d7d7d7; color: #d7d7d7;
border-radius: 4px; border-radius: 4px;
height: 3rem; height: 3rem;
@ -66,12 +87,12 @@ app {
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255,255,255,0.25);
color: white; color: white;
} }
.nav-item a:hover { .sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
color: white; color: white;
} }
@ -96,9 +117,36 @@ app {
color: red; color: red;
} }
@media (max-width: 767.98px) { #blazor-error-ui {
.main .top-row { background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none; 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:not(.auth) {
display: none;
}
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
} }
} }

View File

@ -240,6 +240,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", "{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -1486,6 +1492,30 @@ Global
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.ActiveCfg = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.Build.0 = Debug|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.Build.0 = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.ActiveCfg = Release|Any CPU
{B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.Build.0 = Debug|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU
{A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1596,6 +1626,9 @@ Global
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED} {BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} {CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} {F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE} SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

View File

@ -207,7 +207,10 @@ namespace Microsoft.AspNetCore.Components
public sealed partial class EventHandlerAttribute : System.Attribute public sealed partial class EventHandlerAttribute : System.Attribute
{ {
public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { } public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { }
public EventHandlerAttribute(string attributeName, System.Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault) { }
public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
} }
public partial interface IComponent public partial interface IComponent

View File

@ -207,7 +207,10 @@ namespace Microsoft.AspNetCore.Components
public sealed partial class EventHandlerAttribute : System.Attribute public sealed partial class EventHandlerAttribute : System.Attribute
{ {
public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { } public EventHandlerAttribute(string attributeName, System.Type eventArgsType) { }
public EventHandlerAttribute(string attributeName, System.Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault) { }
public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string AttributeName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool EnablePreventDefault { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool EnableStopPropagation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Type EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
} }
public partial interface IComponent public partial interface IComponent

View File

@ -16,7 +16,18 @@ namespace Microsoft.AspNetCore.Components
/// </summary> /// </summary>
/// <param name="attributeName"></param> /// <param name="attributeName"></param>
/// <param name="eventArgsType"></param> /// <param name="eventArgsType"></param>
public EventHandlerAttribute(string attributeName, Type eventArgsType) public EventHandlerAttribute(string attributeName, Type eventArgsType) : this(attributeName, eventArgsType, false, false)
{
}
/// <summary>
/// Constructs an instance of <see cref="EventHandlerAttribute"/>.
/// </summary>
/// <param name="attributeName"></param>
/// <param name="eventArgsType"></param>
/// <param name="enableStopPropagation"></param>
/// <param name="enablePreventDefault"></param>
public EventHandlerAttribute(string attributeName, Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault)
{ {
if (attributeName == null) if (attributeName == null)
{ {
@ -30,6 +41,8 @@ namespace Microsoft.AspNetCore.Components
AttributeName = attributeName; AttributeName = attributeName;
EventArgsType = eventArgsType; EventArgsType = eventArgsType;
EnableStopPropagation = enableStopPropagation;
EnablePreventDefault = enablePreventDefault;
} }
/// <summary> /// <summary>
@ -41,5 +54,15 @@ namespace Microsoft.AspNetCore.Components
/// Gets the event argument type. /// Gets the event argument type.
/// </summary> /// </summary>
public Type EventArgsType { get; } public Type EventArgsType { get; }
/// <summary>
/// Gets the event's ability to stop propagation.
/// </summary>
public bool EnableStopPropagation { get; }
/// <summary>
/// Gets the event's ability to prevent default event flow.
/// </summary>
public bool EnablePreventDefault { get; }
} }
} }

View File

@ -15,6 +15,8 @@
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", "Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj", "Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
"Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj", "Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj",
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj", "Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj", "Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj",

View File

@ -52,6 +52,12 @@ namespace Microsoft.AspNetCore.Components.Forms
messages.Clear(); messages.Clear();
foreach (var validationResult in validationResults) foreach (var validationResult in validationResults)
{ {
if (!validationResult.MemberNames.Any())
{
messages.Add(new FieldIdentifier(editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage);
continue;
}
foreach (var memberName in validationResult.MemberNames) foreach (var memberName in validationResult.MemberNames)
{ {
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage); messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);

View File

@ -13,17 +13,21 @@ using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public class BlazorClient : IAsyncDisposable public class BlazorClient : IAsyncDisposable
{ {
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?"; private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
private HubConnection? _hubConnection;
public BlazorClient() public BlazorClient()
{ {
CancellationTokenSource = new CancellationTokenSource(); CancellationTokenSource = new CancellationTokenSource();
TaskCompletionSource = new TaskCompletionSource<object>(); CancellationToken = CancellationTokenSource.Token;
TaskCompletionSource = new TaskCompletionSource<object?>();
CancellationTokenSource.Token.Register(() => CancellationTokenSource.Token.Register(() =>
{ {
@ -31,7 +35,7 @@ namespace Ignitor
}); });
} }
public TimeSpan? DefaultConnectionTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan? DefaultConnectionTimeout { get; set; } = TimeSpan.FromSeconds(20);
public TimeSpan? DefaultOperationTimeout { get; set; } = TimeSpan.FromMilliseconds(500); public TimeSpan? DefaultOperationTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
/// <summary> /// <summary>
@ -44,104 +48,103 @@ namespace Ignitor
/// Gets the collections of operation results that are captured when <see cref="CaptureOperations"/> /// Gets the collections of operation results that are captured when <see cref="CaptureOperations"/>
/// is true. /// is true.
/// </summary> /// </summary>
public Operations Operations { get; private set; } public Operations Operations { get; } = new Operations();
public Func<string, Exception> FormatError { get; set; } public Func<string, Exception>? FormatError { get; set; }
private CancellationTokenSource CancellationTokenSource { get; } private CancellationTokenSource CancellationTokenSource { get; }
private CancellationToken CancellationToken => CancellationTokenSource.Token; private CancellationToken CancellationToken { get; }
private TaskCompletionSource<object> TaskCompletionSource { get; } private TaskCompletionSource<object?> TaskCompletionSource { get; }
private CancellableOperation<CapturedAttachComponentCall> NextAttachComponentReceived { get; set; } private CancellableOperation<CapturedAttachComponentCall>? NextAttachComponentReceived { get; set; }
private CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; } private CancellableOperation<CapturedRenderBatch?>? NextBatchReceived { get; set; }
private CancellableOperation<string> NextErrorReceived { get; set; } private CancellableOperation<string?>? NextErrorReceived { get; set; }
private CancellableOperation<Exception> NextDisconnect { get; set; } private CancellableOperation<Exception?>? NextDisconnect { get; set; }
private CancellableOperation<CapturedJSInteropCall> NextJSInteropReceived { get; set; } private CancellableOperation<CapturedJSInteropCall?>? NextJSInteropReceived { get; set; }
private CancellableOperation<string> NextDotNetInteropCompletionReceived { get; set; } private CancellableOperation<string?>? NextDotNetInteropCompletionReceived { get; set; }
public ILoggerProvider LoggerProvider { get; set; } public ILoggerProvider LoggerProvider { get; set; } = NullLoggerProvider.Instance;
public bool ConfirmRenderBatch { get; set; } = true; public bool ConfirmRenderBatch { get; set; } = true;
public event Action<CapturedJSInteropCall> JSInterop; public event Action<CapturedJSInteropCall>? JSInterop;
public event Action<CapturedRenderBatch> RenderBatchReceived; public event Action<CapturedRenderBatch>? RenderBatchReceived;
public event Action<string> DotNetInteropCompletion; public event Action<string>? DotNetInteropCompletion;
public event Action<string> OnCircuitError; public event Action<string>? OnCircuitError;
public string CircuitId { get; set; } public string? CircuitId { get; private set; }
public ElementHive Hive { get; set; } = new ElementHive(); public ElementHive Hive { get; } = new ElementHive();
public bool ImplicitWait => DefaultOperationTimeout != null; public bool ImplicitWait => DefaultOperationTimeout != null;
public HubConnection HubConnection { get; private set; } public HubConnection HubConnection => _hubConnection ?? throw new InvalidOperationException("HubConnection has not been initialized.");
public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout) public Task<CapturedRenderBatch?> PrepareForNextBatch(TimeSpan? timeout)
{ {
if (NextBatchReceived != null && !NextBatchReceived.Disposed) if (NextBatchReceived != null && !NextBatchReceived.Disposed)
{ {
throw new InvalidOperationException("Invalid state previous task not completed"); throw new InvalidOperationException("Invalid state previous task not completed");
} }
NextBatchReceived = new CancellableOperation<CapturedRenderBatch>(timeout); NextBatchReceived = new CancellableOperation<CapturedRenderBatch?>(timeout, CancellationToken);
return NextBatchReceived.Completion.Task; return NextBatchReceived.Completion.Task;
} }
public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout) public Task<CapturedJSInteropCall?> PrepareForNextJSInterop(TimeSpan? timeout)
{ {
if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed) if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed)
{ {
throw new InvalidOperationException("Invalid state previous task not completed"); throw new InvalidOperationException("Invalid state previous task not completed");
} }
NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall>(timeout); NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall?>(timeout, CancellationToken);
return NextJSInteropReceived.Completion.Task; return NextJSInteropReceived.Completion.Task;
} }
public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout) public Task<string?> PrepareForNextDotNetInterop(TimeSpan? timeout)
{ {
if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed) if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed)
{ {
throw new InvalidOperationException("Invalid state previous task not completed"); throw new InvalidOperationException("Invalid state previous task not completed");
} }
NextDotNetInteropCompletionReceived = new CancellableOperation<string>(timeout); NextDotNetInteropCompletionReceived = new CancellableOperation<string?>(timeout, CancellationToken);
return NextDotNetInteropCompletionReceived.Completion.Task; return NextDotNetInteropCompletionReceived.Completion.Task;
} }
public Task<string> PrepareForNextCircuitError(TimeSpan? timeout) public Task<string?> PrepareForNextCircuitError(TimeSpan? timeout)
{ {
if (NextErrorReceived != null && !NextErrorReceived.Disposed) if (NextErrorReceived != null && !NextErrorReceived.Disposed)
{ {
throw new InvalidOperationException("Invalid state previous task not completed"); throw new InvalidOperationException("Invalid state previous task not completed");
} }
NextErrorReceived = new CancellableOperation<string>(timeout); NextErrorReceived = new CancellableOperation<string?>(timeout, CancellationToken);
return NextErrorReceived.Completion.Task; return NextErrorReceived.Completion.Task;
} }
public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout) public Task<Exception?> PrepareForNextDisconnect(TimeSpan? timeout)
{ {
if (NextDisconnect != null && !NextDisconnect.Disposed) if (NextDisconnect != null && !NextDisconnect.Disposed)
{ {
throw new InvalidOperationException("Invalid state previous task not completed"); throw new InvalidOperationException("Invalid state previous task not completed");
} }
NextDisconnect = new CancellableOperation<Exception>(timeout); NextDisconnect = new CancellableOperation<Exception?>(timeout, CancellationToken);
return NextDisconnect.Completion.Task; return NextDisconnect.Completion.Task;
} }
@ -172,44 +175,44 @@ namespace Ignitor
return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value)); return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
} }
public async Task<CapturedRenderBatch> ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null) public async Task<CapturedRenderBatch?> ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
{ {
var task = WaitForRenderBatch(timeout); var task = WaitForRenderBatch(timeout);
await action(); await action();
return await task; return await task;
} }
public async Task<CapturedJSInteropCall> ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null) public async Task<CapturedJSInteropCall?> ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
{ {
var task = WaitForJSInterop(timeout); var task = WaitForJSInterop(timeout);
await action(); await action();
return await task; return await task;
} }
public async Task<string> ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null) public async Task<string?> ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
{ {
var task = WaitForDotNetInterop(timeout); var task = WaitForDotNetInterop(timeout);
await action(); await action();
return await task; return await task;
} }
public async Task<string> ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null) public async Task<string?> ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
{ {
var task = WaitForCircuitError(timeout); var task = WaitForCircuitError(timeout);
await action(); await action();
return await task; return await task;
} }
public async Task<Exception> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null) public async Task<Exception?> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
{ {
var task = WaitForDisconnect(timeout); var task = WaitForDisconnect(timeout);
await action(); await action();
return await task; return await task;
} }
public async Task<(string error, Exception exception)> ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null) public async Task<(string? error, Exception? exception)> ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
{ {
string error = null; string? error = default;
// NOTE: timeout is used for each operation individually. // NOTE: timeout is used for each operation individually.
var exception = await ExpectDisconnect(async () => var exception = await ExpectDisconnect(async () =>
@ -220,7 +223,7 @@ namespace Ignitor
return (error, exception); return (error, exception);
} }
private async Task<CapturedRenderBatch> WaitForRenderBatch(TimeSpan? timeout = null) private async Task<CapturedRenderBatch?> WaitForRenderBatch(TimeSpan? timeout = null)
{ {
if (ImplicitWait) if (ImplicitWait)
{ {
@ -233,7 +236,7 @@ namespace Ignitor
{ {
return await PrepareForNextBatch(timeout ?? DefaultOperationTimeout); return await PrepareForNextBatch(timeout ?? DefaultOperationTimeout);
} }
catch (OperationCanceledException) catch (TimeoutException) when (FormatError != null)
{ {
throw FormatError("Timed out while waiting for batch."); throw FormatError("Timed out while waiting for batch.");
} }
@ -242,7 +245,7 @@ namespace Ignitor
return null; return null;
} }
private async Task<CapturedJSInteropCall> WaitForJSInterop(TimeSpan? timeout = null) private async Task<CapturedJSInteropCall?> WaitForJSInterop(TimeSpan? timeout = null)
{ {
if (ImplicitWait) if (ImplicitWait)
{ {
@ -255,7 +258,7 @@ namespace Ignitor
{ {
return await PrepareForNextJSInterop(timeout ?? DefaultOperationTimeout); return await PrepareForNextJSInterop(timeout ?? DefaultOperationTimeout);
} }
catch (OperationCanceledException) catch (TimeoutException) when (FormatError != null)
{ {
throw FormatError("Timed out while waiting for JS Interop."); throw FormatError("Timed out while waiting for JS Interop.");
} }
@ -264,7 +267,7 @@ namespace Ignitor
return null; return null;
} }
private async Task<string> WaitForDotNetInterop(TimeSpan? timeout = null) private async Task<string?> WaitForDotNetInterop(TimeSpan? timeout = null)
{ {
if (ImplicitWait) if (ImplicitWait)
{ {
@ -277,7 +280,7 @@ namespace Ignitor
{ {
return await PrepareForNextDotNetInterop(timeout ?? DefaultOperationTimeout); return await PrepareForNextDotNetInterop(timeout ?? DefaultOperationTimeout);
} }
catch (OperationCanceledException) catch (TimeoutException) when (FormatError != null)
{ {
throw FormatError("Timed out while waiting for .NET interop."); throw FormatError("Timed out while waiting for .NET interop.");
} }
@ -286,7 +289,7 @@ namespace Ignitor
return null; return null;
} }
private async Task<string> WaitForCircuitError(TimeSpan? timeout = null) private async Task<string?> WaitForCircuitError(TimeSpan? timeout = null)
{ {
if (ImplicitWait) if (ImplicitWait)
{ {
@ -299,7 +302,7 @@ namespace Ignitor
{ {
return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout); return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout);
} }
catch (OperationCanceledException) catch (TimeoutException) when (FormatError != null)
{ {
throw FormatError("Timed out while waiting for circuit error."); throw FormatError("Timed out while waiting for circuit error.");
} }
@ -308,7 +311,7 @@ namespace Ignitor
return null; return null;
} }
private async Task<Exception> WaitForDisconnect(TimeSpan? timeout = null) private async Task<Exception?> WaitForDisconnect(TimeSpan? timeout = null)
{ {
if (ImplicitWait) if (ImplicitWait)
{ {
@ -321,7 +324,7 @@ namespace Ignitor
{ {
return await PrepareForNextDisconnect(timeout ?? DefaultOperationTimeout); return await PrepareForNextDisconnect(timeout ?? DefaultOperationTimeout);
} }
catch (OperationCanceledException) catch (TimeoutException) when (FormatError != null)
{ {
throw FormatError("Timed out while waiting for disconnect."); throw FormatError("Timed out while waiting for disconnect.");
} }
@ -344,7 +347,7 @@ namespace Ignitor
} }
}); });
HubConnection = builder.Build(); _hubConnection = builder.Build();
await HubConnection.StartAsync(CancellationToken); await HubConnection.StartAsync(CancellationToken);
HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent); HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent);
@ -354,11 +357,6 @@ namespace Ignitor
HubConnection.On<string>("JS.Error", OnError); HubConnection.On<string>("JS.Error", OnError);
HubConnection.Closed += OnClosedAsync; HubConnection.Closed += OnClosedAsync;
if (CaptureOperations)
{
Operations = new Operations();
}
if (!connectAutomatically) if (!connectAutomatically)
{ {
return true; return true;
@ -366,7 +364,7 @@ namespace Ignitor
var descriptors = await GetPrerenderDescriptors(uri); var descriptors = await GetPrerenderDescriptors(uri);
await ExpectRenderBatch( await ExpectRenderBatch(
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors), async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors, CancellationToken),
DefaultConnectionTimeout); DefaultConnectionTimeout);
return CircuitId != null; return CircuitId != null;
} }
@ -415,9 +413,9 @@ namespace Ignitor
NextBatchReceived?.Completion?.TrySetResult(null); NextBatchReceived?.Completion?.TrySetResult(null);
} }
public Task ConfirmBatch(int batchId, string error = null) public Task ConfirmBatch(int batchId, string? error = null)
{ {
return HubConnection.InvokeAsync("OnRenderCompleted", batchId, error); return HubConnection.InvokeAsync("OnRenderCompleted", batchId, error, CancellationToken);
} }
private void OnError(string error) private void OnError(string error)
@ -468,7 +466,14 @@ namespace Ignitor
public async Task InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson) public async Task InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson)
{ {
await ExpectDotNetInterop(() => HubConnection.InvokeAsync("BeginInvokeDotNetFromJS", callId?.ToString(), assemblyName, methodIdentifier, dotNetObjectId ?? 0, argsJson)); await ExpectDotNetInterop(() => HubConnection.InvokeAsync(
"BeginInvokeDotNetFromJS",
callId?.ToString(),
assemblyName,
methodIdentifier,
dotNetObjectId ?? 0,
argsJson,
CancellationToken));
} }
public async Task<string> GetPrerenderDescriptors(Uri uri) public async Task<string> GetPrerenderDescriptors(Uri uri)
@ -484,10 +489,13 @@ namespace Ignitor
} }
public void Cancel() public void Cancel()
{
if (!CancellationTokenSource.IsCancellationRequested)
{ {
CancellationTokenSource.Cancel(); CancellationTokenSource.Cancel();
CancellationTokenSource.Dispose(); CancellationTokenSource.Dispose();
} }
}
public ElementNode FindElementById(string id) public ElementNode FindElementById(string id)
{ {
@ -519,6 +527,7 @@ namespace Ignitor
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
Cancel();
if (HubConnection != null) if (HubConnection != null)
{ {
await HubConnection.DisposeAsync(); await HubConnection.DisposeAsync();
@ -526,3 +535,5 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -5,11 +5,12 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
internal class CancellableOperation<TResult> internal class CancellableOperation<TResult>
{ {
public CancellableOperation(TimeSpan? timeout) public CancellableOperation(TimeSpan? timeout, CancellationToken cancellationToken)
{ {
Timeout = timeout; Timeout = timeout;
@ -17,26 +18,38 @@ namespace Ignitor
Completion.Task.ContinueWith( Completion.Task.ContinueWith(
(task, state) => (task, state) =>
{ {
var operation = (CancellableOperation<TResult>)state; var operation = (CancellableOperation<TResult>)state!;
operation.Dispose(); operation.Dispose();
}, },
this, this,
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
Cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue) if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
{ {
Cancellation = new CancellationTokenSource(Timeout.Value); Cancellation.CancelAfter(Timeout.Value);
}
CancellationRegistration = Cancellation.Token.Register( CancellationRegistration = Cancellation.Token.Register(
(self) => (self) =>
{ {
var operation = (CancellableOperation<TResult>)self; var operation = (CancellableOperation<TResult>)self!;
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
operation.Cancellation.Dispose(); if (cancellationToken.IsCancellationRequested)
{
// The operation was externally canceled before it timed out.
Dispose();
return;
}
operation.Completion.TrySetException(new TimeoutException($"The operation timed out after {Timeout}."));
operation.Cancellation?.Dispose();
operation.CancellationRegistration.Dispose(); operation.CancellationRegistration.Dispose();
}, },
this); this);
} }
}
public TimeSpan? Timeout { get; } public TimeSpan? Timeout { get; }
@ -62,3 +75,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -3,6 +3,7 @@
namespace Ignitor namespace Ignitor
{ {
#nullable enable
public class ComponentState public class ComponentState
{ {
public ComponentState(int componentId) public ComponentState(int componentId)
@ -11,6 +12,7 @@ namespace Ignitor
} }
public int ComponentId { get; } public int ComponentId { get; }
public IComponent Component { get; } public IComponent? Component { get; }
} }
#nullable restore
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public abstract class ContainerNode : Node public abstract class ContainerNode : Node
@ -82,3 +83,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -3,7 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public class ElementHive public class ElementHive
@ -35,7 +37,7 @@ namespace Ignitor
} }
} }
public bool TryFindElementById(string id, out ElementNode element) public bool TryFindElementById(string id, [NotNullWhen(true)] out ElementNode? element)
{ {
foreach (var kvp in Components) foreach (var kvp in Components)
{ {
@ -49,7 +51,7 @@ namespace Ignitor
element = null; element = null;
return false; return false;
bool TryGetElementFromChildren(Node node, out ElementNode foundNode) bool TryGetElementFromChildren(Node node, out ElementNode? foundNode)
{ {
if (node is ElementNode elementNode && if (node is ElementNode elementNode &&
elementNode.Attributes.TryGetValue("id", out var elementId) && elementNode.Attributes.TryGetValue("id", out var elementId) &&
@ -81,6 +83,7 @@ namespace Ignitor
{ {
component = new ComponentNode(componentId); component = new ComponentNode(componentId);
Components.Add(componentId, component); Components.Add(componentId, component);
} }
ApplyEdits(batch, component, 0, edits); ApplyEdits(batch, component, 0, edits);
@ -199,7 +202,7 @@ namespace Ignitor
case RenderTreeEditType.StepOut: case RenderTreeEditType.StepOut:
{ {
parent = parent.Parent; parent = parent.Parent ?? throw new InvalidOperationException($"Cannot step out of {parent}");
currentDepth--; currentDepth--;
childIndexAtCurrentDepth = currentDepth == 0 ? childIndex : 0; // The childIndex is only ever nonzero at zero depth childIndexAtCurrentDepth = currentDepth == 0 ? childIndex : 0; // The childIndex is only ever nonzero at zero depth
break; break;
@ -469,3 +472,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -7,6 +7,7 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public class ElementNode : ContainerNode public class ElementNode : ContainerNode
@ -87,7 +88,7 @@ namespace Ignitor
return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(args)); return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(args));
} }
public Task ClickAsync(HubConnection connection) internal Task ClickAsync(HubConnection connection)
{ {
if (!Events.TryGetValue("click", out var clickEventDescriptor)) if (!Events.TryGetValue("click", out var clickEventDescriptor))
{ {
@ -129,3 +130,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -1,10 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public class Error public class Error
{ {
public string Stack { get; set; } public string? Stack { get; set; }
} }
} }
#nullable restore

View File

@ -1,6 +1,5 @@
using System; // Copyright (c) .NET Foundation. All rights reserved.
using System.Collections.Generic; // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Ignitor namespace Ignitor
{ {

View File

@ -1,10 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public abstract class Node public abstract class Node
{ {
public virtual ContainerNode Parent { get; set; } public virtual ContainerNode? Parent { get; set; }
} }
} }
#nullable restore

View File

@ -4,6 +4,8 @@
using System; using System;
using System.IO; using System.IO;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
internal static class NodeSerializer internal static class NodeSerializer
@ -96,7 +98,7 @@ namespace Ignitor
if (attribute.Value != null) if (attribute.Value != null)
{ {
Write("=\""); Write("=\"");
Write(attribute.Value.ToString()); Write(attribute.Value.ToString()!);
Write("\""); Write("\"");
} }
} }
@ -113,7 +115,7 @@ namespace Ignitor
if (properties.Value != null) if (properties.Value != null)
{ {
Write("=\""); Write("=\"");
Write(properties.Value.ToString()); Write(properties.Value.ToString()!);
Write("\""); Write("\"");
} }
} }
@ -194,3 +196,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

@ -3,6 +3,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public sealed class Operations public sealed class Operations
@ -18,3 +19,4 @@ namespace Ignitor
public ConcurrentQueue<CapturedJSInteropCall> JSInteropCalls { get; } = new ConcurrentQueue<CapturedJSInteropCall>(); public ConcurrentQueue<CapturedJSInteropCall> JSInteropCalls { get; } = new ConcurrentQueue<CapturedJSInteropCall>();
} }
} }
#nullable restore

View File

@ -4,6 +4,8 @@
using System; using System;
using System.Text; using System.Text;
#nullable enable
namespace Ignitor namespace Ignitor
{ {
public static class RenderBatchReader public static class RenderBatchReader
@ -206,7 +208,7 @@ namespace Ignitor
return new ArrayRange<ulong>(Array.Empty<ulong>(), 0); return new ArrayRange<ulong>(Array.Empty<ulong>(), 0);
} }
private static string ReadString(ReadOnlySpan<byte> data, string[] strings) private static string? ReadString(ReadOnlySpan<byte> data, string[] strings)
{ {
var index = BitConverter.ToInt32(data.Slice(0, 4)); var index = BitConverter.ToInt32(data.Slice(0, 4));
return index >= 0 ? strings[index] : null; return index >= 0 ? strings[index] : null;
@ -279,3 +281,4 @@ namespace Ignitor
} }
} }
} }
#nullable restore

View File

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

View File

@ -36,10 +36,16 @@ app {
justify-content: flex-end; justify-content: flex-end;
} }
.main .top-row > a { .main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem; margin-left: 1.5rem;
} }
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar { .sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
} }
@ -59,20 +65,20 @@ app {
top: -2px; top: -2px;
} }
.nav-item { .sidebar .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .sidebar .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .sidebar .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item a { .sidebar .nav-item a {
color: #d7d7d7; color: #d7d7d7;
border-radius: 4px; border-radius: 4px;
height: 3rem; height: 3rem;
@ -81,12 +87,12 @@ app {
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255,255,255,0.25);
color: white; color: white;
} }
.nav-item a:hover { .sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
color: white; color: white;
} }
@ -131,9 +137,17 @@ app {
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.main .top-row { .main .top-row:not(.auth) {
display: none; display: none;
} }
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {

View File

@ -12,6 +12,7 @@ using MessagePack;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.Components.Server.BlazorPack namespace Microsoft.AspNetCore.Components.Server.BlazorPack
@ -19,6 +20,7 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
/// <summary> /// <summary>
/// Implements the SignalR Hub Protocol using MessagePack with limited type support. /// Implements the SignalR Hub Protocol using MessagePack with limited type support.
/// </summary> /// </summary>
[NonDefaultHubProtocol]
internal sealed class BlazorPackHubProtocol : IHubProtocol internal sealed class BlazorPackHubProtocol : IHubProtocol
{ {
internal const string ProtocolName = "blazorpack"; internal const string ProtocolName = "blazorpack";
@ -78,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
message = PingMessage.Instance; message = PingMessage.Instance;
return true; return true;
case HubProtocolConstants.CloseMessageType: case HubProtocolConstants.CloseMessageType:
message = CreateCloseMessage(ref reader); message = CreateCloseMessage(ref reader, itemCount);
return true; return true;
default: default:
// Future protocol changes can add message types, old clients can ignore them // Future protocol changes can add message types, old clients can ignore them
@ -196,10 +198,23 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
return ApplyHeaders(headers, new CancelInvocationMessage(invocationId)); return ApplyHeaders(headers, new CancelInvocationMessage(invocationId));
} }
private static CloseMessage CreateCloseMessage(ref MessagePackReader reader) private static CloseMessage CreateCloseMessage(ref MessagePackReader reader, int itemCount)
{ {
var error = ReadString(ref reader, "error"); var error = ReadString(ref reader, "error");
return new CloseMessage(error); var allowReconnect = false;
if (itemCount > 2)
{
allowReconnect = ReadBoolean(ref reader, "allowReconnect");
}
// An empty string is still an error
if (error == null && !allowReconnect)
{
return CloseMessage.Empty;
}
return new CloseMessage(error, allowReconnect);
} }
private static Dictionary<string, string> ReadHeaders(ref MessagePackReader reader) private static Dictionary<string, string> ReadHeaders(ref MessagePackReader reader)
@ -515,7 +530,7 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
private void WriteCloseMessage(CloseMessage message, ref MessagePackWriter writer) private void WriteCloseMessage(CloseMessage message, ref MessagePackWriter writer)
{ {
writer.WriteArrayHeader(2); writer.WriteArrayHeader(3);
writer.Write(HubProtocolConstants.CloseMessageType); writer.Write(HubProtocolConstants.CloseMessageType);
if (string.IsNullOrEmpty(message.Error)) if (string.IsNullOrEmpty(message.Error))
{ {
@ -525,6 +540,8 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
{ {
writer.Write(message.Error); writer.Write(message.Error);
} }
writer.Write(message.AllowReconnect);
} }
private void WritePingMessage(PingMessage _, ref MessagePackWriter writer) private void WritePingMessage(PingMessage _, ref MessagePackWriter writer)
@ -559,6 +576,17 @@ namespace Microsoft.AspNetCore.Components.Server.BlazorPack
return destination; return destination;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool ReadBoolean(ref MessagePackReader reader, string field)
{
if (reader.End || reader.NextMessagePackType != MessagePackType.Boolean)
{
ThrowInvalidDataException(field, "Boolean");
}
return reader.ReadBoolean();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ReadInt32(ref MessagePackReader reader, string field) private static int ReadInt32(ref MessagePackReader reader, string field)
{ {

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.SignalR.Internal
{
// Tells SignalR not to add the IHubProtocol with this attribute to all hubs by default
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
internal class NonDefaultHubProtocolAttribute : Attribute
{
}
}

View File

@ -545,7 +545,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
else else
{ {
return $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + return $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
$"detailed exceptions in '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'. {additionalInformation}"; $"detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'. {additionalInformation}";
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@ export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
if (!hasReferencedPdbs) { if (!hasReferencedPdbs) {
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.'); console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
} else if (!currentBrowserIsChrome) { } else if (!currentBrowserIsChrome) {
console.error('Currently, only Chrome is supported for debugging.'); console.error('Currently, only Edge(Chromium) or Chrome is supported for debugging.');
} else { } else {
launchDebugger(); launchDebugger();
} }

View File

@ -125,6 +125,8 @@ namespace Microsoft.AspNetCore.Components.Forms
public ValidationSummary() { } public ValidationSummary() { }
[Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)]
public System.Collections.Generic.IReadOnlyDictionary<string, object> AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IReadOnlyDictionary<string, object> AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected virtual void Dispose(bool disposing) { } protected virtual void Dispose(bool disposing) { }
protected override void OnParametersSet() { } protected override void OnParametersSet() { }
@ -222,97 +224,97 @@ namespace Microsoft.AspNetCore.Components.Web
public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecopy", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecopy", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecut", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecut", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforedeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforedeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforepaste", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforepaste", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onblur", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onblur", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplay", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplay", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplaythrough", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplaythrough", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onchange", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onchange", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncontextmenu", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncontextmenu", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncopy", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncopy", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncuechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncuechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncut", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncut", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondblclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondblclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrag", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrag", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragend", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragend", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragenter", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragenter", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragleave", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragleave", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragover", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragover", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragstart", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragstart", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrop", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrop", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondurationchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondurationchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onemptied", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onemptied", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onended", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onended", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onerror", typeof(Microsoft.AspNetCore.Components.Web.ErrorEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onerror", typeof(Microsoft.AspNetCore.Components.Web.ErrorEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocus", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocus", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusin", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusin", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusout", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusout", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenerror", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenerror", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ongotpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ongotpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oninput", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oninput", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oninvalid", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oninvalid", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeydown", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeydown", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeypress", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeypress", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeyup", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeyup", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onload", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onload", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadeddata", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadeddata", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadedmetadata", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadedmetadata", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadend", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadend", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadstart", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadstart", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onlostpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onlostpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousedown", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousedown", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousemove", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousemove", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseout", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseout", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseover", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseover", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseup", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseup", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousewheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousewheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpaste", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpaste", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpause", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpause", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onplay", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onplay", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onplaying", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onplaying", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointercancel", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointercancel", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerdown", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerdown", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerenter", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerenter", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerleave", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerleave", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockerror", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockerror", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointermove", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointermove", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerout", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerout", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerover", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerover", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerup", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerup", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onprogress", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onprogress", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onratechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onratechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onreadystatechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onreadystatechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onreset", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onreset", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onscroll", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onscroll", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeked", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeked", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeking", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeking", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselect", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselect", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectionchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectionchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectstart", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectstart", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onstalled", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onstalled", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onstop", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onstop", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onsubmit", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onsubmit", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onsuspend", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onsuspend", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeout", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeout", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeupdate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeupdate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchcancel", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchcancel", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchend", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchend", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchenter", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchenter", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchleave", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchleave", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchmove", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchmove", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchstart", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchstart", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onvolumechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onvolumechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onwaiting", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onwaiting", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onwheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onwheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs), true, true)]
public static partial class EventHandlers public static partial class EventHandlers
{ {
} }

View File

@ -125,6 +125,8 @@ namespace Microsoft.AspNetCore.Components.Forms
public ValidationSummary() { } public ValidationSummary() { }
[Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)]
public System.Collections.Generic.IReadOnlyDictionary<string, object> AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IReadOnlyDictionary<string, object> AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
protected virtual void Dispose(bool disposing) { } protected virtual void Dispose(bool disposing) { }
protected override void OnParametersSet() { } protected override void OnParametersSet() { }
@ -222,97 +224,97 @@ namespace Microsoft.AspNetCore.Components.Web
public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Message { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onabort", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecopy", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecopy", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecut", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforecut", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforedeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforedeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforepaste", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onbeforepaste", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onblur", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onblur", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplay", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplay", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplaythrough", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncanplaythrough", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onchange", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onchange", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncontextmenu", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncontextmenu", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncopy", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncopy", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncuechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncuechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oncut", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oncut", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondblclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondblclick", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondeactivate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondeactivate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrag", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrag", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragend", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragend", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragenter", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragenter", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragleave", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragleave", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragover", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragover", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragstart", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondragstart", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrop", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondrop", typeof(Microsoft.AspNetCore.Components.Web.DragEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ondurationchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ondurationchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onemptied", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onemptied", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onended", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onended", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onerror", typeof(Microsoft.AspNetCore.Components.Web.ErrorEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onerror", typeof(Microsoft.AspNetCore.Components.Web.ErrorEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocus", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocus", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusin", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusin", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusout", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfocusout", typeof(Microsoft.AspNetCore.Components.Web.FocusEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenerror", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onfullscreenerror", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ongotpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ongotpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oninput", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oninput", typeof(Microsoft.AspNetCore.Components.ChangeEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("oninvalid", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("oninvalid", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeydown", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeydown", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeypress", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeypress", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeyup", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onkeyup", typeof(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onload", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onload", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadeddata", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadeddata", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadedmetadata", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadedmetadata", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadend", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadend", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadstart", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onloadstart", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onlostpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onlostpointercapture", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousedown", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousedown", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousemove", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousemove", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseout", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseout", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseover", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseover", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseup", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmouseup", typeof(Microsoft.AspNetCore.Components.Web.MouseEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousewheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onmousewheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpaste", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpaste", typeof(Microsoft.AspNetCore.Components.Web.ClipboardEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpause", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpause", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onplay", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onplay", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onplaying", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onplaying", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointercancel", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointercancel", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerdown", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerdown", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerenter", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerenter", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerleave", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerleave", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockerror", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerlockerror", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointermove", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointermove", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerout", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerout", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerover", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerover", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerup", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onpointerup", typeof(Microsoft.AspNetCore.Components.Web.PointerEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onprogress", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onprogress", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onratechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onratechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onreadystatechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onreadystatechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onreset", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onreset", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onscroll", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onscroll", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeked", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeked", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeking", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onseeking", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselect", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselect", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectionchange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectionchange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectstart", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onselectstart", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onstalled", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onstalled", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onstop", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onstop", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onsubmit", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onsubmit", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onsuspend", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onsuspend", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeout", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeout", typeof(Microsoft.AspNetCore.Components.Web.ProgressEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeupdate", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontimeupdate", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchcancel", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchcancel", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchend", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchend", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchenter", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchenter", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchleave", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchleave", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchmove", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchmove", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchstart", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("ontouchstart", typeof(Microsoft.AspNetCore.Components.Web.TouchEventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onvolumechange", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onvolumechange", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onwaiting", typeof(System.EventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onwaiting", typeof(System.EventArgs), true, true)]
[Microsoft.AspNetCore.Components.EventHandlerAttribute("onwheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs))] [Microsoft.AspNetCore.Components.EventHandlerAttribute("onwheel", typeof(Microsoft.AspNetCore.Components.Web.WheelEventArgs), true, true)]
public static partial class EventHandlers public static partial class EventHandlers
{ {
} }

View File

@ -19,6 +19,12 @@ namespace Microsoft.AspNetCore.Components.Forms
private EditContext _previousEditContext; private EditContext _previousEditContext;
private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler; private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
/// <summary>
/// Gets or sets the model to produce the list of validation messages for.
/// When specified, this lists all errors that are associated with the model instance.
/// </summary>
[Parameter] public object Model { get; set; }
/// <summary> /// <summary>
/// Gets or sets a collection of additional attributes that will be applied to the created <c>ul</c> element. /// Gets or sets a collection of additional attributes that will be applied to the created <c>ul</c> element.
/// </summary> /// </summary>
@ -57,22 +63,31 @@ namespace Microsoft.AspNetCore.Components.Forms
{ {
// As an optimization, only evaluate the messages enumerable once, and // As an optimization, only evaluate the messages enumerable once, and
// only produce the enclosing <ul> if there's at least one message // only produce the enclosing <ul> if there's at least one message
var messagesEnumerator = CurrentEditContext.GetValidationMessages().GetEnumerator(); var validationMessages = Model is null ?
if (messagesEnumerator.MoveNext()) CurrentEditContext.GetValidationMessages() :
CurrentEditContext.GetValidationMessages(new FieldIdentifier(Model, string.Empty));
var first = true;
foreach (var error in validationMessages)
{ {
if (first)
{
first = false;
builder.OpenElement(0, "ul"); builder.OpenElement(0, "ul");
builder.AddMultipleAttributes(1, AdditionalAttributes); builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", "validation-errors"); builder.AddAttribute(2, "class", "validation-errors");
}
do
{
builder.OpenElement(3, "li"); builder.OpenElement(3, "li");
builder.AddAttribute(4, "class", "validation-message"); builder.AddAttribute(4, "class", "validation-message");
builder.AddContent(5, messagesEnumerator.Current); builder.AddContent(5, error);
builder.CloseElement(); builder.CloseElement();
} }
while (messagesEnumerator.MoveNext());
if (!first)
{
// We have at least one validation message.
builder.CloseElement(); builder.CloseElement();
} }
} }

View File

@ -11,117 +11,117 @@ namespace Microsoft.AspNetCore.Components.Web
/// </summary> /// </summary>
// Focus events // Focus events
[EventHandler("onfocus", typeof(FocusEventArgs))] [EventHandler("onfocus", typeof(FocusEventArgs), true, true)]
[EventHandler("onblur", typeof(FocusEventArgs))] [EventHandler("onblur", typeof(FocusEventArgs), true, true)]
[EventHandler("onfocusin", typeof(FocusEventArgs))] [EventHandler("onfocusin", typeof(FocusEventArgs), true, true)]
[EventHandler("onfocusout", typeof(FocusEventArgs))] [EventHandler("onfocusout", typeof(FocusEventArgs), true, true)]
// Mouse events // Mouse events
[EventHandler("onmouseover", typeof(MouseEventArgs))] [EventHandler("onmouseover", typeof(MouseEventArgs), true, true)]
[EventHandler("onmouseout", typeof(MouseEventArgs))] [EventHandler("onmouseout", typeof(MouseEventArgs), true, true)]
[EventHandler("onmousemove", typeof(MouseEventArgs))] [EventHandler("onmousemove", typeof(MouseEventArgs), true, true)]
[EventHandler("onmousedown", typeof(MouseEventArgs))] [EventHandler("onmousedown", typeof(MouseEventArgs), true, true)]
[EventHandler("onmouseup", typeof(MouseEventArgs))] [EventHandler("onmouseup", typeof(MouseEventArgs), true, true)]
[EventHandler("onclick", typeof(MouseEventArgs))] [EventHandler("onclick", typeof(MouseEventArgs), true, true)]
[EventHandler("ondblclick", typeof(MouseEventArgs))] [EventHandler("ondblclick", typeof(MouseEventArgs), true, true)]
[EventHandler("onwheel", typeof(WheelEventArgs))] [EventHandler("onwheel", typeof(WheelEventArgs), true, true)]
[EventHandler("onmousewheel", typeof(WheelEventArgs))] [EventHandler("onmousewheel", typeof(WheelEventArgs), true, true)]
[EventHandler("oncontextmenu", typeof(MouseEventArgs))] [EventHandler("oncontextmenu", typeof(MouseEventArgs), true, true)]
// Drag events // Drag events
[EventHandler("ondrag", typeof(DragEventArgs))] [EventHandler("ondrag", typeof(DragEventArgs), true, true)]
[EventHandler("ondragend", typeof(DragEventArgs))] [EventHandler("ondragend", typeof(DragEventArgs), true, true)]
[EventHandler("ondragenter", typeof(DragEventArgs))] [EventHandler("ondragenter", typeof(DragEventArgs), true, true)]
[EventHandler("ondragleave", typeof(DragEventArgs))] [EventHandler("ondragleave", typeof(DragEventArgs), true, true)]
[EventHandler("ondragover", typeof(DragEventArgs))] [EventHandler("ondragover", typeof(DragEventArgs), true, true)]
[EventHandler("ondragstart", typeof(DragEventArgs))] [EventHandler("ondragstart", typeof(DragEventArgs), true, true)]
[EventHandler("ondrop", typeof(DragEventArgs))] [EventHandler("ondrop", typeof(DragEventArgs), true, true)]
// Keyboard events // Keyboard events
[EventHandler("onkeydown", typeof(KeyboardEventArgs))] [EventHandler("onkeydown", typeof(KeyboardEventArgs), true, true)]
[EventHandler("onkeyup", typeof(KeyboardEventArgs))] [EventHandler("onkeyup", typeof(KeyboardEventArgs), true, true)]
[EventHandler("onkeypress", typeof(KeyboardEventArgs))] [EventHandler("onkeypress", typeof(KeyboardEventArgs), true, true)]
// Input events // Input events
[EventHandler("onchange", typeof(ChangeEventArgs))] [EventHandler("onchange", typeof(ChangeEventArgs), true, true)]
[EventHandler("oninput", typeof(ChangeEventArgs))] [EventHandler("oninput", typeof(ChangeEventArgs), true, true)]
[EventHandler("oninvalid", typeof(EventArgs))] [EventHandler("oninvalid", typeof(EventArgs), true, true)]
[EventHandler("onreset", typeof(EventArgs))] [EventHandler("onreset", typeof(EventArgs), true, true)]
[EventHandler("onselect", typeof(EventArgs))] [EventHandler("onselect", typeof(EventArgs), true, true)]
[EventHandler("onselectstart", typeof(EventArgs))] [EventHandler("onselectstart", typeof(EventArgs), true, true)]
[EventHandler("onselectionchange", typeof(EventArgs))] [EventHandler("onselectionchange", typeof(EventArgs), true, true)]
[EventHandler("onsubmit", typeof(EventArgs))] [EventHandler("onsubmit", typeof(EventArgs), true, true)]
// Clipboard events // Clipboard events
[EventHandler("onbeforecopy", typeof(EventArgs))] [EventHandler("onbeforecopy", typeof(EventArgs), true, true)]
[EventHandler("onbeforecut", typeof(EventArgs))] [EventHandler("onbeforecut", typeof(EventArgs), true, true)]
[EventHandler("onbeforepaste", typeof(EventArgs))] [EventHandler("onbeforepaste", typeof(EventArgs), true, true)]
[EventHandler("oncopy", typeof(ClipboardEventArgs))] [EventHandler("oncopy", typeof(ClipboardEventArgs), true, true)]
[EventHandler("oncut", typeof(ClipboardEventArgs))] [EventHandler("oncut", typeof(ClipboardEventArgs), true, true)]
[EventHandler("onpaste", typeof(ClipboardEventArgs))] [EventHandler("onpaste", typeof(ClipboardEventArgs), true, true)]
// Touch events // Touch events
[EventHandler("ontouchcancel", typeof(TouchEventArgs))] [EventHandler("ontouchcancel", typeof(TouchEventArgs), true, true)]
[EventHandler("ontouchend", typeof(TouchEventArgs))] [EventHandler("ontouchend", typeof(TouchEventArgs), true, true)]
[EventHandler("ontouchmove", typeof(TouchEventArgs))] [EventHandler("ontouchmove", typeof(TouchEventArgs), true, true)]
[EventHandler("ontouchstart", typeof(TouchEventArgs))] [EventHandler("ontouchstart", typeof(TouchEventArgs), true, true)]
[EventHandler("ontouchenter", typeof(TouchEventArgs))] [EventHandler("ontouchenter", typeof(TouchEventArgs), true, true)]
[EventHandler("ontouchleave", typeof(TouchEventArgs))] [EventHandler("ontouchleave", typeof(TouchEventArgs), true, true)]
// Pointer events // Pointer events
[EventHandler("ongotpointercapture", typeof(PointerEventArgs))] [EventHandler("ongotpointercapture", typeof(PointerEventArgs), true, true)]
[EventHandler("onlostpointercapture", typeof(PointerEventArgs))] [EventHandler("onlostpointercapture", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointercancel", typeof(PointerEventArgs))] [EventHandler("onpointercancel", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerdown", typeof(PointerEventArgs))] [EventHandler("onpointerdown", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerenter", typeof(PointerEventArgs))] [EventHandler("onpointerenter", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerleave", typeof(PointerEventArgs))] [EventHandler("onpointerleave", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointermove", typeof(PointerEventArgs))] [EventHandler("onpointermove", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerout", typeof(PointerEventArgs))] [EventHandler("onpointerout", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerover", typeof(PointerEventArgs))] [EventHandler("onpointerover", typeof(PointerEventArgs), true, true)]
[EventHandler("onpointerup", typeof(PointerEventArgs))] [EventHandler("onpointerup", typeof(PointerEventArgs), true, true)]
// Media events // Media events
[EventHandler("oncanplay", typeof(EventArgs))] [EventHandler("oncanplay", typeof(EventArgs), true, true)]
[EventHandler("oncanplaythrough", typeof(EventArgs))] [EventHandler("oncanplaythrough", typeof(EventArgs), true, true)]
[EventHandler("oncuechange", typeof(EventArgs))] [EventHandler("oncuechange", typeof(EventArgs), true, true)]
[EventHandler("ondurationchange", typeof(EventArgs))] [EventHandler("ondurationchange", typeof(EventArgs), true, true)]
[EventHandler("onemptied", typeof(EventArgs))] [EventHandler("onemptied", typeof(EventArgs), true, true)]
[EventHandler("onpause", typeof(EventArgs))] [EventHandler("onpause", typeof(EventArgs), true, true)]
[EventHandler("onplay", typeof(EventArgs))] [EventHandler("onplay", typeof(EventArgs), true, true)]
[EventHandler("onplaying", typeof(EventArgs))] [EventHandler("onplaying", typeof(EventArgs), true, true)]
[EventHandler("onratechange", typeof(EventArgs))] [EventHandler("onratechange", typeof(EventArgs), true, true)]
[EventHandler("onseeked", typeof(EventArgs))] [EventHandler("onseeked", typeof(EventArgs), true, true)]
[EventHandler("onseeking", typeof(EventArgs))] [EventHandler("onseeking", typeof(EventArgs), true, true)]
[EventHandler("onstalled", typeof(EventArgs))] [EventHandler("onstalled", typeof(EventArgs), true, true)]
[EventHandler("onstop", typeof(EventArgs))] [EventHandler("onstop", typeof(EventArgs), true, true)]
[EventHandler("onsuspend", typeof(EventArgs))] [EventHandler("onsuspend", typeof(EventArgs), true, true)]
[EventHandler("ontimeupdate", typeof(EventArgs))] [EventHandler("ontimeupdate", typeof(EventArgs), true, true)]
[EventHandler("onvolumechange", typeof(EventArgs))] [EventHandler("onvolumechange", typeof(EventArgs), true, true)]
[EventHandler("onwaiting", typeof(EventArgs))] [EventHandler("onwaiting", typeof(EventArgs), true, true)]
// Progress events // Progress events
[EventHandler("onloadstart", typeof(ProgressEventArgs))] [EventHandler("onloadstart", typeof(ProgressEventArgs), true, true)]
[EventHandler("ontimeout", typeof(ProgressEventArgs))] [EventHandler("ontimeout", typeof(ProgressEventArgs), true, true)]
[EventHandler("onabort", typeof(ProgressEventArgs))] [EventHandler("onabort", typeof(ProgressEventArgs), true, true)]
[EventHandler("onload", typeof(ProgressEventArgs))] [EventHandler("onload", typeof(ProgressEventArgs), true, true)]
[EventHandler("onloadend", typeof(ProgressEventArgs))] [EventHandler("onloadend", typeof(ProgressEventArgs), true, true)]
[EventHandler("onprogress", typeof(ProgressEventArgs))] [EventHandler("onprogress", typeof(ProgressEventArgs), true, true)]
[EventHandler("onerror", typeof(ErrorEventArgs))] [EventHandler("onerror", typeof(ErrorEventArgs), true, true)]
// General events // General events
[EventHandler("onactivate", typeof(EventArgs))] [EventHandler("onactivate", typeof(EventArgs), true, true)]
[EventHandler("onbeforeactivate", typeof(EventArgs))] [EventHandler("onbeforeactivate", typeof(EventArgs), true, true)]
[EventHandler("onbeforedeactivate", typeof(EventArgs))] [EventHandler("onbeforedeactivate", typeof(EventArgs), true, true)]
[EventHandler("ondeactivate", typeof(EventArgs))] [EventHandler("ondeactivate", typeof(EventArgs), true, true)]
[EventHandler("onended", typeof(EventArgs))] [EventHandler("onended", typeof(EventArgs), true, true)]
[EventHandler("onfullscreenchange", typeof(EventArgs))] [EventHandler("onfullscreenchange", typeof(EventArgs), true, true)]
[EventHandler("onfullscreenerror", typeof(EventArgs))] [EventHandler("onfullscreenerror", typeof(EventArgs), true, true)]
[EventHandler("onloadeddata", typeof(EventArgs))] [EventHandler("onloadeddata", typeof(EventArgs), true, true)]
[EventHandler("onloadedmetadata", typeof(EventArgs))] [EventHandler("onloadedmetadata", typeof(EventArgs), true, true)]
[EventHandler("onpointerlockchange", typeof(EventArgs))] [EventHandler("onpointerlockchange", typeof(EventArgs), true, true)]
[EventHandler("onpointerlockerror", typeof(EventArgs))] [EventHandler("onpointerlockerror", typeof(EventArgs), true, true)]
[EventHandler("onreadystatechange", typeof(EventArgs))] [EventHandler("onreadystatechange", typeof(EventArgs), true, true)]
[EventHandler("onscroll", typeof(EventArgs))] [EventHandler("onscroll", typeof(EventArgs), true, true)]
public static class EventHandlers public static class EventHandlers
{ {
} }

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
// Arrange // Arrange
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Bad input data."; $"detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'. Bad input data.";
var eventDescriptor = Serialize(new WebEventDescriptor() var eventDescriptor = Serialize(new WebEventDescriptor()
{ {
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
// Arrange // Arrange
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to dispatch event."; $"detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'. Failed to dispatch event.";
var eventDescriptor = Serialize(new WebEventDescriptor() var eventDescriptor = Serialize(new WebEventDescriptor()
{ {
@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
// Arrange // Arrange
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'."; $"detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'.";
Client.ConfirmRenderBatch = false; Client.ConfirmRenderBatch = false;

View File

@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
// Arrange // Arrange
var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " + var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " +
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " + "For more details turn on detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'. " +
"Location change to 'http://example.com' failed."; "Location change to 'http://example.com' failed.";
var rootUri = ServerFixture.RootUri; var rootUri = ServerFixture.RootUri;
@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
// Arrange // Arrange
var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " + var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " +
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " + "For more details turn on detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'. " +
"Location change failed."; "Location change failed.";
var rootUri = ServerFixture.RootUri; var rootUri = ServerFixture.RootUri;

View File

@ -75,11 +75,6 @@ namespace Microsoft.AspNetCore.Components
async Task IAsyncLifetime.DisposeAsync() async Task IAsyncLifetime.DisposeAsync()
{ {
if (TestSink != null)
{
TestSink.MessageLogged -= TestSink_MessageLogged;
}
await DisposeAsync(); await DisposeAsync();
} }
@ -90,6 +85,11 @@ namespace Microsoft.AspNetCore.Components
protected virtual Task DisposeAsync() protected virtual Task DisposeAsync()
{ {
if (TestSink != null)
{
TestSink.MessageLogged -= TestSink_MessageLogged;
}
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -97,8 +97,19 @@ namespace Microsoft.AspNetCore.Components
{ {
var log = new LogMessage(context.LogLevel, context.EventId, context.Message, context.Exception); var log = new LogMessage(context.LogLevel, context.EventId, context.Message, context.Exception);
Logs.Enqueue(log); Logs.Enqueue(log);
try
{
// This might produce an InvalidOperationException when the logger tries to log a message after
// the test has completed but before the handler has been removed.
// ---> System.InvalidOperationException: There is no currently active test.
// For that reason, we capture the exception here and silence it, as the message is captured inside the Logs
// variable anyway.
Output.WriteLine(log.ToString()); Output.WriteLine(log.ToString());
} }
catch (Exception)
{
}
}
[DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")] [DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")]
protected sealed class LogMessage protected sealed class LogMessage

View File

@ -1,18 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Threading.Tasks;
using BasicTestApp; using BasicTestApp;
using BasicTestApp.FormsTest; using BasicTestApp.FormsTest;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting; using Microsoft.AspNetCore.E2ETesting;
using Microsoft.AspNetCore.Testing;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI; using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -34,14 +33,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
} }
protected virtual IWebElement MountSimpleValidationComponent()
=> Browser.MountTestComponent<SimpleValidationComponent>();
protected virtual IWebElement MountTypicalValidationComponent()
=> Browser.MountTestComponent<TypicalValidationComponent>();
[Fact] [Fact]
public async Task EditFormWorksWithDataAnnotationsValidator() public async Task EditFormWorksWithDataAnnotationsValidator()
{ {
var appElement = Browser.MountTestComponent<SimpleValidationComponent>(); var appElement = MountSimpleValidationComponent();;
var form = appElement.FindElement(By.TagName("form")); var form = appElement.FindElement(By.TagName("form"));
var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input")); var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input"));
var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input")); var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input"));
var submitButton = appElement.FindElement(By.TagName("button")); var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
// The form emits unmatched attributes // The form emits unmatched attributes
@ -77,7 +82,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputTextInteractsWithEditContext() public void InputTextInteractsWithEditContext()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input")); var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -104,7 +109,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputNumberInteractsWithEditContext_NonNullableInt() public void InputNumberInteractsWithEditContext_NonNullableInt()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var ageInput = appElement.FindElement(By.ClassName("age")).FindElement(By.TagName("input")); var ageInput = appElement.FindElement(By.ClassName("age")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -136,7 +141,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputNumberInteractsWithEditContext_NullableFloat() public void InputNumberInteractsWithEditContext_NullableFloat()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var heightInput = appElement.FindElement(By.ClassName("height")).FindElement(By.TagName("input")); var heightInput = appElement.FindElement(By.ClassName("height")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -160,7 +165,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputTextAreaInteractsWithEditContext() public void InputTextAreaInteractsWithEditContext()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var descriptionInput = appElement.FindElement(By.ClassName("description")).FindElement(By.TagName("textarea")); var descriptionInput = appElement.FindElement(By.ClassName("description")).FindElement(By.TagName("textarea"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -187,7 +192,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputDateInteractsWithEditContext_NonNullableDateTime() public void InputDateInteractsWithEditContext_NonNullableDateTime()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var renewalDateInput = appElement.FindElement(By.ClassName("renewal-date")).FindElement(By.TagName("input")); var renewalDateInput = appElement.FindElement(By.ClassName("renewal-date")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -218,7 +223,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputDateInteractsWithEditContext_NullableDateTimeOffset() public void InputDateInteractsWithEditContext_NullableDateTimeOffset()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var expiryDateInput = appElement.FindElement(By.ClassName("expiry-date")).FindElement(By.TagName("input")); var expiryDateInput = appElement.FindElement(By.ClassName("expiry-date")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -241,7 +246,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputSelectInteractsWithEditContext() public void InputSelectInteractsWithEditContext()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var ticketClassInput = new SelectElement(appElement.FindElement(By.ClassName("ticket-class")).FindElement(By.TagName("select"))); var ticketClassInput = new SelectElement(appElement.FindElement(By.ClassName("ticket-class")).FindElement(By.TagName("select")));
var select = ticketClassInput.WrappedElement; var select = ticketClassInput.WrappedElement;
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -263,7 +268,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void InputCheckboxInteractsWithEditContext() public void InputCheckboxInteractsWithEditContext()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input")); var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input"));
var isEvilInput = appElement.FindElement(By.ClassName("is-evil")).FindElement(By.TagName("input")); var isEvilInput = appElement.FindElement(By.ClassName("is-evil")).FindElement(By.TagName("input"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
@ -297,7 +302,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
var appElement = Browser.MountTestComponent<NotifyPropertyChangedValidationComponent>(); var appElement = Browser.MountTestComponent<NotifyPropertyChangedValidationComponent>();
var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input")); var userNameInput = appElement.FindElement(By.ClassName("user-name")).FindElement(By.TagName("input"));
var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input")); var acceptsTermsInput = appElement.FindElement(By.ClassName("accepts-terms")).FindElement(By.TagName("input"));
var submitButton = appElement.FindElement(By.TagName("button")); var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);
var submissionStatus = appElement.FindElement(By.Id("submission-status")); var submissionStatus = appElement.FindElement(By.Id("submission-status"));
@ -331,11 +336,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
[Fact] [Fact]
public void ValidationMessageDisplaysMessagesForField() public void ValidationMessageDisplaysMessagesForField()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var emailContainer = appElement.FindElement(By.ClassName("email")); var emailContainer = appElement.FindElement(By.ClassName("email"));
var emailInput = emailContainer.FindElement(By.TagName("input")); var emailInput = emailContainer.FindElement(By.TagName("input"));
var emailMessagesAccessor = CreateValidationMessagesAccessor(emailContainer); var emailMessagesAccessor = CreateValidationMessagesAccessor(emailContainer);
var submitButton = appElement.FindElement(By.TagName("button")); var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
// Doesn't show messages for other fields // Doesn't show messages for other fields
submitButton.Click(); submitButton.Click();
@ -355,10 +360,43 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Browser.Empty(emailMessagesAccessor); Browser.Empty(emailMessagesAccessor);
} }
[Fact]
public void ErrorsFromCompareAttribute()
{
var appElement = MountTypicalValidationComponent();
var emailContainer = appElement.FindElement(By.ClassName("email"));
var emailInput = emailContainer.FindElement(By.TagName("input"));
var confirmEmailContainer = appElement.FindElement(By.ClassName("confirm-email"));
var confirmInput = confirmEmailContainer.FindElement(By.TagName("input"));
var confirmEmailValidationMessage = CreateValidationMessagesAccessor(confirmEmailContainer);
var modelErrors = CreateValidationMessagesAccessor(appElement.FindElement(By.ClassName("model-errors")));
CreateValidationMessagesAccessor(emailContainer);
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
// Updates on edit
emailInput.SendKeys("a@b.com\t");
submitButton.Click();
Browser.Empty(confirmEmailValidationMessage);
Browser.Equal(new[] { "Email and confirm email do not match." }, modelErrors);
confirmInput.SendKeys("not-test@example.com\t");
Browser.Equal(new[] { "Email and confirm email do not match." }, confirmEmailValidationMessage);
// Can become correct
confirmInput.Clear();
confirmInput.SendKeys("a@b.com\t");
Browser.Empty(confirmEmailValidationMessage);
submitButton.Click();
Browser.Empty(modelErrors);
}
[Fact] [Fact]
public void InputComponentsCauseContainerToRerenderOnChange() public void InputComponentsCauseContainerToRerenderOnChange()
{ {
var appElement = Browser.MountTestComponent<TypicalValidationComponent>(); var appElement = MountTypicalValidationComponent();
var ticketClassInput = new SelectElement(appElement.FindElement(By.ClassName("ticket-class")).FindElement(By.TagName("select"))); var ticketClassInput = new SelectElement(appElement.FindElement(By.ClassName("ticket-class")).FindElement(By.TagName("select")));
var selectedTicketClassDisplay = appElement.FindElement(By.Id("selected-ticket-class")); var selectedTicketClassDisplay = appElement.FindElement(By.Id("selected-ticket-class"));
var messagesAccessor = CreateValidationMessagesAccessor(appElement); var messagesAccessor = CreateValidationMessagesAccessor(appElement);

View File

@ -0,0 +1,90 @@
// 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.FormsTest;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class FormsTestWithExperimentalValidator : FormsTest
{
public FormsTestWithExperimentalValidator(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<BasicTestApp.Program> serverFixture,
ITestOutputHelper output) : base(browserFixture, serverFixture, output)
{
}
protected override IWebElement MountSimpleValidationComponent()
=> Browser.MountTestComponent<SimpleValidationComponentUsingExperimentalValidator>();
protected override IWebElement MountTypicalValidationComponent()
=> Browser.MountTestComponent<TypicalValidationComponentUsingExperimentalValidator>();
[Fact]
public void EditFormWorksWithNestedValidation()
{
var appElement = Browser.MountTestComponent<ExperimentalValidationComponent>();
var nameInput = appElement.FindElement(By.CssSelector(".name input"));
var emailInput = appElement.FindElement(By.CssSelector(".email input"));
var confirmEmailInput = appElement.FindElement(By.CssSelector(".confirm-email input"));
var streetInput = appElement.FindElement(By.CssSelector(".street input"));
var zipInput = appElement.FindElement(By.CssSelector(".zip input"));
var countryInput = new SelectElement(appElement.FindElement(By.CssSelector(".country select")));
var descriptionInput = appElement.FindElement(By.CssSelector(".description input"));
var weightInput = appElement.FindElement(By.CssSelector(".weight input"));
var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]"));
submitButton.Click();
Browser.Equal(4, () => appElement.FindElements(By.CssSelector(".all-errors .validation-message")).Count);
Browser.Equal("Enter a name", () => appElement.FindElement(By.CssSelector(".name .validation-message")).Text);
Browser.Equal("Enter an email", () => appElement.FindElement(By.CssSelector(".email .validation-message")).Text);
Browser.Equal("A street address is required.", () => appElement.FindElement(By.CssSelector(".street .validation-message")).Text);
Browser.Equal("Description is required.", () => appElement.FindElement(By.CssSelector(".description .validation-message")).Text);
// Verify class-level validation
nameInput.SendKeys("Some person");
emailInput.SendKeys("test@example.com");
countryInput.SelectByValue("Mordor");
descriptionInput.SendKeys("Fragile staff");
streetInput.SendKeys("Mount Doom\t");
submitButton.Click();
// Verify member validation from IValidatableObject on a model property, CustomValidationAttribute on a model attribute, and BlazorCompareAttribute.
Browser.Equal("A ZipCode is required", () => appElement.FindElement(By.CssSelector(".zip .validation-message")).Text);
Browser.Equal("'Confirm email address' and 'EmailAddress' do not match.", () => appElement.FindElement(By.CssSelector(".confirm-email .validation-message")).Text);
Browser.Equal("Fragile items must be placed in secure containers", () => appElement.FindElement(By.CssSelector(".item-error .validation-message")).Text);
Browser.Equal(3, () => appElement.FindElements(By.CssSelector(".all-errors .validation-message")).Count);
zipInput.SendKeys("98052");
confirmEmailInput.SendKeys("test@example.com");
descriptionInput.Clear();
weightInput.SendKeys("0");
descriptionInput.SendKeys("The One Ring\t");
submitButton.Click();
// Verify validation from IValidatableObject on the model.
Browser.Equal("Some items in your list cannot be delivered.", () => appElement.FindElement(By.CssSelector(".model-errors .validation-message")).Text);
Browser.Single(() => appElement.FindElements(By.CssSelector(".all-errors .validation-message")));
// Let's make sure the form submits
descriptionInput.Clear();
descriptionInput.SendKeys("A different ring\t");
submitButton.Click();
Browser.Empty(() => appElement.FindElements(By.CssSelector(".all-errors .validation-message")));
Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.CssSelector(".submission-log")).Text);
}
}
}

View File

@ -17,6 +17,7 @@
<Reference Include="Microsoft.AspNetCore.Blazor" /> <Reference Include="Microsoft.AspNetCore.Blazor" />
<Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" /> <Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" />
<Reference Include="Microsoft.AspNetCore.Components.Authorization" /> <Reference Include="Microsoft.AspNetCore.Components.Authorization" />
<Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,185 @@
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
<p>
This component is used to verify the use of the experimental ObjectGraphDataAnnotationsValidator type with IValidatableObject and deep validation, as well
as the ComparePropertyAttribute.
</p>
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<ObjectGraphDataAnnotationsValidator />
<p class="name">
Name: <InputText @bind-Value="model.Recipient" placeholder="Enter the recipient" />
<ValidationMessage For="@(() => model.Recipient)" />
</p>
<p class="email">
Email: <InputText @bind-Value="model.EmailAddress" />
<ValidationMessage For="@(() => model.EmailAddress)" />
</p>
<p class="confirm-email">
Confirm Email: <InputText @bind-Value="model.ConfirmEmailAddress" />
<ValidationMessage For="@(() => model.ConfirmEmailAddress)" />
</p>
<fieldset>
<legend>Items to deliver</legend>
<p>
<button id="addItem" type="button" @onclick="AddItem">Add Item</button>
</p>
<ul class="items">
@foreach (var item in model.Items)
{
<li>
<div style="display: inline-flex; flex-direction: row">
<div style="flex-grow: 1" class="description">
<InputText @bind-Value="item.Description" placeholder="Description" />
<ValidationMessage For="@(() => item.Description)" />
</div>
<div style="flex-grow: 1" class="weight">
<InputNumber @bind-Value="item.Weight" />
<ValidationMessage For="@(() => item.Weight)" />
</div>
<div style="flex-grow: 1" class="item-error">
<ValidationSummary Model="item" />
</div>
</div>
</li>
}
</ul>
</fieldset>
<fieldset>
<legend>Shipping details</legend>
<p class="street">
Street Address: <InputText @bind-Value="model.Address.Street" />
<ValidationMessage For="@(() => model.Address.Street)" />
</p>
<p class="zip">
Zip Code: <InputText @bind-Value="model.Address.ZipCode" />
<ValidationMessage For="@(() => model.Address.ZipCode)" />
</p>
<p class="country">
Country:
<InputSelect @bind-Value="model.Address.Country">
<option></option>
<option value="@Country.Gondor">@Country.Gondor</option>
<option value="@Country.Mordor">@Country.Mordor</option>
<option value="@Country.Rohan">@Country.Rohan</option>
<option value="@Country.Shire">@Country.Shire</option>
</InputSelect>
<ValidationMessage For="@(() => model.Address.Country)" />
</p>
<p class="address-validation">
<ValidationSummary Model="model.Address" />
</p>
</fieldset>
<div class="model-errors">
<ValidationSummary Model="model"/>
</div>
<button type="submit">Submit</button>
<div class="all-errors">
<ValidationSummary />
</div>
</EditForm>
<ul class="submission-log">
@foreach (var entry in submissionLog)
{
<li>@entry</li>
}
</ul>
@code {
Delivery model = new Delivery();
public class Delivery : IValidatableObject
{
[Required(ErrorMessage = "Enter a name")]
public string Recipient { get; set; }
[Required(ErrorMessage = "Enter an email")]
[EmailAddress(ErrorMessage = "Enter a valid email address")]
public string EmailAddress { get; set; }
[CompareProperty(nameof(EmailAddress))]
[Display(Name = "Confirm email address")]
public string ConfirmEmailAddress { get; set; }
[ValidateComplexType]
public Address Address { get; } = new Address();
[ValidateComplexType]
public List<Item> Items { get; } = new List<Item>
{
new Item(),
};
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (Address.Street == "Mount Doom" && Items.Any(i => i.Description == "The One Ring"))
{
yield return new ValidationResult("Some items in your list cannot be delivered.");
}
}
}
public class Address : IValidatableObject
{
[Required(ErrorMessage = "A street address is required.")]
public string Street { get; set; }
public string ZipCode { get; set; }
[EnumDataType(typeof(Country))]
public Country Country { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (Country == Country.Mordor && string.IsNullOrEmpty(ZipCode))
{
yield return new ValidationResult("A ZipCode is required", new[] { nameof(ZipCode) });
}
}
}
[CustomValidation(typeof(Item), nameof(Item.CustomValidate))]
public class Item
{
[Required(ErrorMessage = "Description is required.")]
public string Description { get; set; }
[Range(0.1, 50, ErrorMessage = "Items must weigh between 0.1 and 5")]
public double Weight { get; set; } = 1;
public static ValidationResult CustomValidate(Item item, ValidationContext context)
{
if (item.Weight < 2.0 && item.Description.StartsWith("Fragile"))
{
return new ValidationResult("Fragile items must be placed in secure containers");
}
return ValidationResult.Success;
}
}
public enum Country { Gondor, Mordor, Rohan, Shire }
List<string> submissionLog = new List<string>();
void HandleValidSubmit()
{
submissionLog.Add("OnValidSubmit");
}
void AddItem()
{
model.Items.Add(new Item());
}
}

View File

@ -2,7 +2,14 @@
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
<EditForm Model="@this" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit" autocomplete="off"> <EditForm Model="@this" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit" autocomplete="off">
@if (UseExperimentalValidator)
{
<ObjectGraphDataAnnotationsValidator />
}
else
{
<DataAnnotationsValidator /> <DataAnnotationsValidator />
}
<p class="user-name"> <p class="user-name">
User name: <input @bind="UserName" class="@context.FieldCssClass(() => UserName)" /> User name: <input @bind="UserName" class="@context.FieldCssClass(() => UserName)" />
@ -29,6 +36,8 @@
} }
@code { @code {
protected virtual bool UseExperimentalValidator => false;
string lastCallback; string lastCallback;
[Required(ErrorMessage = "Please choose a username")] [Required(ErrorMessage = "Please choose a username")]

View File

@ -0,0 +1,7 @@
namespace BasicTestApp.FormsTest
{
public class TypicalValidationComponentUsingExperimentalValidator : TypicalValidationComponent
{
protected override bool UseExperimentalValidator => true;
}
}

View File

@ -2,7 +2,14 @@
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit"> <EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
@if (UseExperimentalValidator)
{
<ObjectGraphDataAnnotationsValidator />
}
else
{
<DataAnnotationsValidator /> <DataAnnotationsValidator />
}
<p class="name"> <p class="name">
Name: <InputText @bind-Value="person.Name" placeholder="Enter your name" /> Name: <InputText @bind-Value="person.Name" placeholder="Enter your name" />
@ -11,6 +18,10 @@
Email: <InputText @bind-Value="person.Email" /> Email: <InputText @bind-Value="person.Email" />
<ValidationMessage For="@(() => person.Email)" /> <ValidationMessage For="@(() => person.Email)" />
</p> </p>
<p class="confirm-email">
Email: <InputText @bind-Value="person.ConfirmEmail" />
<ValidationMessage For="@(() => person.ConfirmEmail)" />
</p>
<p class="age"> <p class="age">
Age (years): <InputNumber @bind-Value="person.AgeInYears" placeholder="Enter your age" /> Age (years): <InputNumber @bind-Value="person.AgeInYears" placeholder="Enter your age" />
</p> </p>
@ -49,12 +60,18 @@
<button type="submit">Submit</button> <button type="submit">Submit</button>
<p class="model-errors">
<ValidationSummary Model="person" />
</p>
<ValidationSummary /> <ValidationSummary />
</EditForm> </EditForm>
<ul>@foreach (var entry in submissionLog) { <li>@entry</li> }</ul> <ul>@foreach (var entry in submissionLog) { <li>@entry</li> }</ul>
@code { @code {
protected virtual bool UseExperimentalValidator => false;
Person person = new Person(); Person person = new Person();
EditContext editContext; EditContext editContext;
ValidationMessageStore customValidationMessageStore; ValidationMessageStore customValidationMessageStore;
@ -75,6 +92,9 @@
[StringLength(10, ErrorMessage = "We only accept very short email addresses (max 10 chars)")] [StringLength(10, ErrorMessage = "We only accept very short email addresses (max 10 chars)")]
public string Email { get; set; } public string Email { get; set; }
[Compare(nameof(Email), ErrorMessage = "Email and confirm email do not match.")]
public string ConfirmEmail { get; set; }
[Range(0, 200, ErrorMessage = "Nobody is that old")] [Range(0, 200, ErrorMessage = "Nobody is that old")]
public int AgeInYears { get; set; } public int AgeInYears { get; set; }

View File

@ -0,0 +1,7 @@
namespace BasicTestApp.FormsTest
{
public class SimpleValidationComponentUsingExperimentalValidator : SimpleValidationComponent
{
protected override bool UseExperimentalValidator => true;
}
}

View File

@ -29,7 +29,10 @@
<option value="BasicTestApp.FocusEventComponent">Focus events</option> <option value="BasicTestApp.FocusEventComponent">Focus events</option>
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option> <option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option> <option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option> <option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
<option value="BasicTestApp.FormsTest.ExperimentalValidationComponent">Experimental validation</option>
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option> <option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option> <option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option> <option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>

View File

@ -4,6 +4,16 @@ html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
} }
a, .btn-link {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
app { app {
position: relative; position: relative;
display: flex; display: flex;
@ -21,8 +31,19 @@ app {
} }
.main .top-row { .main .top-row {
background-color: #e6e6e6; background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5; border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
}
.main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
} }
.sidebar { .sidebar {
@ -44,20 +65,20 @@ app {
top: -2px; top: -2px;
} }
.nav-item { .sidebar .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .sidebar .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .sidebar .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item a { .sidebar .nav-item a {
color: #d7d7d7; color: #d7d7d7;
border-radius: 4px; border-radius: 4px;
height: 3rem; height: 3rem;
@ -66,12 +87,12 @@ app {
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255,255,255,0.25);
color: white; color: white;
} }
.nav-item a:hover { .sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
color: white; color: white;
} }
@ -96,9 +117,36 @@ app {
color: red; color: red;
} }
@media (max-width: 767.98px) { #blazor-error-ui {
.main .top-row { background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none; 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:not(.auth) {
display: none;
}
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
} }
} }

View File

@ -24,17 +24,15 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
public ShutdownTests(ITestOutputHelper output) : base(output) { } public ShutdownTests(ITestOutputHelper output) : base(output) { }
[ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/2577")] [ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows)] [OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2577", FlakyOn.All)]
public async Task ShutdownTestRun() public async Task ShutdownTestRun()
{ {
await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run"); await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run");
} }
[ConditionalFact] [ConditionalFact]
[Flaky("https://github.com/aspnet/Hosting/issues/1214", FlakyOn.All)]
[OSSkipCondition(OperatingSystems.Windows)] [OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
public async Task ShutdownTestWaitForShutdown() public async Task ShutdownTestWaitForShutdown()

View File

@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.WebUtilities
if (!isFinalBlock) if (!isFinalBlock)
{ {
// Don't buffer indefinately // Don't buffer indefinately
if (span.Length > KeyLengthLimit + ValueLengthLimit) if ((uint)span.Length > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
{ {
ThrowKeyOrValueTooLargeException(); ThrowKeyOrValueTooLargeException();
} }
@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.WebUtilities
if (!isFinalBlock) if (!isFinalBlock)
{ {
// Don't buffer indefinately // Don't buffer indefinately
if ((sequenceReader.Consumed - consumedBytes) > KeyLengthLimit + ValueLengthLimit) if ((uint)(sequenceReader.Consumed - consumedBytes) > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
{ {
ThrowKeyOrValueTooLargeException(); ThrowKeyOrValueTooLargeException();
} }

View File

@ -211,6 +211,28 @@ namespace Microsoft.AspNetCore.WebUtilities
Assert.Equal("", dict["t"]); Assert.Equal("", dict["t"]);
} }
[Theory]
[MemberData(nameof(Encodings))]
public void TryParseFormValues_LimitsCanBeLarge(Encoding encoding)
{
var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
KeyValueAccumulator accumulator = default;
var formReader = new FormPipeReader(null, encoding);
formReader.KeyLengthLimit = int.MaxValue;
formReader.ValueLengthLimit = int.MaxValue;
formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
Assert.True(readOnlySequence.IsEmpty);
Assert.Equal(3, accumulator.KeyCount);
var dict = accumulator.GetResults();
Assert.Equal("bar", dict["foo"]);
Assert.Equal("boo", dict["baz"]);
Assert.Equal("", dict["t"]);
}
[Theory] [Theory]
[MemberData(nameof(Encodings))] [MemberData(nameof(Encodings))]
public void TryParseFormValues_SplitAcrossSegmentsWorks(Encoding encoding) public void TryParseFormValues_SplitAcrossSegmentsWorks(Encoding encoding)
@ -230,6 +252,28 @@ namespace Microsoft.AspNetCore.WebUtilities
Assert.Equal("", dict["t"]); Assert.Equal("", dict["t"]);
} }
[Theory]
[MemberData(nameof(Encodings))]
public void TryParseFormValues_SplitAcrossSegmentsWorks_LimitsCanBeLarge(Encoding encoding)
{
var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
KeyValueAccumulator accumulator = default;
var formReader = new FormPipeReader(null, encoding);
formReader.KeyLengthLimit = int.MaxValue;
formReader.ValueLengthLimit = int.MaxValue;
formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
Assert.True(readOnlySequence.IsEmpty);
Assert.Equal(3, accumulator.KeyCount);
var dict = accumulator.GetResults();
Assert.Equal("bar", dict["foo"]);
Assert.Equal("boo", dict["baz"]);
Assert.Equal("", dict["t"]);
}
[Theory] [Theory]
[MemberData(nameof(Encodings))] [MemberData(nameof(Encodings))]
public void TryParseFormValues_MultiSegmentWithArrayPoolAcrossSegmentsWorks(Encoding encoding) public void TryParseFormValues_MultiSegmentWithArrayPoolAcrossSegmentsWorks(Encoding encoding)

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Identity
{ {
var key = await manager.GetAuthenticatorKeyAsync(user); var key = await manager.GetAuthenticatorKeyAsync(user);
int code; int code;
if (!int.TryParse(token, out code)) if (key == null || !int.TryParse(token, out code))
{ {
return false; return false;
} }

View File

@ -108,7 +108,6 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ReturnUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; } public virtual System.Threading.Tasks.Task OnGetAsync(string returnUrl = null) { throw null; }
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnPostAsync(string returnUrl = null) { throw null; } public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnPostAsync(string returnUrl = null) { throw null; }
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnPostSendVerificationEmailAsync() { throw null; }
public partial class InputModel public partial class InputModel
{ {
public InputModel() { } public InputModel() { }
@ -177,7 +176,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
public bool DisplayConfirmAccountLink { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool DisplayConfirmAccountLink { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnGetAsync(string email) { throw null; } public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnGetAsync(string email, string returnUrl = null) { throw null; }
} }
[Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute]
public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel
@ -627,7 +626,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
public bool DisplayConfirmAccountLink { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool DisplayConfirmAccountLink { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Email { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string EmailConfirmationUrl { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnGetAsync(string email) { throw null; } public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> OnGetAsync(string email, string returnUrl = null) { throw null; }
} }
[Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute] [Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute]
public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel public abstract partial class RegisterModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel

View File

@ -41,9 +41,6 @@
<p> <p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a> <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p> </p>
<p>
<button id="resend-confirmation" type="submit" asp-page-handler="SendVerificationEmail" class="btn-link" style="padding:0px;margin:0px;border:0px">Resend email confirmation</button>
</p>
</div> </div>
</form> </form>
</section> </section>

View File

@ -92,12 +92,6 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
/// directly from your code. This API may change or be removed in future releases. /// directly from your code. This API may change or be removed in future releases.
/// </summary> /// </summary>
public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException();
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual Task<IActionResult> OnPostSendVerificationEmailAsync() => throw new NotImplementedException();
} }
internal class LoginModel<TUser> : LoginModel where TUser : class internal class LoginModel<TUser> : LoginModel where TUser : class
@ -105,15 +99,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly ILogger<LoginModel> _logger; private readonly ILogger<LoginModel> _logger;
private readonly IEmailSender _emailSender;
public LoginModel(SignInManager<TUser> signInManager, ILogger<LoginModel> logger, public LoginModel(SignInManager<TUser> signInManager, ILogger<LoginModel> logger,
UserManager<TUser> userManager, UserManager<TUser> userManager)
IEmailSender emailSender)
{ {
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
_emailSender = emailSender;
_logger = logger; _logger = logger;
} }
@ -169,34 +160,5 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
// If we got this far, something failed, redisplay form // If we got this far, something failed, redisplay form
return Page(); return Page();
} }
public override async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
}
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.Email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
return Page();
}
} }
} }

View File

@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
var callbackUrl = Url.Page( var callbackUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { area = "Identity", userId = userId, code = code }, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme); protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
if (_userManager.Options.SignIn.RequireConfirmedAccount) if (_userManager.Options.SignIn.RequireConfirmedAccount)
{ {
return RedirectToPage("RegisterConfirmation", new { email = Input.Email }); return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
} }
else else
{ {

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases. /// directly from your code. This API may change or be removed in future releases.
/// </summary> /// </summary>
public virtual Task<IActionResult> OnGetAsync(string email) => throw new NotImplementedException(); public virtual Task<IActionResult> OnGetAsync(string email, string returnUrl = null) => throw new NotImplementedException();
} }
internal class RegisterConfirmationModel<TUser> : RegisterConfirmationModel where TUser : class internal class RegisterConfirmationModel<TUser> : RegisterConfirmationModel where TUser : class
@ -57,12 +57,13 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
_sender = sender; _sender = sender;
} }
public override async Task<IActionResult> OnGetAsync(string email) public override async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{ {
if (email == null) if (email == null)
{ {
return RedirectToPage("/Index"); return RedirectToPage("/Index");
} }
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _userManager.FindByEmailAsync(email); var user = await _userManager.FindByEmailAsync(email);
if (user == null) if (user == null)
@ -81,7 +82,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal
EmailConfirmationUrl = Url.Page( EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { area = "Identity", userId = userId, code = code }, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme); protocol: Request.Scheme);
} }

View File

@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
var callbackUrl = Url.Page( var callbackUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { area = "Identity", userId = userId, code = code }, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme); protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
if (_userManager.Options.SignIn.RequireConfirmedAccount) if (_userManager.Options.SignIn.RequireConfirmedAccount)
{ {
return RedirectToPage("RegisterConfirmation", new { email = Input.Email }); return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
} }
else else
{ {

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases. /// directly from your code. This API may change or be removed in future releases.
/// </summary> /// </summary>
public virtual Task<IActionResult> OnGetAsync(string email) => throw new NotImplementedException(); public virtual Task<IActionResult> OnGetAsync(string email, string returnUrl = null) => throw new NotImplementedException();
} }
internal class RegisterConfirmationModel<TUser> : RegisterConfirmationModel where TUser : class internal class RegisterConfirmationModel<TUser> : RegisterConfirmationModel where TUser : class
@ -57,12 +57,13 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
_sender = sender; _sender = sender;
} }
public override async Task<IActionResult> OnGetAsync(string email) public override async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{ {
if (email == null) if (email == null)
{ {
return RedirectToPage("/Index"); return RedirectToPage("/Index");
} }
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _userManager.FindByEmailAsync(email); var user = await _userManager.FindByEmailAsync(email);
if (user == null) if (user == null)
@ -81,7 +82,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal
EmailConfirmationUrl = Url.Page( EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { area = "Identity", userId = userId, code = code }, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme); protocol: Request.Scheme);
} }

View File

@ -56,12 +56,13 @@
</div> </div>
<footer class="footer border-top pl-3 text-muted"> <footer class="footer border-top pl-3 text-muted">
<div class="container"> <div class="container">
&copy; @DateTime.Now.Year - @Environment.ApplicationName - &copy; @DateTime.Now.Year - @Environment.ApplicationName
@{ @{
var foundPrivacy = Url.Page("/Privacy", new { area = "" }); var foundPrivacy = Url.Page("/Privacy", new { area = "" });
} }
@if (foundPrivacy != null) @if (foundPrivacy != null)
{ {
@:-
<a asp-area="" asp-page="/Privacy">Privacy</a> <a asp-area="" asp-page="/Privacy">Privacy</a>
} }
</div> </div>

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account
}); });
var registeredLocation = ResponseAssert.IsRedirect(registered); var registeredLocation = ResponseAssert.IsRedirect(registered);
Assert.Equal(RegisterConfirmation.Path + "?email="+userName, registeredLocation.ToString()); Assert.Equal(RegisterConfirmation.Path + "?email="+userName+"&returnUrl=%2F", registeredLocation.ToString());
var registerResponse = await Client.GetAsync(registeredLocation); var registerResponse = await Client.GetAsync(registeredLocation);
var register = await ResponseAssert.IsHtmlDocumentAsync(registerResponse); var register = await ResponseAssert.IsHtmlDocumentAsync(registerResponse);

View File

@ -23,7 +23,8 @@
<Target Name="Build" DependsOnTargets="DebBuild" /> <Target Name="Build" DependsOnTargets="DebBuild" />
<Target Name="Pack" /> <Target Name="Pack" />
<Target Name="DebBuild" DependsOnTargets="$(DebBuildDependsOn)" Condition="'$(IsTargetingPackBuilding)' != 'false'"> <Target Name="DebBuild" DependsOnTargets="$(DebBuildDependsOn)"
Condition="!( '$(IsTargetingPackBuilding)' == 'false' AND '$(MSBuildProjectName)' == 'Debian.TargetingPack' )">
<!-- Generate debian_config.json. We can't simply use WriteLinesToFile because of https://github.com/Microsoft/msbuild/issues/1622. Use our custom GenerateFileFromTemplate task instead --> <!-- Generate debian_config.json. We can't simply use WriteLinesToFile because of https://github.com/Microsoft/msbuild/issues/1622. Use our custom GenerateFileFromTemplate task instead -->
<PropertyGroup> <PropertyGroup>
<DebianConfigProperties> <DebianConfigProperties>

View File

@ -32,7 +32,8 @@
<Target Name="Build" DependsOnTargets="RpmBuild" /> <Target Name="Build" DependsOnTargets="RpmBuild" />
<Target Name="Pack" /> <Target Name="Pack" />
<Target Name="RpmBuild" DependsOnTargets="$(RpmBuildDependsOn)" Condition="'$(IsTargetingPackBuilding)' != 'false'"> <Target Name="RpmBuild" DependsOnTargets="$(RpmBuildDependsOn)"
Condition="!( '$(IsTargetingPackBuilding)' == 'false' AND '$(MSBuildProjectName)' == 'Rpm.TargetingPack' )">
<!-- Create layout: Create changelog --> <!-- Create layout: Create changelog -->
<PropertyGroup> <PropertyGroup>
<ChangeLogProps>DATE=$([System.DateTime]::UtcNow.ToString(ddd MMM dd yyyy))</ChangeLogProps> <ChangeLogProps>DATE=$([System.DateTime]::UtcNow.ToString(ddd MMM dd yyyy))</ChangeLogProps>

View File

@ -82,7 +82,8 @@
<BuildDependsOn Condition="'$(IsTargetingPackBuilding)' == 'false'" /> <BuildDependsOn Condition="'$(IsTargetingPackBuilding)' == 'false'" />
</PropertyGroup> </PropertyGroup>
<Target Name="CreateTargetingPackNugetPackage" AfterTargets="CopyToArtifactsDirectory;Build"> <Target Name="CreateTargetingPackNugetPackage" AfterTargets="CopyToArtifactsDirectory;Build"
Condition="'$(IsTargetingPackBuilding)' != 'false'">
<PropertyGroup> <PropertyGroup>
<MsiFullPath>$(InstallersOutputPath)$(PackageFileName)</MsiFullPath> <MsiFullPath>$(InstallersOutputPath)$(PackageFileName)</MsiFullPath>

View File

@ -882,11 +882,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
public Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind MetadataKind { get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind MetadataKind { get { throw null; } }
public System.Type ModelType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Type ModelType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Reflection.ParameterInfo ParameterInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Reflection.ParameterInfo ParameterInfo { get { throw null; } }
public System.Reflection.PropertyInfo PropertyInfo { get { throw null; } }
public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity other) { throw null; } public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity other) { throw null; }
public override bool Equals(object obj) { throw null; } public override bool Equals(object obj) { throw null; }
public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter) { throw null; }
public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter, System.Type modelType) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter, System.Type modelType) { throw null; }
public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForProperty(System.Reflection.PropertyInfo propertyInfo, System.Type modelType, System.Type containerType) { throw null; }
[System.ObsoleteAttribute("This API is obsolete and may be removed in a future release.")]
public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForProperty(System.Type modelType, string name, System.Type containerType) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForProperty(System.Type modelType, string name, System.Type containerType) { throw null; }
public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForType(System.Type modelType) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForType(System.Type modelType) { throw null; }
public override int GetHashCode() { throw null; } public override int GetHashCode() { throw null; }

View File

@ -17,12 +17,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
Type modelType, Type modelType,
string name = null, string name = null,
Type containerType = null, Type containerType = null,
ParameterInfo parameterInfo = null) object fieldInfo = null)
{ {
ModelType = modelType; ModelType = modelType;
Name = name; Name = name;
ContainerType = containerType; ContainerType = containerType;
ParameterInfo = parameterInfo; FieldInfo = fieldInfo;
} }
/// <summary> /// <summary>
@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// <param name="name">The name of the property.</param> /// <param name="name">The name of the property.</param>
/// <param name="containerType">The container type of the model property.</param> /// <param name="containerType">The container type of the model property.</param>
/// <returns>A <see cref="ModelMetadataIdentity"/>.</returns> /// <returns>A <see cref="ModelMetadataIdentity"/>.</returns>
[Obsolete("This API is obsolete and may be removed in a future release.")]
public static ModelMetadataIdentity ForProperty( public static ModelMetadataIdentity ForProperty(
Type modelType, Type modelType,
string name, string name,
@ -70,6 +71,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
return new ModelMetadataIdentity(modelType, name, containerType); return new ModelMetadataIdentity(modelType, name, containerType);
} }
/// <summary>
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided property.
/// </summary>
/// <param name="modelType">The model type.</param>
/// <param name="propertyInfo">The property.</param>
/// <param name="containerType">The container type of the model property.</param>
/// <returns>A <see cref="ModelMetadataIdentity"/>.</returns>
public static ModelMetadataIdentity ForProperty(
PropertyInfo propertyInfo,
Type modelType,
Type containerType)
{
if (propertyInfo == null)
{
throw new ArgumentNullException(nameof(propertyInfo));
}
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
if (containerType == null)
{
throw new ArgumentNullException(nameof(containerType));
}
return new ModelMetadataIdentity(modelType, propertyInfo.Name, containerType, fieldInfo: propertyInfo);
}
/// <summary> /// <summary>
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided parameter. /// Creates a <see cref="ModelMetadataIdentity"/> for the provided parameter.
/// </summary> /// </summary>
@ -97,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
throw new ArgumentNullException(nameof(modelType)); throw new ArgumentNullException(nameof(modelType));
} }
return new ModelMetadataIdentity(modelType, parameter.Name, parameterInfo: parameter); return new ModelMetadataIdentity(modelType, parameter.Name, fieldInfo: parameter);
} }
/// <summary> /// <summary>
@ -139,11 +170,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// </summary> /// </summary>
public string Name { get; } public string Name { get; }
private object FieldInfo { get; }
/// <summary> /// <summary>
/// Gets a descriptor for the parameter, or <c>null</c> if this instance /// Gets a descriptor for the parameter, or <c>null</c> if this instance
/// does not represent a parameter. /// does not represent a parameter.
/// </summary> /// </summary>
public ParameterInfo ParameterInfo { get; } public ParameterInfo ParameterInfo => FieldInfo as ParameterInfo;
/// <summary>
/// Gets a descriptor for the property, or <c>null</c> if this instance
/// does not represent a property.
/// </summary>
public PropertyInfo PropertyInfo => FieldInfo as PropertyInfo;
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(ModelMetadataIdentity other) public bool Equals(ModelMetadataIdentity other)
@ -152,7 +191,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
ContainerType == other.ContainerType && ContainerType == other.ContainerType &&
ModelType == other.ModelType && ModelType == other.ModelType &&
Name == other.Name && Name == other.Name &&
ParameterInfo == other.ParameterInfo; ParameterInfo == other.ParameterInfo &&
PropertyInfo == other.PropertyInfo;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -170,6 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
hash.Add(ModelType); hash.Add(ModelType);
hash.Add(Name, StringComparer.Ordinal); hash.Add(Name, StringComparer.Ordinal);
hash.Add(ParameterInfo); hash.Add(ParameterInfo);
hash.Add(PropertyInfo);
return hash; return hash;
} }
} }

View File

@ -270,7 +270,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ContainerType_ReturnExpectedMetadata_ForProperty() public void ContainerType_ReturnExpectedMetadata_ForProperty()
{ {
// Arrange & Act // Arrange & Act
var metadata = new TestModelMetadata(typeof(int), nameof(string.Length), typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var metadata = new TestModelMetadata(property, typeof(int), typeof(string));
// Assert // Assert
Assert.Equal(typeof(string), metadata.ContainerType); Assert.Equal(typeof(string), metadata.ContainerType);
@ -308,7 +309,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void Names_ReturnExpectedMetadata_ForProperty() public void Names_ReturnExpectedMetadata_ForProperty()
{ {
// Arrange & Act // Arrange & Act
var metadata = new TestModelMetadata(typeof(int), nameof(string.Length), typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var metadata = new TestModelMetadata(property, typeof(int), typeof(string));
// Assert // Assert
Assert.Equal(nameof(string.Length), metadata.Name); Assert.Equal(nameof(string.Length), metadata.Name);
@ -322,7 +324,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void GetDisplayName_ReturnsDisplayName_IfSet() public void GetDisplayName_ReturnsDisplayName_IfSet()
{ {
// Arrange // Arrange
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var metadata = new TestModelMetadata(property, typeof(int), typeof(string));
metadata.SetDisplayName("displayName"); metadata.SetDisplayName("displayName");
// Act // Act
@ -351,7 +354,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void GetDisplayName_ReturnsPropertyName_WhenSetAndDisplayNameIsNull() public void GetDisplayName_ReturnsPropertyName_WhenSetAndDisplayNameIsNull()
{ {
// Arrange // Arrange
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var metadata = new TestModelMetadata(property, typeof(int), typeof(string));
// Act // Act
var result = metadata.GetDisplayName(); var result = metadata.GetDisplayName();
@ -419,8 +423,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
} }
public TestModelMetadata(Type modelType, string propertyName, Type containerType) public TestModelMetadata(PropertyInfo propertyInfo, Type modelType, Type containerType)
: base(ModelMetadataIdentity.ForProperty(modelType, propertyName, containerType)) : base(ModelMetadataIdentity.ForProperty(propertyInfo, modelType, containerType))
{ {
} }

View File

@ -8,7 +8,6 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters.Json; using Microsoft.AspNetCore.Mvc.Formatters.Json;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Formatters namespace Microsoft.AspNetCore.Mvc.Formatters
@ -87,6 +86,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return InputFormatterResult.Failure(); return InputFormatterResult.Failure();
} }
catch (Exception exception) when (exception is FormatException || exception is OverflowException)
{
// The code in System.Text.Json never throws these exceptions. However a custom converter could produce these errors for instance when
// parsing a value. These error messages are considered safe to report to users using ModelState.
context.ModelState.TryAddModelError(string.Empty, exception, context.Metadata);
Log.JsonInputException(_logger, exception);
return InputFormatterResult.Failure();
}
finally finally
{ {
if (inputStream is TranscodingReadStream transcoding) if (inputStream is TranscodingReadStream transcoding)

View File

@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
private ModelMetadataCacheEntry GetCacheEntry(PropertyInfo property, Type modelType) private ModelMetadataCacheEntry GetCacheEntry(PropertyInfo property, Type modelType)
{ {
return _typeCache.GetOrAdd( return _typeCache.GetOrAdd(
ModelMetadataIdentity.ForProperty(modelType, property.Name, property.DeclaringType), ModelMetadataIdentity.ForProperty(property, modelType, property.DeclaringType),
_cacheEntryFactory); _cacheEntryFactory);
} }
@ -275,8 +275,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var propertyHelper = propertyHelpers[i]; var propertyHelper = propertyHelpers[i];
var propertyKey = ModelMetadataIdentity.ForProperty( var propertyKey = ModelMetadataIdentity.ForProperty(
propertyHelper.Property,
propertyHelper.Property.PropertyType, propertyHelper.Property.PropertyType,
propertyHelper.Name,
key.ModelType); key.ModelType);
var propertyEntry = CreateSinglePropertyDetails(propertyKey, propertyHelper); var propertyEntry = CreateSinglePropertyDetails(propertyKey, propertyHelper);

View File

@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Moq; using Moq;
using Xunit; using Xunit;
@ -27,7 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var context = new DefaultModelBindingContext(); var context = new DefaultModelBindingContext();
#pragma warning disable CS0618 // Type or member is obsolete
var identity = ModelMetadataIdentity.ForProperty(typeof(int), property, typeof(string)); var identity = ModelMetadataIdentity.ForProperty(typeof(int), property, typeof(string));
#pragma warning restore CS0618 // Type or member is obsolete
context.ModelMetadata = new Mock<ModelMetadata>(identity).Object; context.ModelMetadata = new Mock<ModelMetadata>(identity).Object;
// Act // Act

View File

@ -334,7 +334,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Assert // Assert
Assert.True(result.HasError, "Model should have produced an error!"); Assert.True(result.HasError, "Model should have produced an error!");
Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key), Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key),
kvp => { kvp =>
{
Assert.Equal(expectedValue, kvp.Key); Assert.Equal(expectedValue, kvp.Key);
}); });
} }

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
@ -81,6 +83,48 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}); });
} }
[Fact]
public async Task ReadAsync_DoesNotThrowFormatException()
{
// Arrange
var formatter = GetInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes("{\"dateValue\":\"not-a-date\"}");
var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(TypeWithBadConverters), httpContext);
// Act
await formatter.ReadAsync(formatterContext);
Assert.False(formatterContext.ModelState.IsValid);
var kvp = Assert.Single(formatterContext.ModelState);
Assert.Empty(kvp.Key);
var error = Assert.Single(kvp.Value.Errors);
Assert.Equal("The supplied value is invalid.", error.ErrorMessage);
}
[Fact]
public async Task ReadAsync_DoesNotThrowOverflowException()
{
// Arrange
var formatter = GetInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes("{\"shortValue\":\"32768\"}");
var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(TypeWithBadConverters), httpContext);
// Act
await formatter.ReadAsync(formatterContext);
Assert.False(formatterContext.ModelState.IsValid);
var kvp = Assert.Single(formatterContext.ModelState);
Assert.Empty(kvp.Key);
var error = Assert.Single(kvp.Value.Errors);
Assert.Equal("The supplied value is invalid.", error.ErrorMessage);
}
protected override TextInputFormatter GetInputFormatter() protected override TextInputFormatter GetInputFormatter()
{ {
return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger<SystemTextJsonInputFormatter>()); return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger<SystemTextJsonInputFormatter>());
@ -99,5 +143,40 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "$[1].Small"; internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "$[1].Small";
internal override string ReadAsync_ComplexPoco_Expected => "$.Person.Numbers[2]"; internal override string ReadAsync_ComplexPoco_Expected => "$.Person.Numbers[2]";
private class TypeWithBadConverters
{
[JsonConverter(typeof(DateTimeConverter))]
public DateTime DateValue { get; set; }
[JsonConverter(typeof(ShortConverter))]
public short ShortValue { get; set; }
}
private class ShortConverter : JsonConverter<short>
{
public override short Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return short.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
private class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
} }
} }

View File

@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -420,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -438,7 +438,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{ {
// Arrange // Arrange
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindRequiredOnClass).GetProperty(nameof(BindRequiredOnClass.Property)), typeof(int), typeof(BindRequiredOnClass)),
new ModelAttributes(new object[0], new object[0], null)); new ModelAttributes(new object[0], new object[0], null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -456,7 +456,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{ {
// Arrange // Arrange
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindNeverOnClass).GetProperty(nameof(BindNeverOnClass.Property)), typeof(int), typeof(BindNeverOnClass)),
new ModelAttributes(new object[0], new object[0], null)); new ModelAttributes(new object[0], new object[0], null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -474,7 +474,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{ {
// Arrange // Arrange
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindNeverOnClass).GetProperty(nameof(BindNeverOnClass.Property)), typeof(int), typeof(BindNeverOnClass)),
new ModelAttributes(new object[0], new object[0], null)); new ModelAttributes(new object[0], new object[0], null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindNeverOnClass).GetProperty(nameof(BindNeverOnClass.Property)), typeof(int), typeof(BindNeverOnClass)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -520,7 +520,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindNeverOnClass).GetProperty(nameof(BindNeverOnClass.Property)), typeof(int), typeof(BindNeverOnClass)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -543,7 +543,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), ModelMetadataIdentity.ForProperty(typeof(InheritedBindNeverOnClass).GetProperty(nameof(InheritedBindNeverOnClass.Property)), typeof(int), typeof(InheritedBindNeverOnClass)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -566,7 +566,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), ModelMetadataIdentity.ForProperty(typeof(BindRequiredOnClass).GetProperty(nameof(BindRequiredOnClass.Property)), typeof(int), typeof(BindRequiredOnClass)),
new ModelAttributes(new object[0], propertyAttributes, null)); new ModelAttributes(new object[0], propertyAttributes, null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -585,7 +585,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{ {
// Arrange // Arrange
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)), ModelMetadataIdentity.ForProperty(typeof(BindRequiredOverridesInheritedBindNever).GetProperty(nameof(BindRequiredOverridesInheritedBindNever.Property)), typeof(int), typeof(BindRequiredOverridesInheritedBindNever)),
new ModelAttributes(new object[0], new object[0], null)); new ModelAttributes(new object[0], new object[0], null));
var provider = new DefaultBindingMetadataProvider(); var provider = new DefaultBindingMetadataProvider();
@ -641,7 +641,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}; };
var context = new BindingMetadataProviderContext( var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string)),
new ModelAttributes(typeAttributes, new object[0], null)); new ModelAttributes(typeAttributes, new object[0], null));
// These values shouldn't be changed since this is a Type-Metadata // These values shouldn't be changed since this is a Type-Metadata

View File

@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception)); var key = ModelMetadataIdentity.ForProperty(typeof(Exception).GetProperty(nameof(Exception.Message)), typeof(string), typeof(Exception));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], new object[0], null)); var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], new object[0], null));
// Act // Act
@ -123,8 +123,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(TypeWithProperties).GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)),
typeof(string), typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties)); typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null); var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
@ -160,8 +160,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(TypeWithProperties).GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)),
typeof(string), typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties)); typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null); var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
@ -197,8 +197,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(TypeWithProperties).GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)),
typeof(string), typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties)); typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null); var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
@ -393,19 +393,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict); var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var prop1 = typeof(Exception).GetProperty(nameof(Exception.Message));
var prop2 = typeof(Exception).GetProperty(nameof(Exception.StackTrace));
var expectedProperties = new DefaultModelMetadata[] var expectedProperties = new DefaultModelMetadata[]
{ {
new DefaultModelMetadata( new DefaultModelMetadata(
provider.Object, provider.Object,
detailsProvider, detailsProvider,
new DefaultMetadataDetails( new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)), ModelMetadataIdentity.ForProperty(prop1, typeof(int), typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0], null))), attributes: new ModelAttributes(new object[0], new object[0], null))),
new DefaultModelMetadata( new DefaultModelMetadata(
provider.Object, provider.Object,
detailsProvider, detailsProvider,
new DefaultMetadataDetails( new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)), ModelMetadataIdentity.ForProperty(prop2, typeof(int), typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0], null))), attributes: new ModelAttributes(new object[0], new object[0], null))),
}; };
@ -475,7 +478,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
provider.Object, provider.Object,
detailsProvider, detailsProvider,
new DefaultMetadataDetails( new DefaultMetadataDetails(
#pragma warning disable CS0618 // Using the obsolete overload does not affect the intent of this test, but fixing it requires a lot of code churn.
ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)), ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)),
#pragma warning restore CS0618 // Type or member is obsolete
attributes: new ModelAttributes(new object[0], new object[0], null)))); attributes: new ModelAttributes(new object[0], new object[0], null))));
} }
@ -575,7 +580,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
foreach (var kvp in originalNamesAndOrders) foreach (var kvp in originalNamesAndOrders)
{ {
var propertyCache = new DefaultMetadataDetails( var propertyCache = new DefaultMetadataDetails(
#pragma warning disable CS0618 // Using the obsolete overload does not affect the intent of this test, but fixing it requires a lot of code churn.
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)), ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
#pragma warning restore CS0618 // Type or member is obsolete
attributes: new ModelAttributes(new object[0], new object[0], null)) attributes: new ModelAttributes(new object[0], new object[0], null))
{ {
DisplayMetadata = new DisplayMetadata(), DisplayMetadata = new DisplayMetadata(),
@ -934,7 +941,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
// Arrange // Arrange
var property = GetType() var property = GetType()
.GetProperty(nameof(CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty), BindingFlags.Static | BindingFlags.NonPublic); .GetProperty(nameof(CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty), BindingFlags.Static | BindingFlags.NonPublic);
var modelIdentity = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, GetType()); var modelIdentity = ModelMetadataIdentity.ForProperty(property, property.PropertyType, GetType());
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false); var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
// Act // Act
@ -997,7 +1004,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); var property = typeof(TypeWithProperties).GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty));
var propertyIdentity = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(TypeWithProperties));
var propertyMetadata = new Mock<ModelMetadata>(propertyIdentity); var propertyMetadata = new Mock<ModelMetadata>(propertyIdentity);
metadataProvider metadataProvider
@ -1021,10 +1029,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); var property1Identity = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)), typeof(int), modelType);
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false); var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), typeof(string)); var property2Identity = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(TypeWithProperties.PublicGetProtectedSetProperty)), typeof(int), modelType);
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, true); var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, true);
metadataProvider metadataProvider
@ -1048,7 +1056,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); var propertyIdentity = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)), typeof(int), modelType);
var propertyMetadata = CreateModelMetadata(propertyIdentity, metadataProvider.Object, null); var propertyMetadata = CreateModelMetadata(propertyIdentity, metadataProvider.Object, null);
metadataProvider metadataProvider
@ -1072,10 +1080,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), modelType); var property1Identity = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(TypeWithProperties.PublicGetPublicSetProperty)), typeof(int), modelType);
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false); var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), modelType); var property2Identity = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(TypeWithProperties.PublicGetProtectedSetProperty)), typeof(int), modelType);
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, false); var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, false);
metadataProvider metadataProvider
@ -1099,18 +1107,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); var employeeId = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Id)), typeof(int), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); var employeeUnit = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Unit)), typeof(BusinessUnit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); var employeeManager = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Manager)), typeof(Employee), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType); var employeeEmployees = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Employees)), typeof(List<Employee>), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); var unitModel = typeof(BusinessUnit);
var unitHead = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Head)), typeof(Employee), unitModel);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false); var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); var unitId = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Id)), typeof(int), unitModel);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, true); // BusinessUnit.Id has validators. var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, true); // BusinessUnit.Id has validators.
metadataProvider metadataProvider
@ -1139,18 +1148,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); var employeeId = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Id)), typeof(int), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); var employeeUnit = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Unit)), typeof(BusinessUnit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); var employeeManager = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Manager)), typeof(Employee), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType); var employeeEmployees = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Employees)), typeof(List<Employee>), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); var unitModel = typeof(BusinessUnit);
var unitHead = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Head)), typeof(Employee), unitModel);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, true); // BusinessUnit.Head has validators var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, true); // BusinessUnit.Head has validators
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); var unitId = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Id)), typeof(int), unitModel);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false); var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
metadataProvider metadataProvider
@ -1181,9 +1191,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); var employeeId = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Id)), typeof(int), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType); var employeeEmployees = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Employees)), typeof(List<Employee>), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
metadataProvider metadataProvider
@ -1210,18 +1220,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); var employeeId = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Id)), typeof(int), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); var employeeUnit = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Unit)), typeof(BusinessUnit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); var employeeManager = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Manager)), typeof(Employee), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployeesId = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType); var employeeEmployeesId = ModelMetadataIdentity.ForProperty(modelType.GetProperty(nameof(Employee.Employees)), typeof(List<Employee>), modelType);
var employeeEmployeesIdMetadata = CreateModelMetadata(employeeEmployeesId, metadataProvider.Object, false); var employeeEmployeesIdMetadata = CreateModelMetadata(employeeEmployeesId, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); var unitModel = typeof(BusinessUnit);
var unitHead = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Head)), typeof(Employee), unitModel);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false); var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); var unitId = ModelMetadataIdentity.ForProperty(unitModel.GetProperty(nameof(BusinessUnit.Id)), typeof(int), unitModel);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false); var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
metadataProvider metadataProvider

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultValidationMetadataProvider(); var provider = new DefaultValidationMetadataProvider();
var attributes = new Attribute[] { new ValidateNeverAttribute() }; var attributes = new Attribute[] { new ValidateNeverAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
// Act // Act
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultValidationMetadataProvider(); var provider = new DefaultValidationMetadataProvider();
var attributes = new Attribute[] { new ValidateNeverAttribute() }; var attributes = new Attribute[] { new ValidateNeverAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0], null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(attributes, new object[0], null));
// Act // Act
@ -71,8 +71,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultValidationMetadataProvider(); var provider = new DefaultValidationMetadataProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ValidateNeverClass).GetProperty(nameof(ValidateNeverClass.ClassName)),
typeof(string), typeof(string),
nameof(ValidateNeverClass.ClassName),
typeof(ValidateNeverClass)); typeof(ValidateNeverClass));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
@ -93,8 +93,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultValidationMetadataProvider(); var provider = new DefaultValidationMetadataProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ValidateNeverSubclass).GetProperty(nameof(ValidateNeverSubclass.SubclassName)),
typeof(string), typeof(string),
nameof(ValidateNeverSubclass.SubclassName),
typeof(ValidateNeverSubclass)); typeof(ValidateNeverSubclass));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var attribute = new TestClientModelValidationAttribute(); var attribute = new TestClientModelValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
// Act // Act
@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var attribute = new TestModelValidationAttribute(); var attribute = new TestModelValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
// Act // Act
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var attribute = new TestValidationAttribute(); var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(string).GetProperty(nameof(string.Length)), typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null)); var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
context.ValidationMetadata.ValidatorMetadata.Add(attribute); context.ValidationMetadata.ValidatorMetadata.Add(attribute);

View File

@ -16,8 +16,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new ExcludeBindingMetadataProvider(typeof(string)); var provider = new ExcludeBindingMetadataProvider(typeof(string));
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(Person).GetProperty(nameof(Person.Age)),
typeof(int), typeof(int),
nameof(Person.Age),
typeof(Person)); typeof(Person));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null)); var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
@ -40,8 +40,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new ExcludeBindingMetadataProvider(typeof(int)); var provider = new ExcludeBindingMetadataProvider(typeof(int));
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(Person).GetProperty(nameof(Person.Age)),
typeof(int), typeof(int),
nameof(Person.Age),
typeof(Person)); typeof(Person));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null)); var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));

View File

@ -365,12 +365,29 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
// Do nothing. // Do nothing.
} }
else if (context.Key.MetadataKind == ModelMetadataKind.Property) else if (context.Key.MetadataKind == ModelMetadataKind.Property)
{
var property = context.Key.PropertyInfo;
if (property is null)
{
// PropertyInfo was unavailable on ModelIdentity prior to 3.1.
// Making a cogent argument about the nullability of the property requires inspecting the declared type,
// since looking at the runtime type may result in false positives: https://github.com/aspnet/AspNetCore/issues/14812
// The only way we could arrive here is if the ModelMetadata was constructed using the non-default provider.
// We'll cursorily examine the attributes on the property, but not the ContainerType to make a decision about it's nullability.
if (HasNullableAttribute(context.PropertyAttributes, out var propertyHasNullableAttribute))
{
addInferredRequiredAttribute = propertyHasNullableAttribute;
}
}
else
{ {
addInferredRequiredAttribute = IsNullableReferenceType( addInferredRequiredAttribute = IsNullableReferenceType(
context.Key.ContainerType, property.DeclaringType,
member: null, member: null,
context.PropertyAttributes); context.PropertyAttributes);
} }
}
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter) else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
{ {
addInferredRequiredAttribute = IsNullableReferenceType( addInferredRequiredAttribute = IsNullableReferenceType(

View File

@ -1111,7 +1111,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var required = new RequiredAttribute(); var required = new RequiredAttribute();
var attributes = new Attribute[] { required }; var attributes = new Attribute[] { required };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act // Act
@ -1131,7 +1132,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var provider = CreateProvider(); var provider = CreateProvider();
var attributes = new Attribute[] { }; var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.ValidationMetadata.IsRequired = initialValue; context.ValidationMetadata.IsRequired = initialValue;
@ -1152,8 +1154,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes),
typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType))); typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType)));
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType)),
nameof(NullableReferenceTypes.NonNullableReferenceType), typeof(string)); typeof(string),
typeof(NullableReferenceTypes));
var context = new ValidationMetadataProviderContext(key, attributes); var context = new ValidationMetadataProviderContext(key, attributes);
// Act // Act
@ -1174,9 +1177,11 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var attributes = ModelAttributes.GetAttributesForProperty( var attributes = ModelAttributes.GetAttributesForProperty(
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes),
typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceTypeWithRequired))); typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceTypeWithRequired)));
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceTypeWithRequired)),
nameof(NullableReferenceTypes.NonNullableReferenceTypeWithRequired), typeof(string)); typeof(string),
typeof(NullableReferenceTypes));
var context = new ValidationMetadataProviderContext(key, attributes); var context = new ValidationMetadataProviderContext(key, attributes);
// Act // Act
@ -1201,9 +1206,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var attributes = ModelAttributes.GetAttributesForProperty( var attributes = ModelAttributes.GetAttributesForProperty(
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes),
typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType))); typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType)));
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(NullableReferenceTypes), typeof(NullableReferenceTypes).GetProperty(nameof(NullableReferenceTypes.NonNullableReferenceType)),
nameof(NullableReferenceTypes.NonNullableReferenceType), typeof(string)); typeof(string),
typeof(NullableReferenceTypes));
var context = new ValidationMetadataProviderContext(key, attributes); var context = new ValidationMetadataProviderContext(key, attributes);
// Act // Act
@ -1214,6 +1222,189 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute); Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
} }
[Theory]
[InlineData(nameof(DerivedTypeWithAllNonNullProperties.Property1))]
[InlineData(nameof(DerivedTypeWithAllNonNullProperties.Property2))]
public void CreateValidationMetadata_InfersRequiredAttributeOnDerivedType_BaseAnDerivedTypHaveAllNonNullProperties(string propertyName)
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithAllNonNullProperties);
var property = modelType.GetProperty(propertyName);
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// This test verifies how MVC reads the NullableContextOptions. We expect the property to not have a Nullable attribute on, and for
// the types to have NullableContext. We'll encode our expectations as assertions so that we can catch if or when the compiler changes
// this behavior and the test needs to be tweaked.
Assert.False(DataAnnotationsMetadataProvider.HasNullableAttribute(context.PropertyAttributes, out _), "We do not expect NullableAttribute to be defined on the property");
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.IsRequired);
Assert.Contains(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_InfersRequiredAttributeOnDerivedType_PropertyDeclaredOnBaseType()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithAllNonNullProperties_WithNullableProperties);
var property = modelType.GetProperty(nameof(DerivedTypeWithAllNonNullProperties_WithNullableProperties.Property1));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.IsRequired);
Assert.Contains(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_InfersRequiredAttributeOnDerivedType_NullablePropertyDeclaredOnDerviedType()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithAllNonNullProperties_WithNullableProperties);
var property = modelType.GetProperty(nameof(DerivedTypeWithAllNonNullProperties_WithNullableProperties.Property2));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.IsRequired);
Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Theory]
[InlineData(nameof(DerivedTypeWithNullableProperties.Property1))]
[InlineData(nameof(DerivedTypeWithNullableProperties.Property2))]
public void CreateValidationMetadata_BaseAnDerivedTypHaveAllNullableProperties_DoesNotInferRequiredAttribute(string propertyName)
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithNullableProperties);
var property = modelType.GetProperty(propertyName);
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.IsRequired);
Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_InfersRequiredAttribute_BaseTypeIsNullable_PropertyIsNotNull()
{
// Tests the scenario listed in https://github.com/aspnet/AspNetCore/issues/14812
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithNullableProperties_WithNonNullProperties);
var property = modelType.GetProperty(nameof(DerivedTypeWithNullableProperties_WithNonNullProperties.Property2));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.IsRequired);
Assert.Contains(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_InfersRequiredAttribute_ShadowedPropertyIsNonNull()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(DerivedTypeWithNullableProperties_ShadowedProperty);
var property = modelType.GetProperty(nameof(DerivedTypeWithNullableProperties_ShadowedProperty.Property1));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.IsRequired);
Assert.Contains(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_DoesNotInfersRequiredAttribute_TypeImplementingNonNullAbstractClass()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(TypeImplementIInterfaceWithNonNullProperty);
var property = modelType.GetProperty(nameof(TypeImplementIInterfaceWithNonNullProperty.Property));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.IsRequired);
Assert.Contains(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_DoesNotInfersRequiredAttribute_TypeImplementingNonNullAbstractClass_NotNullable()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(TypeImplementIInterfaceWithNonNullProperty_AsNullable);
var property = modelType.GetProperty(nameof(TypeImplementIInterfaceWithNonNullProperty_AsNullable.Property));
var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, modelType);
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.IsRequired);
Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact]
public void CreateValidationMetadata_WithOldModelIdentity_DoesNotInferValueBasedOnContext()
{
// Arrange
var provider = CreateProvider();
var modelType = typeof(TypeWithAllNonNullProperties);
var property = modelType.GetProperty(nameof(TypeWithAllNonNullProperties.Property1));
#pragma warning disable CS0618 // Type or member is obsolete
var key = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, modelType);
#pragma warning restore CS0618 // Type or member is obsolete
var context = new ValidationMetadataProviderContext(key, ModelAttributes.GetAttributesForProperty(modelType, property));
// Act
provider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.IsRequired);
Assert.DoesNotContain(context.ValidationMetadata.ValidatorMetadata, m => m is RequiredAttribute);
}
[Fact] [Fact]
public void CreateValidationMetadata_WillAddValidationAttributes_From_ValidationProviderAttribute() public void CreateValidationMetadata_WillAddValidationAttributes_From_ValidationProviderAttribute()
{ {
@ -1227,7 +1418,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
}); });
var attributes = new Attribute[] { new EmailAddressAttribute(), validationProviderAttribute }; var attributes = new Attribute[] { new EmailAddressAttribute(), validationProviderAttribute };
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act // Act
@ -1254,7 +1446,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var provider = CreateProvider(); var provider = CreateProvider();
var attributes = new Attribute[] { new RequiredAttribute() }; var attributes = new Attribute[] { new RequiredAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsBindingRequired = initialValue; context.BindingMetadata.IsBindingRequired = initialValue;
@ -1275,7 +1468,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var provider = CreateProvider(); var provider = CreateProvider();
var attributes = new Attribute[] { }; var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsReadOnly = initialValue; context.BindingMetadata.IsReadOnly = initialValue;
@ -1294,7 +1488,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var attribute = new TestValidationAttribute(); var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act // Act
@ -1313,7 +1508,29 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var attribute = new TestValidationAttribute(); var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.ValidationMetadata.ValidatorMetadata.Add(attribute);
// Act
provider.CreateValidationMetadata(context);
// Assert
var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata);
Assert.Same(attribute, validatorMetadata);
}
[Fact]
public void CreateValidationDetails_ForProperty()
{
// Arrange
var provider = CreateProvider();
var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute };
var property = typeof(string).GetProperty(nameof(string.Length));
var key = ModelMetadataIdentity.ForProperty(property, typeof(int), typeof(string));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.ValidationMetadata.ValidatorMetadata.Add(attribute); context.ValidationMetadata.ValidatorMetadata.Add(attribute);
@ -1657,6 +1874,56 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
{ {
} }
} }
private class TypeWithAllNonNullProperties
{
public string Property1 { get; set; } = string.Empty;
}
private class DerivedTypeWithAllNonNullProperties : TypeWithAllNonNullProperties
{
public string Property2 { get; set; } = string.Empty;
}
private class DerivedTypeWithAllNonNullProperties_WithNullableProperties : TypeWithAllNonNullProperties
{
public string? Property2 { get; set; } = string.Empty;
}
private class TypeWithNullableProperties
{
public string? Property1 { get; set; }
}
private class DerivedTypeWithNullableProperties : TypeWithNullableProperties
{
public string? Property2 { get; set; }
}
private class DerivedTypeWithNullableProperties_WithNonNullProperties : TypeWithNullableProperties
{
public string Property2 { get; set; } = string.Empty;
}
private class DerivedTypeWithNullableProperties_ShadowedProperty : TypeWithNullableProperties
{
public new string Property1 { get; set; } = string.Empty;
}
public abstract class AbstraceTypehNonNullProperty
{
public abstract string Property { get; set; }
}
public class TypeImplementIInterfaceWithNonNullProperty : AbstraceTypehNonNullProperty
{
public override string Property { get; set; } = string.Empty;
}
#nullable restore #nullable restore
public class TypeImplementIInterfaceWithNonNullProperty_AsNullable : AbstraceTypehNonNullProperty
{
public override string Property { get; set; }
}
} }
} }

View File

@ -24,8 +24,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
}; };
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ClassWithDataMemberIsRequiredTrue).GetProperty(nameof(ClassWithDataMemberIsRequiredTrue.StringProperty)),
typeof(string), typeof(string),
nameof(ClassWithDataMemberIsRequiredTrue.StringProperty),
typeof(ClassWithDataMemberIsRequiredTrue)); typeof(ClassWithDataMemberIsRequiredTrue));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
@ -50,8 +50,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
}; };
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ClassWithDataMemberIsRequiredFalse).GetProperty(nameof(ClassWithDataMemberIsRequiredFalse.StringProperty)),
typeof(string), typeof(string),
nameof(ClassWithDataMemberIsRequiredFalse.StringProperty),
typeof(ClassWithDataMemberIsRequiredFalse)); typeof(ClassWithDataMemberIsRequiredFalse));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
@ -98,8 +98,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
var provider = new DataMemberRequiredBindingMetadataProvider(); var provider = new DataMemberRequiredBindingMetadataProvider();
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ClassWithoutAttributes).GetProperty(nameof(ClassWithoutAttributes.StringProperty)),
typeof(string), typeof(string),
nameof(ClassWithoutAttributes.StringProperty),
typeof(ClassWithoutAttributes)); typeof(ClassWithoutAttributes));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], new object[0])); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], new object[0]));
@ -126,8 +126,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
}; };
var key = ModelMetadataIdentity.ForProperty( var key = ModelMetadataIdentity.ForProperty(
typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract).GetProperty(nameof(ClassWithDataMemberIsRequiredTrueWithoutDataContract.StringProperty)),
typeof(string), typeof(string),
nameof(ClassWithDataMemberIsRequiredTrueWithoutDataContract.StringProperty),
typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract)); typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));

View File

@ -196,8 +196,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
} }
} }
if (!(exception is JsonException || exception is OverflowException)) if (!(exception is JsonException || exception is OverflowException || exception is FormatException))
{ {
// At this point we've already recorded all exceptions as an entry in the ModelStateDictionary.
// We only need to rethrow an exception if we believe it needs to be handled by something further up
// the stack.
// JsonException, OverflowException, and FormatException are assumed to be only encountered when
// parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else
// needs to be rethrown.
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
exceptionDispatchInfo.Throw(); exceptionDispatchInfo.Throw();
} }

View File

@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using Xunit; using Xunit;
@ -21,8 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
public class NewtonsoftJsonInputFormatterTest : JsonInputFormatterTestBase public class NewtonsoftJsonInputFormatterTest : JsonInputFormatterTestBase
{ {
private static readonly ObjectPoolProvider _objectPoolProvider = new DefaultObjectPoolProvider(); private readonly ObjectPoolProvider _objectPoolProvider = new DefaultObjectPoolProvider();
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings(); private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings();
[Fact] [Fact]
public async Task Constructor_BuffersRequestBody_UsingDefaultOptions() public async Task Constructor_BuffersRequestBody_UsingDefaultOptions()
@ -144,7 +145,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var serializerSettings = new JsonSerializerSettings(); var serializerSettings = new JsonSerializerSettings();
// Act // Act
var formatter = new TestableJsonInputFormatter(serializerSettings); var formatter = new TestableJsonInputFormatter(serializerSettings, _objectPoolProvider);
// Assert // Assert
Assert.Same(serializerSettings, formatter.SerializerSettings); Assert.Same(serializerSettings, formatter.SerializerSettings);
@ -185,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
MaxDepth = 2, MaxDepth = 2,
DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind, DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
}; };
var formatter = new TestableJsonInputFormatter(settings); var formatter = new TestableJsonInputFormatter(settings, _objectPoolProvider);
// Act // Act
var actual = formatter.CreateJsonSerializer(null); var actual = formatter.CreateJsonSerializer(null);
@ -304,7 +305,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
} }
[Fact] [Fact]
public async Task ReadAsync_lowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions() public async Task ReadAsync_AllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
{ {
// Arrange // Arrange
var formatter = new NewtonsoftJsonInputFormatter( var formatter = new NewtonsoftJsonInputFormatter(
@ -336,10 +337,72 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.NotEmpty(modelError.ErrorMessage); Assert.NotEmpty(modelError.ErrorMessage);
} }
[Fact]
public async Task ReadAsync_DoesNotRethrowFormatExceptions()
{
// Arrange
_serializerSettings.Converters.Add(new IsoDateTimeConverter());
var formatter = new NewtonsoftJsonInputFormatter(
GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions(),
new MvcNewtonsoftJsonOptions());
var contentBytes = Encoding.UTF8.GetBytes("{\"dateValue\":\"not-a-date\"}");
var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(TypeWithPrimitives), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.False(formatterContext.ModelState.IsValid);
var modelError = Assert.Single(formatterContext.ModelState["dateValue"].Errors);
Assert.Null(modelError.Exception);
Assert.Equal("The supplied value is invalid.", modelError.ErrorMessage);
}
[Fact]
public async Task ReadAsync_DoesNotRethrowOverflowExceptions()
{
// Arrange
_serializerSettings.Converters.Add(new IsoDateTimeConverter());
var formatter = new NewtonsoftJsonInputFormatter(
GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions(),
new MvcNewtonsoftJsonOptions());
var contentBytes = Encoding.UTF8.GetBytes("{\"shortValue\":\"32768\"}");
var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(TypeWithPrimitives), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.False(formatterContext.ModelState.IsValid);
var modelError = Assert.Single(formatterContext.ModelState["shortValue"].Errors);
Assert.Null(modelError.Exception);
Assert.Equal("The supplied value is invalid.", modelError.ErrorMessage);
}
private class TestableJsonInputFormatter : NewtonsoftJsonInputFormatter private class TestableJsonInputFormatter : NewtonsoftJsonInputFormatter
{ {
public TestableJsonInputFormatter(JsonSerializerSettings settings) public TestableJsonInputFormatter(JsonSerializerSettings settings, ObjectPoolProvider objectPoolProvider)
: base(GetLogger(), settings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions(), new MvcNewtonsoftJsonOptions()) : base(GetLogger(), settings, ArrayPool<char>.Shared, objectPoolProvider, new MvcOptions(), new MvcNewtonsoftJsonOptions())
{ {
} }
@ -418,5 +481,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public string Password { get; set; } public string Password { get; set; }
} }
public class TypeWithPrimitives
{
public DateTime DateValue { get; set; }
[JsonConverter(typeof(IncorrectShortConverter))]
public short ShortValue { get; set; }
}
private class IncorrectShortConverter : JsonConverter<short>
{
public override short ReadJson(JsonReader reader, Type objectType, short existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return short.Parse(reader.Value.ToString());
}
public override void WriteJson(JsonWriter writer, short value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
} }
} }

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var property = containerType.GetRuntimeProperty(propertyName); var property = containerType.GetRuntimeProperty(propertyName);
Assert.NotNull(property); Assert.NotNull(property);
var key = ModelMetadataIdentity.ForProperty(property.PropertyType, propertyName, containerType); var key = ModelMetadataIdentity.ForProperty(property, property.PropertyType, containerType);
var builder = new MetadataBuilder(key); var builder = new MetadataBuilder(key);
_detailsProvider.Builders.Add(builder); _detailsProvider.Builders.Add(builder);

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="main"> <div class="main">
<div class="top-row px-4"> <div class="top-row px-4 auth">
<LoginDisplay /> <LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div> </div>

View File

@ -1,4 +1,5 @@
{ {
"DetailedErrors": true,
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",

View File

@ -36,10 +36,16 @@ app {
justify-content: flex-end; justify-content: flex-end;
} }
.main .top-row > a { .main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem; margin-left: 1.5rem;
} }
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar { .sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
} }
@ -59,20 +65,20 @@ app {
top: -2px; top: -2px;
} }
.nav-item { .sidebar .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .sidebar .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .sidebar .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item a { .sidebar .nav-item a {
color: #d7d7d7; color: #d7d7d7;
border-radius: 4px; border-radius: 4px;
height: 3rem; height: 3rem;
@ -81,12 +87,12 @@ app {
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255,255,255,0.25);
color: white; color: white;
} }
.nav-item a:hover { .sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
color: white; color: white;
} }
@ -131,9 +137,17 @@ app {
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.main .top-row { .main .top-row:not(.auth) {
display: none; display: none;
} }
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {

View File

@ -10,10 +10,10 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
public static readonly string DisplayName = "Facebook"; public static readonly string DisplayName = "Facebook";
// https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login // https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login
public static readonly string AuthorizationEndpoint = "https://www.facebook.com/v3.3/dialog/oauth"; public static readonly string AuthorizationEndpoint = "https://www.facebook.com/v4.0/dialog/oauth";
public static readonly string TokenEndpoint = "https://graph.facebook.com/v3.3/oauth/access_token"; public static readonly string TokenEndpoint = "https://graph.facebook.com/v4.0/oauth/access_token";
public static readonly string UserInformationEndpoint = "https://graph.facebook.com/v3.3/me"; public static readonly string UserInformationEndpoint = "https://graph.facebook.com/v4.0/me";
} }
} }

View File

@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
var transaction = await server.SendAsync("http://example.com/base/login"); var transaction = await server.SendAsync("http://example.com/base/login");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
var location = transaction.Response.Headers.Location.AbsoluteUri; var location = transaction.Response.Headers.Location.AbsoluteUri;
Assert.Contains("https://www.facebook.com/v3.3/dialog/oauth", location); Assert.Contains("https://www.facebook.com/v4.0/dialog/oauth", location);
Assert.Contains("response_type=code", location); Assert.Contains("response_type=code", location);
Assert.Contains("client_id=", location); Assert.Contains("client_id=", location);
Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/base/signin-facebook"), location); Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/base/signin-facebook"), location);
@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
var transaction = await server.SendAsync("http://example.com/login"); var transaction = await server.SendAsync("http://example.com/login");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
var location = transaction.Response.Headers.Location.AbsoluteUri; var location = transaction.Response.Headers.Location.AbsoluteUri;
Assert.Contains("https://www.facebook.com/v3.3/dialog/oauth", location); Assert.Contains("https://www.facebook.com/v4.0/dialog/oauth", location);
Assert.Contains("response_type=code", location); Assert.Contains("response_type=code", location);
Assert.Contains("client_id=", location); Assert.Contains("client_id=", location);
Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/signin-facebook"), location); Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/signin-facebook"), location);
@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
var transaction = await server.SendAsync("http://example.com/challenge"); var transaction = await server.SendAsync("http://example.com/challenge");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
var location = transaction.Response.Headers.Location.AbsoluteUri; var location = transaction.Response.Headers.Location.AbsoluteUri;
Assert.Contains("https://www.facebook.com/v3.3/dialog/oauth", location); Assert.Contains("https://www.facebook.com/v4.0/dialog/oauth", location);
Assert.Contains("response_type=code", location); Assert.Contains("response_type=code", location);
Assert.Contains("client_id=", location); Assert.Contains("client_id=", location);
Assert.Contains("redirect_uri=", location); Assert.Contains("redirect_uri=", location);

View File

@ -76,9 +76,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.False(https); Assert.False(https);
} }
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win8, WindowsVersions.Win81, WindowsVersions.Win2008R2, SkipReason = "UnixDomainSocketEndPoint is not supported on older versions of Windows")]
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/14382", Queues = "Windows.10.Amd64.Open")] [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/14382", Queues = "Windows.10.Amd64.Open")]
[ConditionalFact] [ConditionalFact]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
public void ParseAddressUnixPipe() public void ParseAddressUnixPipe()
{ {
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https); var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https);

View File

@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
// [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016 // [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1 [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1
[OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win8, WindowsVersions.Win81)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)]
public void DefaultConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected) public void DefaultConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected)
=> DefaultConfigSectionCanSetProtocols(input, expected); => DefaultConfigSectionCanSetProtocols(input, expected);
@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
[InlineData("http2", HttpProtocols.Http2)] [InlineData("http2", HttpProtocols.Http2)]
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7)] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
public void DefaultConfigSectionCanSetProtocols_NonMacAndWin7(string input, HttpProtocols expected) public void DefaultConfigSectionCanSetProtocols_NonMacAndWin7(string input, HttpProtocols expected)
=> DefaultConfigSectionCanSetProtocols(input, expected); => DefaultConfigSectionCanSetProtocols(input, expected);
@ -389,7 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
// [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016 // [InlineData("http2", HttpProtocols.Http2)] // Not supported due to missing ALPN support. https://github.com/dotnet/corefx/issues/33016
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1 [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] // Gracefully falls back to HTTP/1
[OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win8, WindowsVersions.Win81)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)]
public void EndpointConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected) => public void EndpointConfigSectionCanSetProtocols_MacAndWin7(string input, HttpProtocols expected) =>
EndpointConfigSectionCanSetProtocols(input, expected); EndpointConfigSectionCanSetProtocols(input, expected);
@ -398,7 +398,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
[InlineData("http2", HttpProtocols.Http2)] [InlineData("http2", HttpProtocols.Http2)]
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7)] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
public void EndpointConfigSectionCanSetProtocols_NonMacAndWin7(string input, HttpProtocols expected) => public void EndpointConfigSectionCanSetProtocols_NonMacAndWin7(string input, HttpProtocols expected) =>
EndpointConfigSectionCanSetProtocols(input, expected); EndpointConfigSectionCanSetProtocols(input, expected);

View File

@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
[ConditionalFact] [ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win8, WindowsVersions.Win81)] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win10, WindowsVersions.Win81)]
// Win7 SslStream is missing ALPN support. // Win7 SslStream is missing ALPN support.
public void TlsAndHttp2NotSupportedOnWin7() public void TlsAndHttp2NotSupportedOnWin7()
{ {

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
[CollectDump] [CollectDump]
[ConditionalFact] [ConditionalFact]
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/9985", Queues = "Fedora.28.Amd64.Open")] // https://github.com/aspnet/AspNetCore/issues/9985 [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/9985", Queues = "Fedora.28.Amd64.Open")]
[Flaky("https://github.com/aspnet/AspNetCore/issues/9985", FlakyOn.All)] [Flaky("https://github.com/aspnet/AspNetCore/issues/9985", FlakyOn.All)]
public async Task GracefulShutdownWaitsForRequestsToFinish() public async Task GracefulShutdownWaitsForRequestsToFinish()
{ {

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
#if LIBUV #if LIBUV
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "Libuv does not support unix domain sockets on Windows.")] [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Libuv does not support unix domain sockets on Windows.")]
#else #else
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win8, WindowsVersions.Win81, WindowsVersions.Win2008R2, SkipReason = "UnixDomainSocketEndPoint is not supported on older versions of Windows")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
#endif #endif
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/14382", Queues = "Windows.10.Amd64.Open")] [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/14382", Queues = "Windows.10.Amd64.Open")]
[ConditionalFact] [ConditionalFact]

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using Xunit.Sdk; using Xunit.Sdk;
namespace OpenQA.Selenium namespace OpenQA.Selenium
@ -13,15 +14,31 @@ namespace OpenQA.Selenium
// case. // case.
public class BrowserAssertFailedException : XunitException public class BrowserAssertFailedException : XunitException
{ {
public BrowserAssertFailedException(IReadOnlyList<LogEntry> logs, Exception innerException, string screenShotPath) public BrowserAssertFailedException(IReadOnlyList<LogEntry> logs, Exception innerException, string screenShotPath, string innerHTML)
: base(BuildMessage(innerException, logs, screenShotPath), innerException) : base(BuildMessage(innerException, logs, screenShotPath, innerHTML), innerException)
{ {
} }
private static string BuildMessage(Exception innerException, IReadOnlyList<LogEntry> logs, string screenShotPath) => private static string BuildMessage(Exception exception, IReadOnlyList<LogEntry> logs, string screenShotPath, string innerHTML)
innerException.ToString() + Environment.NewLine + {
(File.Exists(screenShotPath) ? $"Screen shot captured at '{screenShotPath}'" + Environment.NewLine : "") + var builder = new StringBuilder();
(logs.Count > 0 ? "Encountered browser logs" : "No browser logs found") + " while running the assertion." + Environment.NewLine + builder.AppendLine(exception.ToString());
string.Join(Environment.NewLine, logs);
if (File.Exists(screenShotPath))
{
builder.AppendLine($"Screen shot captured at '{screenShotPath}'");
}
if (logs.Count > 0)
{
builder.AppendLine("Encountered browser errors")
.AppendJoin(Environment.NewLine, logs);
}
builder.AppendLine("Page content:")
.AppendLine(innerHTML);
return builder.ToString();
}
} }
} }

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