diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3acc059820..cb9cb3cdff 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -19,4 +19,4 @@
/src/Servers/ @tratcher @jkotalik @anurse @halter73
/src/Middleware/Rewrite @jkotalik @anurse
/src/Middleware/HttpsPolicy @jkotalik @anurse
-/src/SignalR/ @mikaelm12 @BrennanConroy @halter73 @anurse
+/src/SignalR/ @BrennanConroy @halter73 @anurse
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index 4210405c67..3ac3e8dbf6 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -15,6 +15,7 @@
+
diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs
index 7a1edc2d8c..533cd99399 100644
--- a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs
+++ b/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -89,19 +91,26 @@ namespace Microsoft.AspNetCore.Builder
}
catch (Exception ex)
{
-
await context.Response.WriteAsync($@"
-
Unable to find debuggable browser tab
-
- Could not get a list of browser tabs from {debuggerTabsListUrl}.
- Ensure Chrome is running with debugging enabled.
-
- Resolution
+Unable to find debuggable browser tab
+
+ Could not get a list of browser tabs from {debuggerTabsListUrl}.
+ Ensure your browser is running with debugging enabled.
+
+Resolution
+
+
If you are using Google Chrome for your development, follow these instructions:
{GetLaunchChromeInstructions(appRootUrl)}
- ... then use that new tab for debugging.
- Underlying exception:
- {ex}
-");
+
+
+
If you are using Microsoft Edge (Chromium) for your development, follow these instructions:
+ {GetLaunchEdgeInstructions(appRootUrl)}
+
+This should launch a new browser window with debugging enabled..
+Underlying exception:
+{ex}
+ ");
+
return;
}
@@ -144,20 +153,42 @@ namespace Microsoft.AspNetCore.Builder
private static string GetLaunchChromeInstructions(string appRootUrl)
{
+ var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug");
+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- return $@"Close all Chrome instances, then press Win+R and enter the following:
- ""%programfiles(x86)%\Google\Chrome\Application\chrome.exe"" --remote-debugging-port=9222 {appRootUrl}
";
+ return $@"Press Win+R and enter the following:
+ chrome --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}
";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- return $@"Close all Chrome instances, then in a terminal window execute the following:
- google-chrome --remote-debugging-port=9222 {appRootUrl}
";
+ return $@"In a terminal window execute the following:
+ google-chrome --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}
";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- return $@"Close all Chrome instances, then in a terminal window execute the following:
- open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 {appRootUrl}
";
+ return $@"Execute the following:
+ open /Applications/Google\ Chrome.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}
";
+ }
+ 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 $@"Press Win+R and enter the following:
+ msedge --remote-debugging-port=9222 --user-data-dir=""{profilePath}"" {appRootUrl}
";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return $@"In a terminal window execute the following:
+ open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port=9222 --user-data-dir={profilePath} {appRootUrl}
";
}
else
{
diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
index 168aafe542..2b7b716c20 100644
--- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
+++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
@@ -4,7 +4,7 @@
Please take our
- brief survey
+ brief survey
and tell us what you think.
diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css
index 22f9ecb710..4e4425c9b3 100644
--- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css
+++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css
@@ -4,6 +4,16 @@ html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
+a, .btn-link {
+ color: #0366d6;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
app {
position: relative;
display: flex;
@@ -21,10 +31,21 @@ app {
}
.main .top-row {
- background-color: #e6e6e6;
+ background-color: #f7f7f7;
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 {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
@@ -44,38 +65,38 @@ app {
top: -2px;
}
-.nav-item {
- font-size: 0.9rem;
- padding-bottom: 0.5rem;
-}
-
- .nav-item:first-of-type {
- padding-top: 1rem;
+ .sidebar .nav-item {
+ font-size: 0.9rem;
+ padding-bottom: 0.5rem;
}
- .nav-item:last-of-type {
- padding-bottom: 1rem;
- }
-
- .nav-item a {
- color: #d7d7d7;
- border-radius: 4px;
- height: 3rem;
- display: flex;
- align-items: center;
- line-height: 3rem;
- }
-
- .nav-item a.active {
- background-color: rgba(255,255,255,0.25);
- color: white;
+ .sidebar .nav-item:first-of-type {
+ padding-top: 1rem;
}
- .nav-item a:hover {
- background-color: rgba(255,255,255,0.1);
- color: white;
+ .sidebar .nav-item:last-of-type {
+ padding-bottom: 1rem;
}
+ .sidebar .nav-item a {
+ color: #d7d7d7;
+ border-radius: 4px;
+ height: 3rem;
+ display: flex;
+ align-items: center;
+ line-height: 3rem;
+ }
+
+ .sidebar .nav-item a.active {
+ background-color: rgba(255,255,255,0.25);
+ color: white;
+ }
+
+ .sidebar .nav-item a:hover {
+ background-color: rgba(255,255,255,0.1);
+ color: white;
+ }
+
.content {
padding-top: 1.1rem;
}
@@ -116,9 +137,17 @@ app {
}
@media (max-width: 767.98px) {
- .main .top-row {
+ .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;
+ }
}
@media (min-width: 768px) {
diff --git a/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs b/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs
new file mode 100644
index 0000000000..3f74ce647f
--- /dev/null
+++ b/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs
@@ -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
+{
+ ///
+ /// A that compares two properties
+ ///
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ public sealed class ComparePropertyAttribute : CompareAttribute
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The property to compare with the current property.
+ public ComparePropertyAttribute(string otherProperty)
+ : base(otherProperty)
+ {
+ }
+
+ ///
+ 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 });
+ }
+ }
+}
+
diff --git a/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj b/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj
new file mode 100644
index 0000000000..a166d5f1f3
--- /dev/null
+++ b/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ Provides experimental support for validation using DataAnnotations.
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs b/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs
new file mode 100644
index 0000000000..df1971e0a2
--- /dev/null
+++ b/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs
@@ -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