Refactor attribute route

This commit is contained in:
Kiran Challa 2015-04-20 14:05:56 -07:00
parent 064c01cf2b
commit 162c4709c1
5 changed files with 117 additions and 45 deletions

View File

@ -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<string, object>(StringComparer.OrdinalIgnoreCase)
Target = _target,
RouteName = routeInfo.Name,
RouteTemplate = routeInfo.RouteTemplate,
TemplateMatcher = new TemplateMatcher(
routeInfo.ParsedTemplate,
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ AttributeRouting.RouteGroupKey, routeInfo.RouteGroup },
},
constraints: null,
dataTokens: null,
inlineConstraintResolver: _constraintResolver),
{ AttributeRouting.RouteGroupKey, routeInfo.RouteGroup }
}),
Constraints = routeInfo.Constraints
});
}

View File

@ -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
/// </summary>
public decimal Precedence { get; set; }
/// <summary>
/// The <see cref="TemplateRoute"/>.
/// </summary>
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<string, IRouteConstraint> Constraints { get; set; }
}
}

View File

@ -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<string, AttributeRouteLinkGenerationEntry> _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<string, AttributeRouteLinkGenerationEntry>(
@ -101,20 +100,53 @@ namespace Microsoft.AspNet.Mvc.Routing
/// <inheritdoc />
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<string, object> destination,
IDictionary<string, object> 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;

View File

@ -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<TemplateRoute>(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<TemplateRoute>(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<TemplateRoute>(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<string>()))
.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<string, IRouteConstraint> 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; }

View File

@ -66,7 +66,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
{
typeof(RouteCollection).FullName,
typeof(AttributeRoute).FullName,
typeof(TemplateRoute).FullName,
typeof(MvcRouteHandler).FullName,
},
result.Routers);