Skip to content

Commit e2f9c56

Browse files
committed
Merge branch 'main' into copilot/fix-62408
2 parents 44de796 + 5f21e7c commit e2f9c56

File tree

22 files changed

+1115
-111
lines changed

22 files changed

+1115
-111
lines changed

eng/Version.Details.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,17 +396,17 @@
396396
<Uri>https://github.com/dotnet/dotnet</Uri>
397397
<Sha>23101ed07a38169d3e59d63b9d4d33e6d1d609bb</Sha>
398398
</Dependency>
399-
<Dependency Name="Microsoft.Extensions.Caching.Hybrid" Version="9.8.0-preview.1.25358.1">
399+
<Dependency Name="Microsoft.Extensions.Caching.Hybrid" Version="9.8.0-preview.1.25361.1">
400400
<Uri>https://github.com/dotnet/extensions</Uri>
401-
<Sha>23c62b80989f7baf55dc394829895b636f960fdc</Sha>
401+
<Sha>cc0d010d068ddab0c5951b6cfc5793d379c0532c</Sha>
402402
</Dependency>
403-
<Dependency Name="Microsoft.Extensions.Diagnostics.Testing" Version="9.8.0-preview.1.25358.1">
403+
<Dependency Name="Microsoft.Extensions.Diagnostics.Testing" Version="9.8.0-preview.1.25361.1">
404404
<Uri>https://github.com/dotnet/extensions</Uri>
405-
<Sha>23c62b80989f7baf55dc394829895b636f960fdc</Sha>
405+
<Sha>cc0d010d068ddab0c5951b6cfc5793d379c0532c</Sha>
406406
</Dependency>
407-
<Dependency Name="Microsoft.Extensions.TimeProvider.Testing" Version="9.8.0-preview.1.25358.1">
407+
<Dependency Name="Microsoft.Extensions.TimeProvider.Testing" Version="9.8.0-preview.1.25361.1">
408408
<Uri>https://github.com/dotnet/extensions</Uri>
409-
<Sha>23c62b80989f7baf55dc394829895b636f960fdc</Sha>
409+
<Sha>cc0d010d068ddab0c5951b6cfc5793d379c0532c</Sha>
410410
</Dependency>
411411
<Dependency Name="NuGet.Frameworks" Version="6.2.4">
412412
<Uri>https://github.com/nuget/nuget.client</Uri>

eng/Versions.props

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@
143143
<SystemNumericsTensorsVersion>10.0.0-preview.7.25358.102</SystemNumericsTensorsVersion>
144144
<SystemRuntimeCachingVersion>10.0.0-preview.7.25358.102</SystemRuntimeCachingVersion>
145145
<!-- Packages from dotnet/extensions -->
146-
<MicrosoftExtensionsCachingHybridVersion>9.8.0-preview.1.25358.1</MicrosoftExtensionsCachingHybridVersion>
147-
<MicrosoftExtensionsDiagnosticsTestingVersion>9.8.0-preview.1.25358.1</MicrosoftExtensionsDiagnosticsTestingVersion>
148-
<MicrosoftExtensionsTimeProviderTestingVersion>9.8.0-preview.1.25358.1</MicrosoftExtensionsTimeProviderTestingVersion>
146+
<MicrosoftExtensionsCachingHybridVersion>9.8.0-preview.1.25361.1</MicrosoftExtensionsCachingHybridVersion>
147+
<MicrosoftExtensionsDiagnosticsTestingVersion>9.8.0-preview.1.25361.1</MicrosoftExtensionsDiagnosticsTestingVersion>
148+
<MicrosoftExtensionsTimeProviderTestingVersion>9.8.0-preview.1.25361.1</MicrosoftExtensionsTimeProviderTestingVersion>
149149
<!-- Packages from dotnet/efcore -->
150150
<dotnetefVersion>10.0.0-preview.7.25358.102</dotnetefVersion>
151151
<MicrosoftEntityFrameworkCoreInMemoryVersion>10.0.0-preview.7.25358.102</MicrosoftEntityFrameworkCoreInMemoryVersion>
@@ -325,8 +325,8 @@
325325
<XunitExtensibilityCoreVersion>$(XunitVersion)</XunitExtensibilityCoreVersion>
326326
<XunitExtensibilityExecutionVersion>$(XunitVersion)</XunitExtensibilityExecutionVersion>
327327
<MicrosoftDataSqlClientVersion>5.2.2</MicrosoftDataSqlClientVersion>
328-
<MicrosoftOpenApiVersion>2.0.0-preview.29</MicrosoftOpenApiVersion>
329-
<MicrosoftOpenApiYamlReaderVersion>2.0.0-preview.29</MicrosoftOpenApiYamlReaderVersion>
328+
<MicrosoftOpenApiVersion>2.0.0</MicrosoftOpenApiVersion>
329+
<MicrosoftOpenApiYamlReaderVersion>2.0.0</MicrosoftOpenApiYamlReaderVersion>
330330
<!-- dotnet tool versions (see also auto-updated DotnetEfVersion property). -->
331331
<DotnetDumpVersion>6.0.322601</DotnetDumpVersion>
332332
<DotnetServeVersion>1.10.93</DotnetServeVersion>

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#nullable enable
22
*REMOVED*Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.ResourceAssetProperty!>? properties) -> void
33
Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.ResourceAssetProperty!>? properties = null) -> void
4-
Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type!
4+
Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type?
55
Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void
66
Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions
77
Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler<Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs!>!

src/Components/Components/src/Routing/Router.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,15 @@ static readonly IReadOnlyDictionary<string, object> _emptyParametersDictionary
7070
/// Gets or sets the content to display when no match is found for the requested route.
7171
/// </summary>
7272
[Parameter]
73+
[Obsolete("NotFound is deprecated. Use NotFoundPage instead.")]
7374
public RenderFragment NotFound { get; set; }
7475

7576
/// <summary>
7677
/// Gets or sets the page content to display when no match is found for the requested route.
7778
/// </summary>
7879
[Parameter]
7980
[DynamicallyAccessedMembers(LinkerFlags.Component)]
80-
public Type NotFoundPage { get; set; } = default!;
81+
public Type? NotFoundPage { get; set; }
8182

8283
/// <summary>
8384
/// Gets or sets the content to display when a match is found for the requested route.
@@ -143,6 +144,12 @@ public async Task SetParametersAsync(ParameterView parameters)
143144

144145
if (NotFoundPage != null)
145146
{
147+
#pragma warning disable CS0618 // Type or member is obsolete
148+
if (NotFound != null)
149+
{
150+
throw new InvalidOperationException($"Setting {nameof(NotFound)} and {nameof(NotFoundPage)} properties simultaneously is not supported. Use either {nameof(NotFound)} or {nameof(NotFoundPage)}.");
151+
}
152+
#pragma warning restore CS0618 // Type or member is obsolete
146153
if (!typeof(IComponent).IsAssignableFrom(NotFoundPage))
147154
{
148155
throw new InvalidOperationException($"The type {NotFoundPage.FullName} " +
@@ -401,10 +408,12 @@ private void RenderNotFound()
401408
new RouteData(NotFoundPage, _emptyParametersDictionary));
402409
builder.CloseComponent();
403410
}
411+
#pragma warning disable CS0618 // Type or member is obsolete
404412
else if (NotFound != null)
405413
{
406414
NotFound(builder);
407415
}
416+
#pragma warning restore CS0618 // Type or member is obsolete
408417
else
409418
{
410419
DefaultNotFoundContent(builder);
@@ -429,6 +438,7 @@ async Task IHandleAfterRender.OnAfterRenderAsync()
429438

430439
private static partial class Log
431440
{
441+
#pragma warning disable CS0618 // Type or member is obsolete
432442
[LoggerMessage(1, LogLevel.Debug, $"Displaying {nameof(NotFound)} because path '{{Path}}' with base URI '{{BaseUri}}' does not match any component route", EventName = "DisplayingNotFound")]
433443
internal static partial void DisplayingNotFound(ILogger logger, string path, string baseUri);
434444

@@ -440,5 +450,6 @@ private static partial class Log
440450

441451
[LoggerMessage(4, LogLevel.Debug, $"Displaying {nameof(NotFound)} on request", EventName = "DisplayingNotFoundOnRequest")]
442452
internal static partial void DisplayingNotFound(ILogger logger);
453+
#pragma warning restore CS0618 // Type or member is obsolete
443454
}
444455
}

src/Components/Components/test/Routing/RouterTest.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
#pragma warning disable CS0618 // Type or member is obsolete
5+
46
using System.Reflection;
57
using Microsoft.AspNetCore.Components.RenderTree;
68
using Microsoft.AspNetCore.Components.Test.Helpers;
@@ -265,6 +267,40 @@ await _renderer.Dispatcher.InvokeAsync(() =>
265267
Assert.Equal("Not found", renderedFrame.TextContent);
266268
}
267269

270+
[Fact]
271+
public async Task ThrowsExceptionWhenBothNotFoundAndNotFoundPageAreSet()
272+
{
273+
// Arrange
274+
var services = new ServiceCollection();
275+
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
276+
services.AddSingleton<NavigationManager>(_navigationManager);
277+
services.AddSingleton<INavigationInterception, TestNavigationInterception>();
278+
services.AddSingleton<IScrollToLocationHash, TestScrollToLocationHash>();
279+
var serviceProvider = services.BuildServiceProvider();
280+
281+
var renderer = new TestRenderer(serviceProvider);
282+
renderer.ShouldHandleExceptions = true;
283+
var router = (Router)renderer.InstantiateComponent<Router>();
284+
router.AppAssembly = Assembly.GetExecutingAssembly();
285+
router.Found = routeData => (builder) => builder.AddContent(0, $"Rendering route matching {routeData.PageType}");
286+
renderer.AssignRootComponentId(router);
287+
288+
var parameters = new Dictionary<string, object>
289+
{
290+
{ nameof(Router.AppAssembly), typeof(RouterTest).Assembly },
291+
{ nameof(Router.NotFound), (RenderFragment)(builder => builder.AddContent(0, "Custom not found")) },
292+
{ nameof(Router.NotFoundPage), typeof(NotFoundTestComponent) }
293+
};
294+
295+
// Act & Assert
296+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
297+
await renderer.Dispatcher.InvokeAsync(() =>
298+
router.SetParametersAsync(ParameterView.FromDictionary(parameters))));
299+
300+
Assert.Contains("Setting NotFound and NotFoundPage properties simultaneously is not supported", exception.Message);
301+
Assert.Contains("Use either NotFound or NotFoundPage", exception.Message);
302+
}
303+
268304
internal class TestNavigationManager : NavigationManager
269305
{
270306
public TestNavigationManager() =>
@@ -306,4 +342,8 @@ public class MatchAnythingComponent : ComponentBase { }
306342

307343
[Route("a/b/c")]
308344
public class MultiSegmentRouteComponent : ComponentBase { }
345+
346+
[Route("not-found")]
347+
public class NotFoundTestComponent : ComponentBase { }
348+
309349
}

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,25 @@
3030
<HeadOutlet />
3131
</head>
3232
<body>
33-
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
34-
<Found Context="routeData">
35-
<RouteView RouteData="@routeData" />
36-
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
37-
</Found>
38-
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
39-
</Router>
33+
@if (NotFoundPageType != null)
34+
{
35+
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
36+
<Found Context="routeData">
37+
<RouteView RouteData="@routeData" />
38+
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
39+
</Found>
40+
</Router>
41+
}
42+
else
43+
{
44+
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }">
45+
<Found Context="routeData">
46+
<RouteView RouteData="@routeData" />
47+
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
48+
</Found>
49+
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
50+
</Router>
51+
}
4052
<script>
4153
// This script must come before blazor.web.js to test that
4254
// the framework does the right thing when an element is already focused.

src/Components/test/testassets/Components.WasmMinimal/Routes.razor

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,22 @@
2323
}
2424
}
2525

26-
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
27-
<Found Context="routeData">
28-
<RouteView RouteData="@routeData" />
29-
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
30-
</Found>
31-
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
32-
</Router>
26+
@if (NotFoundPageType != null)
27+
{
28+
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
29+
<Found Context="routeData">
30+
<RouteView RouteData="@routeData" />
31+
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
32+
</Found>
33+
</Router>
34+
}
35+
else
36+
{
37+
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }">
38+
<Found Context="routeData">
39+
<RouteView RouteData="@routeData" />
40+
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
41+
</Found>
42+
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
43+
</Router>
44+
}

src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ public class PublicProgramSourceGenerator : IIncrementalGenerator
1212
{
1313
private const string PublicPartialProgramClassSource = """
1414
// <auto-generated />
15-
#pragma warning disable CS1591
15+
/// <summary>
16+
/// Auto-generated public partial Program class for top-level statement apps.
17+
/// </summary>
1618
public partial class Program { }
17-
#pragma warning restore CS1591
1819
""";
1920

2021
public void Initialize(IncrementalGeneratorInitializationContext context)

src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ public class PublicTopLevelProgramGeneratorTests
99
{
1010
private const string ExpectedGeneratedSource = """
1111
// <auto-generated />
12-
#pragma warning disable CS1591
12+
/// <summary>
13+
/// Auto-generated public partial Program class for top-level statement apps.
14+
/// </summary>
1315
public partial class Program { }
14-
#pragma warning restore CS1591
1516
""";
1617

1718
[Fact]

src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,14 +405,28 @@ private static void AddSupportedResponseTypes(
405405
foreach (var metadata in responseMetadataTypes)
406406
{
407407
if (metadata.StatusCode == apiResponseType.StatusCode &&
408-
metadata.Type == apiResponseType.Type &&
408+
TypesAreCompatible(apiResponseType.Type, metadata.Type) &&
409409
metadata.Description is not null)
410410
{
411411
matchingDescription = metadata.Description;
412412
}
413413
}
414414
return matchingDescription;
415415
}
416+
417+
static bool TypesAreCompatible(Type? apiResponseType, Type? metadataType)
418+
{
419+
// We need to a special check for cases where the inferred type is different than the one specified in attributes.
420+
// For example, an endpoint that defines [ProducesResponseType<IEnumerable<WeatherForecast>>],
421+
// but the endpoint returns weatherForecasts.ToList(). Because List<> is a different type than IEnumerable<>, it would incorrectly set OpenAPI metadata incorrectly.
422+
// We use a conservative unidirectional check where the attribute type must be assignable from the inferred type.
423+
// This handles inheritance (BaseClass ← DerivedClass) and interface implementation (IEnumerable<T> ← List<T>).
424+
// This should be sufficient, as it's more common to specify an interface or base class type in the attribute and a concrete type in the endpoint implementation,
425+
// compared to doing the opposite.
426+
// For more information, check the related bug: https://github.com/dotnet/aspnetcore/issues/60518
427+
return apiResponseType == metadataType ||
428+
metadataType?.IsAssignableFrom(apiResponseType) == true;
429+
}
416430
}
417431

418432
private static ApiResponseType CreateDefaultApiResponseType(Type responseType)

0 commit comments

Comments
 (0)