Refactor attribute route
This commit is contained in:
parent
064c01cf2b
commit
162c4709c1
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
|
@ -103,16 +102,16 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
Order = routeInfo.Order,
|
Order = routeInfo.Order,
|
||||||
Precedence = routeInfo.Precedence,
|
Precedence = routeInfo.Precedence,
|
||||||
Route = new TemplateRoute(
|
Target = _target,
|
||||||
_target,
|
RouteName = routeInfo.Name,
|
||||||
routeInfo.RouteTemplate,
|
RouteTemplate = routeInfo.RouteTemplate,
|
||||||
defaults: new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
TemplateMatcher = new TemplateMatcher(
|
||||||
|
routeInfo.ParsedTemplate,
|
||||||
|
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ AttributeRouting.RouteGroupKey, routeInfo.RouteGroup },
|
{ AttributeRouting.RouteGroupKey, routeInfo.RouteGroup }
|
||||||
},
|
}),
|
||||||
constraints: null,
|
Constraints = routeInfo.Constraints
|
||||||
dataTokens: null,
|
|
||||||
inlineConstraintResolver: _constraintResolver),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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.AspNet.Routing.Template;
|
||||||
|
using Microsoft.Framework.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Routing
|
namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -21,9 +24,14 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Precedence { get; set; }
|
public decimal Precedence { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
public IRouter Target { get; set; }
|
||||||
/// The <see cref="TemplateRoute"/>.
|
|
||||||
/// </summary>
|
public string RouteName { get; set; }
|
||||||
public TemplateRoute Route { get; set; }
|
|
||||||
|
public string RouteTemplate { get; set; }
|
||||||
|
|
||||||
|
public TemplateMatcher TemplateMatcher { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, IRouteConstraint> Constraints { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
private readonly IRouter _next;
|
private readonly IRouter _next;
|
||||||
private readonly LinkGenerationDecisionTree _linkGenerationTree;
|
private readonly LinkGenerationDecisionTree _linkGenerationTree;
|
||||||
private readonly TemplateRoute[] _matchingRoutes;
|
private readonly AttributeRouteMatchingEntry[] _matchingEntries;
|
||||||
private readonly IDictionary<string, AttributeRouteLinkGenerationEntry> _namedEntries;
|
private readonly IDictionary<string, AttributeRouteLinkGenerationEntry> _namedEntries;
|
||||||
|
|
||||||
private ILogger _logger;
|
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 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.
|
// 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)
|
.OrderBy(o => o.Order)
|
||||||
.ThenBy(e => e.Precedence)
|
.ThenBy(e => e.Precedence)
|
||||||
.ThenBy(e => e.Route.RouteTemplate, StringComparer.Ordinal)
|
.ThenBy(e => e.RouteTemplate, StringComparer.Ordinal)
|
||||||
.Select(e => e.Route)
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var namedEntries = new Dictionary<string, AttributeRouteLinkGenerationEntry>(
|
var namedEntries = new Dictionary<string, AttributeRouteLinkGenerationEntry>(
|
||||||
|
|
@ -101,20 +100,53 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task RouteAsync([NotNull] RouteContext context)
|
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 oldRouteData = context.RouteData;
|
||||||
|
|
||||||
var newRouteData = new RouteData(oldRouteData);
|
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
|
try
|
||||||
{
|
{
|
||||||
context.RouteData = newRouteData;
|
context.RouteData = newRouteData;
|
||||||
await route.RouteAsync(context);
|
|
||||||
|
await matchingEntry.Target.RouteAsync(context);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
// Restore the original values to prevent polluting the route data.
|
||||||
if (!context.IsHandled)
|
if (!context.IsHandled)
|
||||||
{
|
{
|
||||||
context.RouteData = oldRouteData;
|
context.RouteData = oldRouteData;
|
||||||
|
|
@ -261,6 +293,19 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return new VirtualPathData(this, path);
|
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)
|
private bool ContextHasSameValue(VirtualPathContext context, string key, object value)
|
||||||
{
|
{
|
||||||
object providedValue;
|
object providedValue;
|
||||||
|
|
|
||||||
|
|
@ -1459,8 +1459,8 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
Assert.Equal("Index", context.RouteData.Values["action"]);
|
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||||
Assert.Single(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
|
Assert.Single(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
|
||||||
Assert.IsType<TemplateRoute>(context.RouteData.Routers[0]);
|
Assert.Equal(1, context.RouteData.Routers.Count);
|
||||||
Assert.Same(next.Object, context.RouteData.Routers[1]);
|
Assert.Equal(next.Object.GetType(), context.RouteData.Routers[0].GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1502,8 +1502,8 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
||||||
Assert.Empty(context.RouteData.Routers);
|
Assert.Empty(context.RouteData.Routers);
|
||||||
|
|
||||||
Assert.IsType<TemplateRoute>(nestedRouteData.Routers[0]);
|
Assert.Equal(1, nestedRouteData.Routers.Count);
|
||||||
Assert.Same(next.Object, nestedRouteData.Routers[1]);
|
Assert.Equal(next.Object.GetType(), nestedRouteData.Routers[0].GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1545,8 +1545,8 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
||||||
Assert.Empty(context.RouteData.Routers);
|
Assert.Empty(context.RouteData.Routers);
|
||||||
|
|
||||||
Assert.IsType<TemplateRoute>(nestedRouteData.Routers[0]);
|
Assert.Equal(1, nestedRouteData.Routers.Count);
|
||||||
Assert.Same(next.Object, nestedRouteData.Routers[1]);
|
Assert.Equal(next.Object.GetType(), nestedRouteData.Routers[0].GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RouteContext CreateRouteContext(string requestPath)
|
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)
|
private static AttributeRouteMatchingEntry CreateMatchingEntry(IRouter router, string template, int order)
|
||||||
{
|
{
|
||||||
var routeGroup = string.Format("{0}&&{1}", order, template);
|
var routeGroup = string.Format("{0}&&{1}", order, template);
|
||||||
|
|
||||||
var entry = new AttributeRouteMatchingEntry();
|
var entry = new AttributeRouteMatchingEntry();
|
||||||
entry.Route = new TemplateRoute(
|
entry.Target = router;
|
||||||
target: router,
|
entry.RouteTemplate = template;
|
||||||
routeTemplate: template,
|
var parsedRouteTemplate = TemplateParser.Parse(template);
|
||||||
defaults: new RouteValueDictionary(new { test_route_group = routeGroup }),
|
entry.TemplateMatcher = new TemplateMatcher(
|
||||||
constraints: null,
|
parsedRouteTemplate,
|
||||||
dataTokens: null,
|
new RouteValueDictionary(new { test_route_group = routeGroup }));
|
||||||
inlineConstraintResolver: CreateConstraintResolver());
|
entry.Precedence = AttributeRoutePrecedence.Compute(parsedRouteTemplate);
|
||||||
|
|
||||||
var routeTemplate = TemplateParser.Parse(template);
|
|
||||||
entry.Precedence = AttributeRoutePrecedence.Compute(routeTemplate);
|
|
||||||
entry.Order = order;
|
entry.Order = order;
|
||||||
|
entry.Constraints = GetRouteConstriants(CreateConstraintResolver(), template, parsedRouteTemplate);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1661,10 +1657,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
It.IsAny<string>()))
|
It.IsAny<string>()))
|
||||||
.Returns(mockConstraint.Object);
|
.Returns(mockConstraint.Object);
|
||||||
|
|
||||||
var entry = new AttributeRouteMatchingEntry()
|
var entry = new AttributeRouteMatchingEntry();
|
||||||
{
|
entry.Target = new StubRouter();
|
||||||
Route = new TemplateRoute(new StubRouter(), template, mockConstraintResolver.Object)
|
entry.RouteTemplate = template;
|
||||||
};
|
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
@ -1728,7 +1723,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
version: 1);
|
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;
|
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
|
|
||||||
|
|
@ -1741,6 +1738,30 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
version: 1);
|
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
|
private class StubRouter : IRouter
|
||||||
{
|
{
|
||||||
public VirtualPathContext GenerationContext { get; set; }
|
public VirtualPathContext GenerationContext { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
typeof(RouteCollection).FullName,
|
typeof(RouteCollection).FullName,
|
||||||
typeof(AttributeRoute).FullName,
|
typeof(AttributeRoute).FullName,
|
||||||
typeof(TemplateRoute).FullName,
|
|
||||||
typeof(MvcRouteHandler).FullName,
|
typeof(MvcRouteHandler).FullName,
|
||||||
},
|
},
|
||||||
result.Routers);
|
result.Routers);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue