diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs
index 0ece2b91de..e0628dc1bb 100644
--- a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs
+++ b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs
@@ -51,6 +51,20 @@ namespace Microsoft.AspNetCore.Routing
}
}
+ ///
+ /// Creates a new instance with the specified values.
+ ///
+ /// The values.
+ public RouteData(RouteValueDictionary values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ _values = values;
+ }
+
///
/// Gets the data tokens produced by routes on the current routing path.
///
@@ -84,7 +98,7 @@ namespace Microsoft.AspNetCore.Routing
}
///
- /// Gets the set of values produced by routes on the current routing path.
+ /// Gets the values produced by routes on the current routing path.
///
public RouteValueDictionary Values
{
diff --git a/src/Microsoft.AspNetCore.Routing/DispatcherMiddleware.cs b/src/Microsoft.AspNetCore.Routing/DispatcherMiddleware.cs
index cccebfe7b1..6dd9cbed16 100644
--- a/src/Microsoft.AspNetCore.Routing/DispatcherMiddleware.cs
+++ b/src/Microsoft.AspNetCore.Routing/DispatcherMiddleware.cs
@@ -57,6 +57,9 @@ namespace Microsoft.AspNetCore.Routing
var feature = new EndpointFeature();
httpContext.Features.Set(feature);
+ // Back compat support for users of IRoutingFeature
+ httpContext.Features.Set(feature);
+
// There's an inherent race condition between waiting for init and accessing the matcher
// this is OK because once `_matcher` is initialized, it will not be set to null again.
var matcher = await InitializeAsync();
diff --git a/src/Microsoft.AspNetCore.Routing/EndpointFeature.cs b/src/Microsoft.AspNetCore.Routing/EndpointFeature.cs
index 62d943ca84..99e5709867 100644
--- a/src/Microsoft.AspNetCore.Routing/EndpointFeature.cs
+++ b/src/Microsoft.AspNetCore.Routing/EndpointFeature.cs
@@ -6,12 +6,39 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing
{
- public sealed class EndpointFeature : IEndpointFeature
+ public sealed class EndpointFeature : IEndpointFeature, IRoutingFeature
{
+ private RouteData _routeData;
+ private RouteValueDictionary _values;
+
public Endpoint Endpoint { get; set; }
public Func Invoker { get; set; }
- public RouteValueDictionary Values { get; set; }
+ public RouteValueDictionary Values
+ {
+ get => _values;
+ set
+ {
+ _values = value;
+
+ // RouteData will be created next get with new Values
+ _routeData = null;
+ }
+ }
+
+ RouteData IRoutingFeature.RouteData
+ {
+ get
+ {
+ if (_routeData == null)
+ {
+ _routeData = new RouteData(_values);
+ }
+
+ return _routeData;
+ }
+ set => throw new NotSupportedException();
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DispatcherMiddlewareTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DispatcherMiddlewareTest.cs
index 4f5b72de8d..26fb753c38 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/DispatcherMiddlewareTest.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/DispatcherMiddlewareTest.cs
@@ -58,6 +58,30 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal(expectedMessage, write.State?.ToString());
}
+ [Fact]
+ public async Task Invoke_BackCompatGetRouteValue_ValueUsedFromEndpointFeature()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new TestServiceProvider();
+
+ var middleware = CreateMiddleware();
+
+ // Act
+ await middleware.Invoke(httpContext);
+ var routeData = httpContext.GetRouteData();
+ var routeValue = httpContext.GetRouteValue("controller");
+ var endpointFeature = httpContext.Features.Get();
+
+ // Assert
+ Assert.NotNull(routeData);
+ Assert.Equal("Home", (string)routeValue);
+
+ // changing route data value is reflected in endpoint feature values
+ routeData.Values["testKey"] = "testValue";
+ Assert.Equal("testValue", endpointFeature.Values["testKey"]);
+ }
+
private DispatcherMiddleware CreateMiddleware(Logger logger = null)
{
RequestDelegate next = (c) => Task.FromResult