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)