diff --git a/src/Http/Http.Abstractions/src/Metadata/IStatusCodePagesMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IStatusCodePagesMetadata.cs new file mode 100644 index 000000000000..1594b3da76e0 --- /dev/null +++ b/src/Http/Http.Abstractions/src/Metadata/IStatusCodePagesMetadata.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Http.Metadata; + +/// +/// Defines a contract used to specify metadata for skipping the StatusCodePage +/// middleware in . +/// +public interface ISkipStatusCodePagesMetadata +{ +} diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt index 244ddbf827dc..945b4e263fab 100644 --- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable *REMOVED*abstract Microsoft.AspNetCore.Http.HttpResponse.ContentType.get -> string! abstract Microsoft.AspNetCore.Http.HttpResponse.ContentType.get -> string? +Microsoft.AspNetCore.Http.Metadata.ISkipStatusCodePagesMetadata diff --git a/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs b/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs index 8e47ff9ca221..0e91ccdbf84b 100644 --- a/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs +++ b/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Diagnostics; @@ -41,6 +42,13 @@ public async Task Invoke(HttpContext context) { var statusCodeFeature = new StatusCodePagesFeature(); context.Features.Set(statusCodeFeature); + var endpoint = context.GetEndpoint(); + var skipStatusCodePageMetadata = endpoint?.Metadata.GetMetadata(); + + if (skipStatusCodePageMetadata is not null) + { + statusCodeFeature.Enabled = false; + } await _next(context); diff --git a/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj b/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj index 8a7999f7eeb0..b0a4f171d50f 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj +++ b/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs index cc43acc02650..0bec738dab0d 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -283,4 +284,38 @@ public async Task Reexecute_WorksAfterUseRoutingWithGlobalRouteBuilder() var content = await response.Content.ReadAsStringAsync(); Assert.Equal("errorPage", content); } + + [Fact] + public async Task SkipStatusCodePages_SupportsEndpoints() + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseTestServer(); + await using var app = builder.Build(); + + app.UseRouting(); + + app.UseStatusCodePages(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", [SkipStatusCodePages] (c) => + { + c.Response.StatusCode = 404; + return Task.CompletedTask; + }); + }); + + app.Run((context) => + { + throw new InvalidOperationException("Invalid input provided."); + }); + + await app.StartAsync(); + + using var server = app.GetTestServer(); + var client = server.CreateClient(); + var response = await client.GetAsync("/"); + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/SkipStatusCodePagesAttribute.cs b/src/Mvc/Mvc.ViewFeatures/src/SkipStatusCodePagesAttribute.cs index 78cd16c7ca08..6ee2da2f59bf 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/SkipStatusCodePagesAttribute.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/SkipStatusCodePagesAttribute.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc.Filters; namespace Microsoft.AspNetCore.Mvc; @@ -11,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc; /// A filter that prevents execution of the StatusCodePages middleware. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] -public class SkipStatusCodePagesAttribute : Attribute, IResourceFilter +public class SkipStatusCodePagesAttribute : Attribute, IResourceFilter, ISkipStatusCodePagesMetadata { /// public void OnResourceExecuted(ResourceExecutedContext context)