diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs index 77f24aa8a1..4381c859ca 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; @@ -103,16 +102,16 @@ namespace Microsoft.AspNet.Mvc.Routing { Order = routeInfo.Order, Precedence = routeInfo.Precedence, - Route = new TemplateRoute( - _target, - routeInfo.RouteTemplate, - defaults: new Dictionary(StringComparer.OrdinalIgnoreCase) + Target = _target, + RouteName = routeInfo.Name, + RouteTemplate = routeInfo.RouteTemplate, + TemplateMatcher = new TemplateMatcher( + routeInfo.ParsedTemplate, + new Dictionary(StringComparer.OrdinalIgnoreCase) { - { AttributeRouting.RouteGroupKey, routeInfo.RouteGroup }, - }, - constraints: null, - dataTokens: null, - inlineConstraintResolver: _constraintResolver), + { AttributeRouting.RouteGroupKey, routeInfo.RouteGroup } + }), + Constraints = routeInfo.Constraints }); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteMatchingEntry.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteMatchingEntry.cs index 5629183c58..2d64d00ffd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteMatchingEntry.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouteMatchingEntry.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; +using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Routing { @@ -21,9 +24,14 @@ namespace Microsoft.AspNet.Mvc.Routing /// public decimal Precedence { get; set; } - /// - /// The . - /// - public TemplateRoute Route { get; set; } + public IRouter Target { get; set; } + + public string RouteName { get; set; } + + public string RouteTemplate { get; set; } + + public TemplateMatcher TemplateMatcher { get; set; } + + public IReadOnlyDictionary Constraints { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/InnerAttributeRoute.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/InnerAttributeRoute.cs index 1506c30923..0bcc10d827 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/InnerAttributeRoute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/InnerAttributeRoute.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Routing { private readonly IRouter _next; private readonly LinkGenerationDecisionTree _linkGenerationTree; - private readonly TemplateRoute[] _matchingRoutes; + private readonly AttributeRouteMatchingEntry[] _matchingEntries; private readonly IDictionary _namedEntries; private ILogger _logger; @@ -51,11 +51,10 @@ namespace Microsoft.AspNet.Mvc.Routing // We use ordinal comparison for the templates because we only care about them being exactly equal and // we don't want to make any equivalence between templates based on the culture of the machine. - _matchingRoutes = matchingEntries + _matchingEntries = matchingEntries .OrderBy(o => o.Order) .ThenBy(e => e.Precedence) - .ThenBy(e => e.Route.RouteTemplate, StringComparer.Ordinal) - .Select(e => e.Route) + .ThenBy(e => e.RouteTemplate, StringComparer.Ordinal) .ToArray(); var namedEntries = new Dictionary( @@ -101,20 +100,53 @@ namespace Microsoft.AspNet.Mvc.Routing /// public async Task RouteAsync([NotNull] RouteContext context) { - foreach (var route in _matchingRoutes) + foreach(var matchingEntry in _matchingEntries) { + var requestPath = context.HttpContext.Request.Path.Value; + + if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') + { + requestPath = requestPath.Substring(1); + } + + var values = matchingEntry.TemplateMatcher.Match(requestPath); + if (values == null) + { + // If we got back a null value set, that means the URI did not match + continue; + } + var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); - newRouteData.Routers.Add(route); + newRouteData.Routers.Add(matchingEntry.Target); + MergeValues(newRouteData.Values, values); + + if (!RouteConstraintMatcher.Match( + matchingEntry.Constraints, + newRouteData.Values, + context.HttpContext, + this, + RouteDirection.IncomingRequest, + _constraintLogger)) + { + continue; + } + + _logger.LogInformation( + "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.", + matchingEntry.RouteName, + matchingEntry.RouteTemplate); try { context.RouteData = newRouteData; - await route.RouteAsync(context); + + await matchingEntry.Target.RouteAsync(context); } finally { + // Restore the original values to prevent polluting the route data. if (!context.IsHandled) { context.RouteData = oldRouteData; @@ -261,6 +293,19 @@ namespace Microsoft.AspNet.Mvc.Routing return new VirtualPathData(this, path); } + private static void MergeValues( + IDictionary destination, + IDictionary values) + { + foreach (var kvp in values) + { + // This will replace the original value for the specified key. + // Values from the matched route will take preference over previous + // data in the route context. + destination[kvp.Key] = kvp.Value; + } + } + private bool ContextHasSameValue(VirtualPathContext context, string key, object value) { object providedValue; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/InnerAttributeRouteTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/InnerAttributeRouteTest.cs index cfe6f70af6..69168b2c06 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/InnerAttributeRouteTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/InnerAttributeRouteTest.cs @@ -1459,8 +1459,8 @@ namespace Microsoft.AspNet.Mvc.Routing Assert.Equal("Index", context.RouteData.Values["action"]); Assert.Single(context.RouteData.Values, kvp => kvp.Key == "test_route_group"); - Assert.IsType(context.RouteData.Routers[0]); - Assert.Same(next.Object, context.RouteData.Routers[1]); + Assert.Equal(1, context.RouteData.Routers.Count); + Assert.Equal(next.Object.GetType(), context.RouteData.Routers[0].GetType()); } [Fact] @@ -1502,8 +1502,8 @@ namespace Microsoft.AspNet.Mvc.Routing Assert.Empty(context.RouteData.Routers); - Assert.IsType(nestedRouteData.Routers[0]); - Assert.Same(next.Object, nestedRouteData.Routers[1]); + Assert.Equal(1, nestedRouteData.Routers.Count); + Assert.Equal(next.Object.GetType(), nestedRouteData.Routers[0].GetType()); } [Fact] @@ -1545,8 +1545,8 @@ namespace Microsoft.AspNet.Mvc.Routing Assert.Empty(context.RouteData.Routers); - Assert.IsType(nestedRouteData.Routers[0]); - Assert.Same(next.Object, nestedRouteData.Routers[1]); + Assert.Equal(1, nestedRouteData.Routers.Count); + Assert.Equal(next.Object.GetType(), nestedRouteData.Routers[0].GetType()); } private static RouteContext CreateRouteContext(string requestPath) @@ -1582,20 +1582,16 @@ namespace Microsoft.AspNet.Mvc.Routing private static AttributeRouteMatchingEntry CreateMatchingEntry(IRouter router, string template, int order) { var routeGroup = string.Format("{0}&&{1}", order, template); - var entry = new AttributeRouteMatchingEntry(); - entry.Route = new TemplateRoute( - target: router, - routeTemplate: template, - defaults: new RouteValueDictionary(new { test_route_group = routeGroup }), - constraints: null, - dataTokens: null, - inlineConstraintResolver: CreateConstraintResolver()); - - var routeTemplate = TemplateParser.Parse(template); - entry.Precedence = AttributeRoutePrecedence.Compute(routeTemplate); + entry.Target = router; + entry.RouteTemplate = template; + var parsedRouteTemplate = TemplateParser.Parse(template); + entry.TemplateMatcher = new TemplateMatcher( + parsedRouteTemplate, + new RouteValueDictionary(new { test_route_group = routeGroup })); + entry.Precedence = AttributeRoutePrecedence.Compute(parsedRouteTemplate); entry.Order = order; - + entry.Constraints = GetRouteConstriants(CreateConstraintResolver(), template, parsedRouteTemplate); return entry; } @@ -1661,10 +1657,9 @@ namespace Microsoft.AspNet.Mvc.Routing It.IsAny())) .Returns(mockConstraint.Object); - var entry = new AttributeRouteMatchingEntry() - { - Route = new TemplateRoute(new StubRouter(), template, mockConstraintResolver.Object) - }; + var entry = new AttributeRouteMatchingEntry(); + entry.Target = new StubRouter(); + entry.RouteTemplate = template; return entry; } @@ -1728,7 +1723,9 @@ namespace Microsoft.AspNet.Mvc.Routing version: 1); } - private static InnerAttributeRoute CreateRoutingAttributeRoute(ILoggerFactory loggerFactory = null, params AttributeRouteMatchingEntry[] entries) + private static InnerAttributeRoute CreateRoutingAttributeRoute( + ILoggerFactory loggerFactory = null, + params AttributeRouteMatchingEntry[] entries) { loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; @@ -1741,6 +1738,30 @@ namespace Microsoft.AspNet.Mvc.Routing version: 1); } + private static IReadOnlyDictionary GetRouteConstriants( + IInlineConstraintResolver inlineConstraintResolver, + string template, + RouteTemplate parsedRouteTemplate) + { + var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, template); + foreach (var parameter in parsedRouteTemplate.Parameters) + { + if (parameter.InlineConstraints != null) + { + if (parameter.IsOptional) + { + constraintBuilder.SetOptional(parameter.Name); + } + + foreach (var constraint in parameter.InlineConstraints) + { + constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint); + } + } + } + + return constraintBuilder.Build(); + } private class StubRouter : IRouter { public VirtualPathContext GenerationContext { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RouteDataTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RouteDataTest.cs index b0705b0ba0..ee74275ae3 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RouteDataTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RouteDataTest.cs @@ -66,7 +66,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { typeof(RouteCollection).FullName, typeof(AttributeRoute).FullName, - typeof(TemplateRoute).FullName, typeof(MvcRouteHandler).FullName, }, result.Routers);