diff --git a/src/Http/Http.Abstractions/src/HttpContext.cs b/src/Http/Http.Abstractions/src/HttpContext.cs
index 6aa00a5b7ff2..4842446b33be 100644
--- a/src/Http/Http.Abstractions/src/HttpContext.cs
+++ b/src/Http/Http.Abstractions/src/HttpContext.cs
@@ -72,6 +72,15 @@ public abstract class HttpContext
///
public abstract ISession Session { get; set; }
+ ///
+ /// Gets or sets the for the current request.
+ ///
+ ///
+ /// The endpoint for a request is typically set by routing middleware. A request might not have
+ /// an endpoint if routing middleware hasn't run yet, or the request didn't match a route.
+ ///
+ public abstract Endpoint? Endpoint { get; set; }
+
///
/// Aborts the connection underlying this request.
///
@@ -90,7 +99,7 @@ private sealed class HttpContextDebugView(HttpContext context)
public HttpContextFeatureDebugView Features => new HttpContextFeatureDebugView(_context.Features);
public HttpRequest Request => _context.Request;
public HttpResponse Response => _context.Response;
- public Endpoint? Endpoint => _context.GetEndpoint();
+ public Endpoint? Endpoint => _context.Endpoint;
public ConnectionInfo Connection => _context.Connection;
public WebSocketManager WebSockets => _context.WebSockets;
public ClaimsPrincipal User => _context.User;
diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
index 6ccd592237b8..2aa2ecebeabb 100644
--- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
+abstract Microsoft.AspNetCore.Http.HttpContext.Endpoint.get -> Microsoft.AspNetCore.Http.Endpoint?
+abstract Microsoft.AspNetCore.Http.HttpContext.Endpoint.set -> void
Microsoft.AspNetCore.Http.Metadata.IAllowCookieRedirectMetadata
Microsoft.AspNetCore.Http.Metadata.IDisableCookieRedirectMetadata
Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata
diff --git a/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs b/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
index 5c953912a379..f0edc35369aa 100644
--- a/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
@@ -142,6 +142,72 @@ public void SetAndGetEndpoint_Roundtrip_EndpointIsRoundtrip()
Assert.Equal(initialEndpoint, endpoint);
}
+ [Fact]
+ public void EndpointProperty_Get_ReturnsNullByDefault()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+
+ // Act
+ var endpoint = context.Endpoint;
+
+ // Assert
+ Assert.Null(endpoint);
+ }
+
+ [Fact]
+ public void EndpointProperty_SetAndGet_Roundtrip()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var testEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+
+ // Act
+ context.Endpoint = testEndpoint;
+ var retrievedEndpoint = context.Endpoint;
+
+ // Assert
+ Assert.Equal(testEndpoint, retrievedEndpoint);
+ }
+
+ [Fact]
+ public void EndpointProperty_SetNull_ClearsEndpoint()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var testEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ context.Endpoint = testEndpoint;
+
+ // Act
+ context.Endpoint = null;
+
+ // Assert
+ Assert.Null(context.Endpoint);
+ }
+
+ [Fact]
+ public void EndpointProperty_ConsistentWithExtensionMethods()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var testEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+
+ // Act - Set via property
+ context.Endpoint = testEndpoint;
+
+ // Assert - Both property and extension method return the same value
+ Assert.Equal(testEndpoint, context.Endpoint);
+ Assert.Equal(testEndpoint, context.GetEndpoint());
+
+ // Act - Set via extension method
+ var newEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "New endpoint");
+ context.SetEndpoint(newEndpoint);
+
+ // Assert - Both property and extension method return the new value
+ Assert.Equal(newEndpoint, context.Endpoint);
+ Assert.Equal(newEndpoint, context.GetEndpoint());
+ }
+
private class EndpointFeature : IEndpointFeature
{
public Endpoint? Endpoint { get; set; }
diff --git a/src/Http/Http/src/DefaultHttpContext.cs b/src/Http/Http/src/DefaultHttpContext.cs
index 2439fc48efe9..5a4092407764 100644
--- a/src/Http/Http/src/DefaultHttpContext.cs
+++ b/src/Http/Http/src/DefaultHttpContext.cs
@@ -213,6 +213,13 @@ public override ISession Session
}
}
+ ///
+ public override Endpoint? Endpoint
+ {
+ get { return this.GetEndpoint(); }
+ set { this.SetEndpoint(value); }
+ }
+
// This property exists because of backwards compatibility.
// We send an anonymous object with an HttpContext property
// via DiagnosticListener in various events throughout the pipeline. Instead