From 5f75c07bbff7824275c1486c3cc03f0df3eef3af Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 2 Oct 2018 16:03:58 +1300 Subject: [PATCH] Add RoutesValues to HttpRequest (#1042) --- .../HttpRequest.cs | 7 +++ .../Features/RouteValuesFeature.cs | 34 ++++++++++++ .../Internal/DefaultHttpRequest.cs | 12 +++++ .../Internal/DefaultHttpRequestTests.cs | 52 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Http/Features/RouteValuesFeature.cs diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/HttpRequest.cs b/src/Microsoft.AspNetCore.Http.Abstractions/HttpRequest.cs index 3488bf3bb5..4c4d0d1af1 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/HttpRequest.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/HttpRequest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Http { @@ -117,5 +118,11 @@ namespace Microsoft.AspNetCore.Http /// /// public abstract Task ReadFormAsync(CancellationToken cancellationToken = new CancellationToken()); + + /// + /// Gets the collection of route values for this request. + /// + /// The collection of route values for this request. + public virtual RouteValueDictionary RouteValues { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Http/Features/RouteValuesFeature.cs b/src/Microsoft.AspNetCore.Http/Features/RouteValuesFeature.cs new file mode 100644 index 0000000000..e4a459e991 --- /dev/null +++ b/src/Microsoft.AspNetCore.Http/Features/RouteValuesFeature.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// A feature for routing values. Use + /// to access the values associated with the current request. + /// + public class RouteValuesFeature : IRouteValuesFeature + { + private RouteValueDictionary _routeValues; + + /// + /// Gets or sets the associated with the currrent + /// request. + /// + public RouteValueDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new RouteValueDictionary(); + } + + return _routeValues; + } + set => _routeValues = value; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs b/src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs index f216475db6..e2512f60dc 100644 --- a/src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http.Internal @@ -17,6 +18,7 @@ namespace Microsoft.AspNetCore.Http.Internal private readonly static Func _newQueryFeature = f => new QueryFeature(f); private readonly static Func _newFormFeature = r => new FormFeature(r); private readonly static Func _newRequestCookiesFeature = f => new RequestCookiesFeature(f); + private readonly static Func _newRouteValuesFeature = f => new RouteValuesFeature(); private HttpContext _context; private FeatureReferences _features; @@ -52,6 +54,9 @@ namespace Microsoft.AspNetCore.Http.Internal private IRequestCookiesFeature RequestCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature); + private IRouteValuesFeature RouteValuesFeature => + _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature); + public override PathString PathBase { get { return new PathString(HttpRequestFeature.PathBase); } @@ -151,12 +156,19 @@ namespace Microsoft.AspNetCore.Http.Internal return FormFeature.ReadFormAsync(cancellationToken); } + public override RouteValueDictionary RouteValues + { + get { return RouteValuesFeature.RouteValues; } + set { RouteValuesFeature.RouteValues = value; } + } + struct FeatureInterfaces { public IHttpRequestFeature Request; public IQueryFeature Query; public IFormFeature Form; public IRequestCookiesFeature Cookies; + public IRouteValuesFeature RouteValues; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Http.Tests/Internal/DefaultHttpRequestTests.cs b/test/Microsoft.AspNetCore.Http.Tests/Internal/DefaultHttpRequestTests.cs index dbe1d54dd0..09e47a962e 100644 --- a/test/Microsoft.AspNetCore.Http.Tests/Internal/DefaultHttpRequestTests.cs +++ b/test/Microsoft.AspNetCore.Http.Tests/Internal/DefaultHttpRequestTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using Xunit; @@ -194,6 +195,57 @@ namespace Microsoft.AspNetCore.Http.Internal Assert.Equal(new[] { "name2=value2" }, cookieHeaders); } + [Fact] + public void RouteValues_GetAndSet() + { + var context = new DefaultHttpContext(); + var request = context.Request; + + var routeValuesFeature = context.Features.Get(); + // No feature set for initial DefaultHttpRequest + Assert.Null(routeValuesFeature); + + // Route values returns empty collection by default + Assert.Empty(request.RouteValues); + + // Get and set value on request route values + request.RouteValues["new"] = "setvalue"; + Assert.Equal("setvalue", request.RouteValues["new"]); + + routeValuesFeature = context.Features.Get(); + // Accessing DefaultHttpRequest.RouteValues creates feature + Assert.NotNull(routeValuesFeature); + + request.RouteValues = new RouteValueDictionary(new { key = "value" }); + // Can set DefaultHttpRequest.RouteValues + Assert.NotNull(request.RouteValues); + Assert.Equal("value", request.RouteValues["key"]); + + // DefaultHttpRequest.RouteValues uses feature + Assert.Equal(routeValuesFeature.RouteValues, request.RouteValues); + + // Setting route values to null sets empty collection on request + routeValuesFeature.RouteValues = null; + Assert.Empty(request.RouteValues); + + var customRouteValuesFeature = new CustomRouteValuesFeature + { + RouteValues = new RouteValueDictionary(new { key = "customvalue" }) + }; + context.Features.Set(customRouteValuesFeature); + // Can override DefaultHttpRequest.RouteValues with custom feature + Assert.Equal(customRouteValuesFeature.RouteValues, request.RouteValues); + + // Can clear feature + context.Features.Set(null); + Assert.Empty(request.RouteValues); + } + + private class CustomRouteValuesFeature : IRouteValuesFeature + { + public RouteValueDictionary RouteValues { get; set; } + } + private static HttpRequest CreateRequest(IHeaderDictionary headers) { var context = new DefaultHttpContext();