Add IRouteHandler, RouteBase, and IRoutingFeature

Adds IRouterHandler, an abstraction for endpoints in the routing system
that can't chain (example: delegates). The idea is that some kinds of
routes aren't really friendly to chaining. If you don't support chaining,
then accept IRouteHandler and work with that rather than IRouter.

There's one implementation of IRouteHandler, RouteHandler. It implements
both IRouter and IRouteHandler.

Adds RouteBase as a base class for routes based on our template syntax and
defaults/constraints/data-tokens. Updated a lot of signatures to be
get/set virtual and mutable to facilitate or bigger variety of usage
scenarios.

Renamed TemplateRoute to just Route, now inherits from RouteBase.

Adds IRoutingFeature for middleware scenarios where you don't have access
to the route context.

Also adds some basic extension methods for accessing route values.
This commit is contained in:
Ryan Nowak 2015-12-09 16:22:05 -08:00
parent 411a59125c
commit 36180ab6d0
31 changed files with 615 additions and 785 deletions

View File

@ -1,31 +0,0 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNet.Routing;
namespace RoutingSample.Web
{
public class DelegateRouteEndpoint : IRouter
{
public delegate Task RoutedDelegate(RouteContext context);
private readonly RoutedDelegate _appFunc;
public DelegateRouteEndpoint(RoutedDelegate appFunc)
{
_appFunc = appFunc;
}
public async Task RouteAsync(RouteContext context)
{
await _appFunc(context);
context.IsHandled = true;
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return null;
}
}
}

View File

@ -1,18 +0,0 @@
// 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 System.Collections.Generic;
using System.Linq;
namespace RoutingSample.Web
{
public static class DictionaryExtensions
{
public static string Print(this IDictionary<string, object> routeValues)
{
var values = routeValues.Select(kvp => kvp.Key + ":" + kvp.Value.ToString());
return string.Join(" ", values);
}
}
}

View File

@ -7,12 +7,12 @@ using Microsoft.AspNet.Routing;
namespace RoutingSample.Web namespace RoutingSample.Web
{ {
internal class PrefixRoute : IRouter public class PrefixRoute : IRouter
{ {
private readonly IRouter _target; private readonly IRouteHandler _target;
private readonly string _prefix; private readonly string _prefix;
public PrefixRoute(IRouter target, string prefix) public PrefixRoute(IRouteHandler target, string prefix)
{ {
_target = target; _target = target;
@ -34,7 +34,7 @@ namespace RoutingSample.Web
_prefix = prefix; _prefix = prefix;
} }
public async Task RouteAsync(RouteContext context) public Task RouteAsync(RouteContext context)
{ {
var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty; var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;
if (requestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)) if (requestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase))
@ -44,12 +44,14 @@ namespace RoutingSample.Web
var lastCharacter = requestPath[_prefix.Length]; var lastCharacter = requestPath[_prefix.Length];
if (lastCharacter != '/' && lastCharacter != '#' && lastCharacter != '?') if (lastCharacter != '/' && lastCharacter != '#' && lastCharacter != '?')
{ {
return; return Task.FromResult(0);
} }
} }
await _target.RouteAsync(context); context.Handler = _target.GetRequestHandler(context.HttpContext, context.RouteData);
} }
return Task.FromResult(0);
} }
public VirtualPathData GetVirtualPath(VirtualPathContext context) public VirtualPathData GetVirtualPath(VirtualPathContext context)

View File

@ -1,34 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace RoutingSample.Web namespace RoutingSample.Web
{ {
public static class RouteBuilderExtensions public static class RouteBuilderExtensions
{ {
public static IRouteBuilder AddPrefixRoute(this IRouteBuilder routeBuilder, public static IRouteBuilder AddPrefixRoute(
string prefix) this IRouteBuilder routeBuilder,
{ string prefix,
if (routeBuilder.DefaultHandler == null) IRouteHandler handler)
{
throw new InvalidOperationException("DefaultHandler must be set.");
}
if (routeBuilder.ServiceProvider == null)
{
throw new InvalidOperationException("ServiceProvider must be set.");
}
return AddPrefixRoute(routeBuilder, prefix, routeBuilder.DefaultHandler);
}
public static IRouteBuilder AddPrefixRoute(this IRouteBuilder routeBuilder,
string prefix,
IRouter handler)
{ {
routeBuilder.Routes.Add(new PrefixRoute(handler, prefix)); routeBuilder.Routes.Add(new PrefixRoute(handler, prefix));
return routeBuilder; return routeBuilder;
@ -45,7 +28,7 @@ namespace RoutingSample.Web
var constraintResolver = routeBuilder.ServiceProvider.GetService<IInlineConstraintResolver>(); var constraintResolver = routeBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
var route = new TemplateRoute( var route = new Route(
target: routeBuilder.DefaultHandler, target: routeBuilder.DefaultHandler,
routeTemplate: routeTemplate, routeTemplate: routeTemplate,
defaults: defaultsDictionary, defaults: defaultsDictionary,

View File

@ -1,12 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text.RegularExpressions;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace RoutingSample.Web namespace RoutingSample.Web
@ -20,46 +17,20 @@ namespace RoutingSample.Web
public void Configure(IApplicationBuilder builder) public void Configure(IApplicationBuilder builder)
{ {
var endpoint1 = new DelegateRouteEndpoint(async (context) => var endpoint1 = new RouteHandler((c) =>
await context {
.HttpContext return c.Response.WriteAsync($"match1, route values - {string.Join(", ", c.GetRouteData().Values)}");
.Response });
.WriteAsync(
"match1, route values -" + context.RouteData.Values.Print()));
var endpoint2 = new DelegateRouteEndpoint(async (context) => var endpoint2 = new RouteHandler((c) => c.Response.WriteAsync("Hello, World!"));
await context
.HttpContext
.Response
.WriteAsync("Hello, World!"));
var routeBuilder = new RouteBuilder(); var routeBuilder = new RouteBuilder()
routeBuilder.DefaultHandler = endpoint1; {
routeBuilder.ServiceProvider = builder.ApplicationServices; DefaultHandler = endpoint1,
ServiceProvider = builder.ApplicationServices,
routeBuilder.AddPrefixRoute("api/store"); };
routeBuilder.MapRoute("defaultRoute",
"api/constraint/{controller}",
null,
new { controller = "my.*" });
routeBuilder.MapRoute("regexStringRoute",
"api/rconstraint/{controller}",
new { foo = "Bar" },
new { controller = new RegexRouteConstraint("^(my.*)$") });
routeBuilder.MapRoute("regexRoute",
"api/r2constraint/{controller}",
new { foo = "Bar2" },
new
{
controller = new RegexRouteConstraint(
new Regex("^(my.*)$", RegexOptions.None, TimeSpan.FromSeconds(10)))
});
routeBuilder.MapRoute("parameterConstraintRoute",
"api/{controller}/{*extra}",
new { controller = "Store" });
routeBuilder.AddPrefixRoute("api/store", endpoint1);
routeBuilder.AddPrefixRoute("hello/world", endpoint2); routeBuilder.AddPrefixRoute("hello/world", endpoint2);
routeBuilder.MapLocaleRoute("en-US", "store/US/{action}", new { controller = "Store" }); routeBuilder.MapLocaleRoute("en-US", "store/US/{action}", new { controller = "Store" });

View File

@ -0,0 +1,24 @@
// 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.AspNet.Http;
namespace Microsoft.AspNet.Routing
{
/// <summary>
/// Defines a contract for a handler of a route.
/// </summary>
public interface IRouteHandler
{
/// <summary>
/// Gets a <see cref="RequestDelegate"/> to handle the request, based on the provided
/// <paramref name="routeData"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="routeData">The <see cref="RouteData"/> associated with the current routing match.</param>
/// <returns>
/// A <see cref="RequestDelegate"/>, or <c>null</c> if the handler cannot handle this request.
/// </returns>
RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
}
}

View File

@ -0,0 +1,16 @@
// 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.
namespace Microsoft.AspNet.Routing
{
/// <summary>
/// A feature interface for routing functionality.
/// </summary>
public interface IRoutingFeature
{
/// <summary>
/// Gets or sets the <see cref="Routing.RouteData"/> associated with the current request.
/// </summary>
RouteData RouteData { get; set; }
}
}

View File

@ -6,10 +6,17 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Routing namespace Microsoft.AspNet.Routing
{ {
/// <summary>
/// A context object for <see cref="IRouter.RouteAsync(RouteContext)"/>.
/// </summary>
public class RouteContext public class RouteContext
{ {
private RouteData _routeData; private RouteData _routeData;
/// <summary>
/// Creates a new <see cref="RouteContext"/> for the provided <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
public RouteContext(HttpContext httpContext) public RouteContext(HttpContext httpContext)
{ {
HttpContext = httpContext; HttpContext = httpContext;
@ -17,10 +24,20 @@ namespace Microsoft.AspNet.Routing
RouteData = new RouteData(); RouteData = new RouteData();
} }
public HttpContext HttpContext { get; private set; } /// <summary>
/// Gets or sets the handler for the request. An <see cref="IRouter"/> should set <see cref="Handler"/>
/// when it matches.
/// </summary>
public RequestDelegate Handler { get; set; }
public bool IsHandled { get; set; } /// <summary>
/// Gets the <see cref="Http.HttpContext"/> associated with the current request.
/// </summary>
public HttpContext HttpContext { get; }
/// <summary>
/// Gets or sets the <see cref="Routing.RouteData"/> associated with the current context.
/// </summary>
public RouteData RouteData public RouteData RouteData
{ {
get get

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Routing
/// </summary> /// </summary>
public class RouteData public class RouteData
{ {
private Dictionary<string, object> _dataTokens; private RouteValueDictionary _dataTokens;
private RouteValueDictionary _values; private RouteValueDictionary _values;
/// <summary> /// <summary>
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Routing
// Perf: Avoid allocating DataTokens and RouteValues unless we need to make a copy. // Perf: Avoid allocating DataTokens and RouteValues unless we need to make a copy.
if (other._dataTokens != null) if (other._dataTokens != null)
{ {
_dataTokens = new Dictionary<string, object>(other._dataTokens, StringComparer.OrdinalIgnoreCase); _dataTokens = new RouteValueDictionary(other._dataTokens);
} }
if (other._values != null) if (other._values != null)
@ -51,13 +51,13 @@ namespace Microsoft.AspNet.Routing
/// <summary> /// <summary>
/// Gets the data tokens produced by routes on the current routing path. /// Gets the data tokens produced by routes on the current routing path.
/// </summary> /// </summary>
public IDictionary<string, object> DataTokens public RouteValueDictionary DataTokens
{ {
get get
{ {
if (_dataTokens == null) if (_dataTokens == null)
{ {
_dataTokens = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _dataTokens = new RouteValueDictionary();
} }
return _dataTokens; return _dataTokens;

View File

@ -0,0 +1,53 @@
// 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 System;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Routing
{
/// <summary>
/// Extension methods for <see cref="HttpContext"/> related to routing.
/// </summary>
public static class RoutingHttpContextExtensions
{
/// <summary>
/// Gets the <see cref="RouteData"/> associated with the provided <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <returns>The <see cref="RouteData"/>, or null.</returns>
public static RouteData GetRouteData(this HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature;
return routingFeature?.RouteData;
}
/// <summary>
/// Gets a route value from <see cref="RouteData.Values"/> associated with the provided
/// <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="key">The key of the route value.</param>
/// <returns>The corresponding route value, or null.</returns>
public static object GetRouteValue(this HttpContext httpContext, string key)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature;
return routingFeature?.RouteData.Values[key];
}
}
}

View File

@ -59,8 +59,8 @@ namespace Microsoft.AspNet.Routing
public string RouteName { get; } public string RouteName { get; }
/// <summary> /// <summary>
/// Gets the set of new values provided for virtual path generation. /// Gets or sets the set of new values provided for virtual path generation.
/// </summary> /// </summary>
public RouteValueDictionary Values { get; } public RouteValueDictionary Values { get; set; }
} }
} }

View File

@ -21,6 +21,7 @@
"dotnet5.4": { "dotnet5.4": {
"dependencies": { "dependencies": {
"System.Collections.Concurrent": "4.0.11-*", "System.Collections.Concurrent": "4.0.11-*",
"System.Threading.Tasks": "4.0.11-*",
"System.Reflection.Extensions": "4.0.1-*" "System.Reflection.Extensions": "4.0.1-*"
} }
} }

View File

@ -7,15 +7,20 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Routing namespace Microsoft.AspNet.Routing
{ {
/// <summary> /// <summary>
/// Defines a contract for a route builder in an application. A route builder specifies the routes for an application. /// Defines a contract for a route builder in an application. A route builder specifies the routes for
/// an application.
/// </summary> /// </summary>
public interface IRouteBuilder public interface IRouteBuilder
{ {
/// <summary> /// <summary>
/// Gets or sets the default <see cref="IRouter"/> that is used if an <see cref="IRouter"/> is added to the list of routes but does not specify its own. /// Gets or sets the default <see cref="IRouter"/> that is used as a handler if an <see cref="IRouter"/>
/// is added to the list of routes but does not specify its own.
/// </summary> /// </summary>
IRouter DefaultHandler { get; set; } IRouter DefaultHandler { get; set; }
/// <summary>
/// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
/// </summary>
IServiceProvider ServiceProvider { get; } IServiceProvider ServiceProvider { get; }
/// <summary> /// <summary>

View File

@ -0,0 +1,16 @@
// 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 System.Threading.Tasks;
namespace Microsoft.AspNet.Routing.Internal
{
public static class TaskCache
{
#if DOTNET5_4
public static readonly Task CompletedTask = Task.CompletedTask;
#else
public static readonly Task CompletedTask = Task.FromResult(0);
#endif
}
}

View File

@ -0,0 +1,76 @@
// 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 System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Routing
{
public class Route : RouteBase
{
private readonly IRouter _target;
public Route(
IRouter target,
string routeTemplate,
IInlineConstraintResolver inlineConstraintResolver)
: this(
target,
routeTemplate,
defaults: null,
constraints: null,
dataTokens: null,
inlineConstraintResolver: inlineConstraintResolver)
{
}
public Route(
IRouter target,
string routeTemplate,
RouteValueDictionary defaults,
IDictionary<string, object> constraints,
RouteValueDictionary dataTokens,
IInlineConstraintResolver inlineConstraintResolver)
: this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
{
}
public Route(
IRouter target,
string routeName,
string routeTemplate,
RouteValueDictionary defaults,
IDictionary<string, object> constraints,
RouteValueDictionary dataTokens,
IInlineConstraintResolver inlineConstraintResolver)
: base(
routeTemplate,
routeName,
inlineConstraintResolver,
defaults,
constraints,
dataTokens)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
_target = target;
}
public string RouteTemplate => ParsedTemplate.TemplateText;
protected override Task OnRouteMatched(RouteContext context)
{
context.RouteData.Routers.Add(_target);
return _target.RouteAsync(context);
}
protected override VirtualPathData OnVirtualPathGenerated(VirtualPathContext context)
{
return _target.GetVirtualPath(context);
}
}
}

View File

@ -1,118 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Internal;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Routing.Template namespace Microsoft.AspNet.Routing
{ {
public class TemplateRoute : INamedRouter public abstract class RouteBase : IRouter, INamedRouter
{ {
private readonly IReadOnlyDictionary<string, IRouteConstraint> _constraints; private TemplateMatcher _matcher;
private readonly IReadOnlyDictionary<string, object> _dataTokens; private TemplateBinder _binder;
private readonly IReadOnlyDictionary<string, object> _defaults;
private readonly IRouter _target;
private readonly RouteTemplate _parsedTemplate;
private readonly string _routeTemplate;
private readonly TemplateMatcher _matcher;
private readonly TemplateBinder _binder;
private ILogger _logger; private ILogger _logger;
private ILogger _constraintLogger; private ILogger _constraintLogger;
public TemplateRoute( public RouteBase(
IRouter target, string template,
string routeTemplate, string name,
IInlineConstraintResolver inlineConstraintResolver) IInlineConstraintResolver constraintResolver,
: this( RouteValueDictionary defaults,
target,
routeTemplate,
defaults: null,
constraints: null,
dataTokens: null,
inlineConstraintResolver: inlineConstraintResolver)
{
}
public TemplateRoute(
IRouter target,
string routeTemplate,
IDictionary<string, object> defaults,
IDictionary<string, object> constraints, IDictionary<string, object> constraints,
IDictionary<string, object> dataTokens, RouteValueDictionary dataTokens)
IInlineConstraintResolver inlineConstraintResolver)
: this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
{ {
} if (constraintResolver == null)
public TemplateRoute(
IRouter target,
string routeName,
string routeTemplate,
IDictionary<string, object> defaults,
IDictionary<string, object> constraints,
IDictionary<string, object> dataTokens,
IInlineConstraintResolver inlineConstraintResolver)
{
if (target == null)
{ {
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(constraintResolver));
} }
_target = target; template = template ?? string.Empty;
_routeTemplate = routeTemplate ?? string.Empty; Name = name;
Name = routeName; ConstraintResolver = constraintResolver;
DataTokens = dataTokens ?? new RouteValueDictionary();
_dataTokens = dataTokens == null ? new RouteValueDictionary() : new RouteValueDictionary(dataTokens);
// Data we parse from the template will be used to fill in the rest of the constraints or // Data we parse from the template will be used to fill in the rest of the constraints or
// defaults. The parser will throw for invalid routes. // defaults. The parser will throw for invalid routes.
_parsedTemplate = TemplateParser.Parse(RouteTemplate); ParsedTemplate = TemplateParser.Parse(template);
_constraints = GetConstraints(inlineConstraintResolver, RouteTemplate, _parsedTemplate, constraints); Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
_defaults = GetDefaults(_parsedTemplate, defaults); Defaults = GetDefaults(ParsedTemplate, defaults);
_matcher = new TemplateMatcher(_parsedTemplate, Defaults);
_binder = new TemplateBinder(_parsedTemplate, Defaults);
} }
public string Name { get; private set; } public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
public IReadOnlyDictionary<string, object> Defaults protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
{
get { return _defaults; }
}
public IReadOnlyDictionary<string, object> DataTokens public virtual RouteValueDictionary DataTokens { get; protected set; }
{
get { return _dataTokens; }
}
public RouteTemplate ParsedTemplate public virtual RouteValueDictionary Defaults { get; protected set; }
{
get { return _parsedTemplate; }
}
public string RouteTemplate public virtual string Name { get; protected set; }
{
get { return _routeTemplate; }
}
public IReadOnlyDictionary<string, IRouteConstraint> Constraints public virtual RouteTemplate ParsedTemplate { get; protected set; }
{
get { return _constraints; }
}
public async virtual Task RouteAsync(RouteContext context) protected abstract Task OnRouteMatched(RouteContext context);
protected abstract VirtualPathData OnVirtualPathGenerated(VirtualPathContext context);
/// <inheritdoc />
public virtual Task RouteAsync(RouteContext context)
{ {
if (context == null) if (context == null)
{ {
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
EnsureMatcher();
EnsureLoggers(context.HttpContext); EnsureLoggers(context.HttpContext);
var requestPath = context.HttpContext.Request.Path; var requestPath = context.HttpContext.Request.Path;
@ -121,57 +78,46 @@ namespace Microsoft.AspNet.Routing.Template
if (values == null) if (values == null)
{ {
// If we got back a null value set, that means the URI did not match // If we got back a null value set, that means the URI did not match
return; return TaskCache.CompletedTask;
} }
var oldRouteData = context.RouteData; // Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all
var newRouteData = new RouteData(oldRouteData);
// Perf: Avoid accessing data tokens if you don't need to write to it, these dictionaries are all
// created lazily. // created lazily.
if (_dataTokens.Count > 0) if (DataTokens.Count > 0)
{ {
MergeValues(newRouteData.DataTokens, _dataTokens); MergeValues(context.RouteData.DataTokens, DataTokens);
} }
newRouteData.Routers.Add(_target); if (values.Count > 0)
MergeValues(newRouteData.Values, values); {
MergeValues(context.RouteData.Values, values);
}
if (!RouteConstraintMatcher.Match( if (!RouteConstraintMatcher.Match(
Constraints, Constraints,
newRouteData.Values, context.RouteData.Values,
context.HttpContext, context.HttpContext,
this, this,
RouteDirection.IncomingRequest, RouteDirection.IncomingRequest,
_constraintLogger)) _constraintLogger))
{ {
return; return TaskCache.CompletedTask;
} }
_logger.LogDebug( _logger.LogDebug(
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.", "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.",
Name, Name,
RouteTemplate); ParsedTemplate.TemplateText);
try return OnRouteMatched(context);
{
context.RouteData = newRouteData;
await _target.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
} }
/// <inheritdoc />
public virtual VirtualPathData GetVirtualPath(VirtualPathContext context) public virtual VirtualPathData GetVirtualPath(VirtualPathContext context)
{ {
EnsureBinder();
EnsureLoggers(context.HttpContext);
var values = _binder.GetValues(context.AmbientValues, context.Values); var values = _binder.GetValues(context.AmbientValues, context.Values);
if (values == null) if (values == null)
{ {
@ -179,7 +125,6 @@ namespace Microsoft.AspNet.Routing.Template
return null; return null;
} }
EnsureLoggers(context.HttpContext);
if (!RouteConstraintMatcher.Match( if (!RouteConstraintMatcher.Match(
Constraints, Constraints,
values.CombinedValues, values.CombinedValues,
@ -191,7 +136,9 @@ namespace Microsoft.AspNet.Routing.Template
return null; return null;
} }
var pathData = _target.GetVirtualPath(context); context.Values = values.CombinedValues;
var pathData = OnVirtualPathGenerated(context);
if (pathData != null) if (pathData != null)
{ {
// If the target generates a value then that can short circuit. // If the target generates a value then that can short circuit.
@ -202,13 +149,13 @@ namespace Microsoft.AspNet.Routing.Template
// to see if the values were validated. // to see if the values were validated.
// When we still cannot produce a value, this should return null. // When we still cannot produce a value, this should return null.
var tempPath = _binder.BindValues(values.AcceptedValues); var virtualPath = _binder.BindValues(values.AcceptedValues);
if (tempPath == null) if (virtualPath == null)
{ {
return null; return null;
} }
pathData = new VirtualPathData(this, tempPath); pathData = new VirtualPathData(this, virtualPath);
if (DataTokens != null) if (DataTokens != null)
{ {
foreach (var dataToken in DataTokens) foreach (var dataToken in DataTokens)
@ -220,13 +167,12 @@ namespace Microsoft.AspNet.Routing.Template
return pathData; return pathData;
} }
private static IReadOnlyDictionary<string, IRouteConstraint> GetConstraints( protected static IDictionary<string, IRouteConstraint> GetConstraints(
IInlineConstraintResolver inlineConstraintResolver, IInlineConstraintResolver inlineConstraintResolver,
string template,
RouteTemplate parsedTemplate, RouteTemplate parsedTemplate,
IDictionary<string, object> constraints) IDictionary<string, object> constraints)
{ {
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, template); var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
if (constraints != null) if (constraints != null)
{ {
@ -252,12 +198,10 @@ namespace Microsoft.AspNet.Routing.Template
return constraintBuilder.Build(); return constraintBuilder.Build();
} }
private static RouteValueDictionary GetDefaults( protected static RouteValueDictionary GetDefaults(
RouteTemplate parsedTemplate, RouteTemplate parsedTemplate,
IDictionary<string, object> defaults) RouteValueDictionary defaults)
{ {
// Do not use RouteValueDictionary.Empty for defaults, it might be modified inside
// UpdateInlineDefaultValuesAndConstraints()
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults); var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
foreach (var parameter in parsedTemplate.Parameters) foreach (var parameter in parsedTemplate.Parameters)
@ -293,17 +237,11 @@ namespace Microsoft.AspNet.Routing.Template
} }
} }
// Needed because IDictionary<> is not an IReadOnlyDictionary<> private void EnsureBinder()
private static void MergeValues(
IDictionary<string, object> destination,
IReadOnlyDictionary<string, object> values)
{ {
foreach (var kvp in values) if (_binder == null)
{ {
// This will replace the original value for the specified key. _binder = new TemplateBinder(ParsedTemplate, Defaults);
// Values from the matched route will take preference over previous
// data in the route context.
destination[kvp.Key] = kvp.Value;
} }
} }
@ -312,14 +250,22 @@ namespace Microsoft.AspNet.Routing.Template
if (_logger == null) if (_logger == null)
{ {
var factory = context.RequestServices.GetRequiredService<ILoggerFactory>(); var factory = context.RequestServices.GetRequiredService<ILoggerFactory>();
_logger = factory.CreateLogger<TemplateRoute>(); _logger = factory.CreateLogger(typeof(RouteBase).FullName);
_constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName); _constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
} }
} }
private void EnsureMatcher()
{
if (_matcher == null)
{
_matcher = new TemplateMatcher(ParsedTemplate, Defaults);
}
}
public override string ToString() public override string ToString()
{ {
return _routeTemplate; return ParsedTemplate.TemplateText;
} }
} }
} }

View File

@ -17,11 +17,7 @@ namespace Microsoft.AspNet.Routing
public IServiceProvider ServiceProvider { get; set; } public IServiceProvider ServiceProvider { get; set; }
public IList<IRouter> Routes public IList<IRouter> Routes { get; }
{
get;
private set;
}
public IRouter Build() public IRouter Build()
{ {

View File

@ -21,9 +21,10 @@ namespace Microsoft.AspNet.Builder
/// <param name="name">The name of the route.</param> /// <param name="name">The name of the route.</param>
/// <param name="template">The URL pattern of the route.</param> /// <param name="template">The URL pattern of the route.</param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, public static IRouteBuilder MapRoute(
string name, this IRouteBuilder routeBuilder,
string template) string name,
string template)
{ {
MapRoute(routeBuilder, name, template, defaults: null); MapRoute(routeBuilder, name, template, defaults: null);
return routeBuilder; return routeBuilder;
@ -35,50 +36,73 @@ namespace Microsoft.AspNet.Builder
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param> /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param> /// <param name="name">The name of the route.</param>
/// <param name="template">The URL pattern of the route.</param> /// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">An object that contains default values for route parameters. The object's properties represent the names and values of the default values.</param> /// <param name="defaults">
/// An object that contains default values for route parameters. The object's properties represent the names
/// and values of the default values.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, public static IRouteBuilder MapRoute(
string name, this IRouteBuilder routeBuilder,
string template, string name,
object defaults) string template,
object defaults)
{ {
return MapRoute(routeBuilder, name, template, defaults, constraints: null); return MapRoute(routeBuilder, name, template, defaults, constraints: null);
} }
/// <summary> /// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and constraints. /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
/// constraints.
/// </summary> /// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param> /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param> /// <param name="name">The name of the route.</param>
/// <param name="template">The URL pattern of the route.</param> /// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">An object that contains default values for route parameters. The object's properties represent the names and values of the default values.</param> /// <param name="defaults">
/// <param name="constraints">An object that contains constraints for the route. The object's properties represent the names and values of the constraints.</param> /// An object that contains default values for route parameters. The object's properties represent the names
/// and values of the default values.
/// </param>
/// <param name="constraints">
/// An object that contains constraints for the route. The object's properties represent the names and values
/// of the constraints.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, public static IRouteBuilder MapRoute(
string name, this IRouteBuilder routeBuilder,
string template, string name,
object defaults, string template,
object constraints) object defaults,
object constraints)
{ {
return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null); return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
} }
/// <summary> /// <summary>
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and data tokens. /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
/// data tokens.
/// </summary> /// </summary>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param> /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name of the route.</param> /// <param name="name">The name of the route.</param>
/// <param name="template">The URL pattern of the route.</param> /// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults">An object that contains default values for route parameters. The object's properties represent the names and values of the default values.</param> /// <param name="defaults">
/// <param name="constraints">An object that contains constraints for the route. The object's properties represent the names and values of the constraints.</param> /// An object that contains default values for route parameters. The object's properties represent the names
/// <param name="dataTokens">An object that contains data tokens for the route. The object's properties represent the names and values of the data tokens.</param> /// and values of the default values.
/// </param>
/// <param name="constraints">
/// An object that contains constraints for the route. The object's properties represent the names and values
/// of the constraints.
/// </param>
/// <param name="dataTokens">
/// An object that contains data tokens for the route. The object's properties represent the names and values
/// of the data tokens.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, public static IRouteBuilder MapRoute(
string name, this IRouteBuilder routeBuilder,
string template, string name,
object defaults, string template,
object constraints, object defaults,
object dataTokens) object constraints,
object dataTokens)
{ {
if (routeBuilder.DefaultHandler == null) if (routeBuilder.DefaultHandler == null)
{ {
@ -88,26 +112,17 @@ namespace Microsoft.AspNet.Builder
var inlineConstraintResolver = routeBuilder var inlineConstraintResolver = routeBuilder
.ServiceProvider .ServiceProvider
.GetRequiredService<IInlineConstraintResolver>(); .GetRequiredService<IInlineConstraintResolver>();
routeBuilder.Routes.Add(new TemplateRoute(routeBuilder.DefaultHandler,
name, routeBuilder.Routes.Add(new Route(
template, routeBuilder.DefaultHandler,
ObjectToDictionary(defaults), name,
ObjectToDictionary(constraints), template,
ObjectToDictionary(dataTokens), new RouteValueDictionary(defaults),
inlineConstraintResolver)); new RouteValueDictionary(constraints),
new RouteValueDictionary(dataTokens),
inlineConstraintResolver));
return routeBuilder; return routeBuilder;
} }
private static IDictionary<string, object> ObjectToDictionary(object value)
{
var dictionary = value as IDictionary<string, object>;
if (dictionary != null)
{
return dictionary;
}
return new RouteValueDictionary(value);
}
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -69,14 +68,15 @@ namespace Microsoft.AspNet.Routing
context.RouteData = newRouteData; context.RouteData = newRouteData;
await route.RouteAsync(context); await route.RouteAsync(context);
if (context.IsHandled)
if (context.Handler != null)
{ {
break; break;
} }
} }
finally finally
{ {
if (!context.IsHandled) if (context.Handler == null)
{ {
context.RouteData = oldRouteData; context.RouteData = oldRouteData;
} }

View File

@ -50,8 +50,8 @@ namespace Microsoft.AspNet.Routing
/// <summary> /// <summary>
/// Builds a mapping of constraints. /// Builds a mapping of constraints.
/// </summary> /// </summary>
/// <returns>An <see cref="IReadOnlyDictionary{string, IRouteConstraint}"/> of the constraints.</returns> /// <returns>An <see cref="IDictionary{string, IRouteConstraint}"/> of the constraints.</returns>
public IReadOnlyDictionary<string, IRouteConstraint> Build() public IDictionary<string, IRouteConstraint> Build()
{ {
var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase); var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in _constraints) foreach (var kvp in _constraints)

View File

@ -10,12 +10,13 @@ namespace Microsoft.AspNet.Routing
{ {
public static class RouteConstraintMatcher public static class RouteConstraintMatcher
{ {
public static bool Match(IReadOnlyDictionary<string, IRouteConstraint> constraints, public static bool Match(
IDictionary<string, object> routeValues, IDictionary<string, IRouteConstraint> constraints,
HttpContext httpContext, IDictionary<string, object> routeValues,
IRouter route, HttpContext httpContext,
RouteDirection routeDirection, IRouter route,
ILogger logger) RouteDirection routeDirection,
ILogger logger)
{ {
if (routeValues == null) if (routeValues == null)
{ {

View File

@ -0,0 +1,36 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Internal;
namespace Microsoft.AspNet.Routing
{
public class RouteHandler : IRouteHandler, IRouter
{
private readonly RequestDelegate _requestDelegate;
public RouteHandler(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
}
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
{
return _requestDelegate;
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
// Nothing to do.
return null;
}
public Task RouteAsync(RouteContext context)
{
context.Handler = _requestDelegate;
return TaskCache.CompletedTask;
}
}
}

View File

@ -5,8 +5,6 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Routing.Logging.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Builder namespace Microsoft.AspNet.Builder
@ -35,12 +33,26 @@ namespace Microsoft.AspNet.Builder
await _router.RouteAsync(context); await _router.RouteAsync(context);
if (!context.IsHandled) if (context.Handler == null)
{ {
_logger.LogDebug("Request did not match any routes."); _logger.LogDebug("Request did not match any routes.");
httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
{
RouteData = context.RouteData,
};
await _next.Invoke(httpContext); await _next.Invoke(httpContext);
} }
else
{
await context.Handler(context.HttpContext);
}
}
private class RoutingFeature : IRoutingFeature
{
public RouteData RouteData { get; set; }
} }
} }
} }

View File

@ -13,47 +13,48 @@ namespace Microsoft.AspNet.Routing.Tree
public class TreeRouteLinkGenerationEntry public class TreeRouteLinkGenerationEntry
{ {
/// <summary> /// <summary>
/// The <see cref="TemplateBinder"/>. /// Gets or sets the <see cref="TemplateBinder"/>.
/// </summary> /// </summary>
public TemplateBinder Binder { get; set; } public TemplateBinder Binder { get; set; }
/// <summary> /// <summary>
/// The route constraints. /// Gets or sets the route constraints.
/// </summary> /// </summary>
public IReadOnlyDictionary<string, IRouteConstraint> Constraints { get; set; } public IDictionary<string, IRouteConstraint> Constraints { get; set; }
/// <summary> /// <summary>
/// The route defaults. /// Gets or sets the route defaults.
/// </summary> /// </summary>
public IReadOnlyDictionary<string, object> Defaults { get; set; } public IDictionary<string, object> Defaults { get; set; }
/// <summary> /// <summary>
/// The order of the template. /// Gets or sets the order of the template.
/// </summary> /// </summary>
public int Order { get; set; } public int Order { get; set; }
/// <summary> /// <summary>
/// The precedence of the template for link generation. Greater number means higher precedence. /// Gets or sets the precedence of the template for link generation. A greater value of
/// <see cref="GenerationPrecedence"/> means that an entry is considered first.
/// </summary> /// </summary>
public decimal GenerationPrecedence { get; set; } public decimal GenerationPrecedence { get; set; }
/// <summary> /// <summary>
/// The name of the route. /// Gets or sets the name of the route.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// The route group. /// Gets or sets the route group.
/// </summary> /// </summary>
public string RouteGroup { get; set; } public string RouteGroup { get; set; }
/// <summary> /// <summary>
/// The set of values that must be present for link genration. /// Gets or sets the set of values that must be present for link genration.
/// </summary> /// </summary>
public IDictionary<string, object> RequiredLinkValues { get; set; } public IDictionary<string, object> RequiredLinkValues { get; set; }
/// <summary> /// <summary>
/// The <see cref="Template"/>. /// Gets or sets the <see cref="Template"/>.
/// </summary> /// </summary>
public RouteTemplate Template { get; set; } public RouteTemplate Template { get; set; }
} }

View File

@ -45,6 +45,6 @@ namespace Microsoft.AspNet.Routing.Tree
/// <summary> /// <summary>
/// The route constraints. /// The route constraints.
/// </summary> /// </summary>
public IReadOnlyDictionary<string, IRouteConstraint> Constraints { get; set; } public IDictionary<string, IRouteConstraint> Constraints { get; set; }
} }
} }

View File

@ -199,20 +199,19 @@ namespace Microsoft.AspNet.Routing.Tree
try try
{ {
await match.Entry.Target.RouteAsync(context); await match.Entry.Target.RouteAsync(context);
if (context.Handler != null)
{
return;
}
} }
finally finally
{ {
if (!context.IsHandled) if (context.Handler == null)
{ {
// Restore the original values to prevent polluting the route data. // Restore the original values to prevent polluting the route data.
context.RouteData = oldRouteData; context.RouteData = oldRouteData;
} }
} }
if (context.IsHandled)
{
return;
}
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Routing.Template; using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,6 +17,8 @@ namespace Microsoft.AspNet.Routing
{ {
public class RouteCollectionTest public class RouteCollectionTest
{ {
private static readonly RequestDelegate NullHandler = (c) => Task.FromResult(0);
[Theory] [Theory]
[InlineData(@"Home/Index/23", "/home/index/23", true, false)] [InlineData(@"Home/Index/23", "/home/index/23", true, false)]
[InlineData(@"Home/Index/23", "/Home/Index/23", false, false)] [InlineData(@"Home/Index/23", "/Home/Index/23", false, false)]
@ -143,7 +144,7 @@ namespace Microsoft.AspNet.Routing
// Assert // Assert
route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1)); route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(0)); route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(0));
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(1, context.RouteData.Routers.Count); Assert.Equal(1, context.RouteData.Routers.Count);
Assert.Same(route1.Object, context.RouteData.Routers[0]); Assert.Same(route1.Object, context.RouteData.Routers[0]);
@ -169,7 +170,7 @@ namespace Microsoft.AspNet.Routing
// Assert // Assert
route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1)); route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1)); route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(1, context.RouteData.Routers.Count); Assert.Equal(1, context.RouteData.Routers.Count);
Assert.Same(route2.Object, context.RouteData.Routers[0]); Assert.Same(route2.Object, context.RouteData.Routers[0]);
@ -194,7 +195,7 @@ namespace Microsoft.AspNet.Routing
// Assert // Assert
route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1)); route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1)); route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
Assert.False(context.IsHandled); Assert.Null(context.Handler);
Assert.Empty(context.RouteData.Routers); Assert.Empty(context.RouteData.Routers);
} }
@ -485,14 +486,14 @@ namespace Microsoft.AspNet.Routing
target target
.Setup(e => e.RouteAsync(It.IsAny<RouteContext>())) .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => c.IsHandled = accept) .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
.Returns(Task.FromResult<object>(null)) .Returns(Task.FromResult<object>(null))
.Verifiable(); .Verifiable();
return target.Object; return target.Object;
} }
private static TemplateRoute CreateTemplateRoute( private static Route CreateTemplateRoute(
string template, string template,
string routerName = null, string routerName = null,
RouteValueDictionary dataTokens = null) RouteValueDictionary dataTokens = null)
@ -504,7 +505,7 @@ namespace Microsoft.AspNet.Routing
var resolverMock = new Mock<IInlineConstraintResolver>(); var resolverMock = new Mock<IInlineConstraintResolver>();
return new TemplateRoute( return new Route(
target.Object, target.Object,
routerName, routerName,
template, template,
@ -610,7 +611,7 @@ namespace Microsoft.AspNet.Routing
target target
.Setup(e => e.RouteAsync(It.IsAny<RouteContext>())) .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => c.IsHandled = accept) .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
.Returns(Task.FromResult<object>(null)) .Returns(Task.FromResult<object>(null))
.Verifiable(); .Verifiable();

View File

@ -8,54 +8,19 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints; using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Extensions.Logging.Internal;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Moq; using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Routing.Template namespace Microsoft.AspNet.Routing
{ {
public class TemplateRouteTest public class RouteTest
{ {
private static readonly RequestDelegate NullHandler = (c) => Task.FromResult(0);
private static IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver(); private static IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
private async Task<Tuple<TestSink, RouteContext>> SetUp(
bool loggerEnabled,
string routeName,
string template,
string requestPath,
TestSink testSink = null)
{
if (testSink == null)
{
testSink = new TestSink(
TestSink.EnableWithTypeName<TemplateRoute>,
TestSink.EnableWithTypeName<TemplateRoute>);
}
var loggerFactory = new TestLoggerFactory(testSink, loggerEnabled);
TemplateRoute route;
if (!string.IsNullOrEmpty(routeName))
{
route = CreateRoute(routeName, template);
}
else
{
route = CreateRoute(template);
}
var context = CreateRouteContext(requestPath, loggerFactory);
// Act
await route.RouteAsync(context);
return Tuple.Create(testSink, context);
}
[Fact] [Fact]
public void CreateTemplate_InlineConstraint_Regex_Malformed() public void CreateTemplate_InlineConstraint_Regex_Malformed()
{ {
@ -67,7 +32,7 @@ namespace Microsoft.AspNet.Routing.Template
"'IInlineConstraintResolverProxy'."; "'IInlineConstraintResolverProxy'.";
var exception = Assert.Throws<InvalidOperationException>( var exception = Assert.Throws<InvalidOperationException>(
() => new TemplateRoute( () => new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -78,67 +43,6 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal(expected, exception.Message); Assert.Equal(expected, exception.Message);
} }
[Fact]
public async Task RouteAsync_MatchSuccess_LogsCorrectValues()
{
// Arrange & Act
var routeName = "Default";
var template = "{controller}/{action}";
var result = await SetUp(
routeName: routeName,
template: template,
requestPath: "/Home/Index",
loggerEnabled: true);
var sink = result.Item1;
var expected = "Request successfully matched the route with " +
$"name '{routeName}' and template '{template}'.";
// Assert
Assert.Empty(sink.Scopes);
Assert.Single(sink.Writes);
Assert.Equal(expected, sink.Writes[0].State?.ToString());
}
[Fact]
public async Task RouteAsync_MatchFailOnConstraints_LogsCorrectValues()
{
// Arrange & Act
var sink = new TestSink((writeEnabled) => true, (scopeEnabled) => true);
var template = "{controller}/{action}/{id:int}";
var result = await SetUp(
routeName: "Default",
template: template,
requestPath: "/Home/Index/Failure",
loggerEnabled: true,
testSink: sink);
var expectedMessage = "Route value 'Failure' with key 'id' did not match the " +
$"constraint '{typeof(IntRouteConstraint).FullName}'.";
var expectedLogValues = new[]
{
new KeyValuePair<string, object>("RouteValue", "Failure"),
new KeyValuePair<string, object>("RouteKey", "id"),
new KeyValuePair<string, object>("RouteConstraint", typeof(IntRouteConstraint).FullName)
};
// Assert
Assert.Empty(sink.Scopes);
Assert.Single(sink.Writes);
var sinkWrite = sink.Writes[0];
var formattedLogValues = Assert.IsType<FormattedLogValues>(sinkWrite.State);
Assert.Equal(expectedMessage, formattedLogValues.ToString());
var actualLogValues = formattedLogValues.GetValues();
foreach(var expectedPair in expectedLogValues)
{
Assert.Contains(
actualLogValues,
(kvp) =>
{
return string.Equals(expectedPair.Key, kvp.Key)
&& string.Equals(expectedPair.Value?.ToString(), kvp.Value?.ToString());
});
}
}
[Fact] [Fact]
public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches() public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches()
{ {
@ -160,11 +64,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -187,12 +91,11 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal("USA", context.RouteData.Values["country"]); Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.True(context.RouteData.Values.ContainsKey("id")); Assert.True(context.RouteData.Values.ContainsKey("id"));
Assert.Equal("5", context.RouteData.Values["id"]); Assert.Equal("5", context.RouteData.Values["id"]);
Assert.NotSame(originalRouteDataValues, context.RouteData.Values); Assert.Same(originalRouteDataValues, context.RouteData.Values);
Assert.Equal("Contoso", context.RouteData.DataTokens["company"]); Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
Assert.Equal("Friday", context.RouteData.DataTokens["today"]); Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
Assert.NotSame(originalDataTokens, context.RouteData.DataTokens); Assert.Same(originalDataTokens, context.RouteData.DataTokens);
Assert.NotSame(route.DataTokens, context.RouteData.DataTokens);
} }
[Fact] [Fact]
@ -215,13 +118,13 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var constraint = new CapturingConstraint(); var constraint = new CapturingConstraint();
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -249,171 +152,10 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal("USA", context.RouteData.Values["country"]); Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.True(context.RouteData.Values.ContainsKey("id")); Assert.True(context.RouteData.Values.ContainsKey("id"));
Assert.Equal("5", context.RouteData.Values["id"]); Assert.Equal("5", context.RouteData.Values["id"]);
Assert.NotSame(originalRouteDataValues, context.RouteData.Values); Assert.Same(originalRouteDataValues, context.RouteData.Values);
Assert.Equal("Contoso", context.RouteData.DataTokens["company"]); Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
Assert.Equal("Friday", context.RouteData.DataTokens["today"]); Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
Assert.NotSame(originalDataTokens, context.RouteData.DataTokens);
Assert.NotSame(route.DataTokens, context.RouteData.DataTokens);
}
[Fact]
public async Task RouteAsync_CleansUpMergedRouteData_IfRouteDoesNotMatch()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = context.RouteData.Values;
originalRouteDataValues.Add("country", "USA");
var originalDataTokens = context.RouteData.DataTokens;
originalDataTokens.Add("company", "Contoso");
IDictionary<string, object> routeValues = null;
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = false;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
mockTarget.Object,
template,
defaults: null,
constraints: null,
dataTokens: new RouteValueDictionary(new { today = "Friday" }),
inlineConstraintResolver: _inlineConstraintResolver);
// Act
await route.RouteAsync(context);
// Assert
Assert.NotNull(routeValues);
Assert.True(routeValues.ContainsKey("country"));
Assert.Equal("USA", routeValues["country"]);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.False(context.RouteData.Values.ContainsKey("id"));
Assert.Same(originalRouteDataValues, context.RouteData.Values);
Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
Assert.False(context.RouteData.DataTokens.ContainsKey("today"));
Assert.Same(originalDataTokens, context.RouteData.DataTokens);
}
[Fact]
public async Task RouteAsync_CleansUpMergedRouteData_IfHandlerThrows()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = context.RouteData.Values;
originalRouteDataValues.Add("country", "USA");
var originalDataTokens = context.RouteData.DataTokens;
originalDataTokens.Add("company", "Contoso");
IDictionary<string, object> routeValues = null;
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = false;
})
.Throws(new Exception());
var route = new TemplateRoute(
mockTarget.Object,
template,
defaults: null,
constraints: null,
dataTokens: new RouteValueDictionary(new { today = "Friday" }),
inlineConstraintResolver: _inlineConstraintResolver);
// Act
var ex = await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
// Assert
Assert.NotNull(routeValues);
Assert.True(routeValues.ContainsKey("country"));
Assert.Equal("USA", routeValues["country"]);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.False(context.RouteData.Values.ContainsKey("id"));
Assert.Same(originalRouteDataValues, context.RouteData.Values);
Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
Assert.False(context.RouteData.DataTokens.ContainsKey("today"));
Assert.Same(originalDataTokens, context.RouteData.DataTokens);
}
[Fact]
public async Task RouteAsync_CleansUpMergedRouteData_IfConstraintThrows()
{
// Arrange
var template = "{controller}/{action}/{id:int}";
var context = CreateRouteContext("/Home/Index/5");
var originalRouteDataValues = context.RouteData.Values;
originalRouteDataValues.Add("country", "USA");
var originalDataTokens = context.RouteData.DataTokens;
originalDataTokens.Add("company", "Contoso");
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
mockTarget
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(ctx =>
{
ctx.IsHandled = true;
})
.Returns(Task.FromResult(true));
var constraint = new Mock<IRouteConstraint>(MockBehavior.Strict);
constraint
.Setup(c => c.Match(
It.IsAny<HttpContext>(),
It.IsAny<IRouter>(),
It.IsAny<string>(),
It.IsAny<IDictionary<string, object>>(),
It.IsAny<RouteDirection>()))
.Callback(() => { throw new Exception(); });
var route = new TemplateRoute(
mockTarget.Object,
template,
defaults: null,
constraints: new RouteValueDictionary(new { action = constraint.Object }),
dataTokens: new RouteValueDictionary(new { today = "Friday" }),
inlineConstraintResolver: _inlineConstraintResolver);
// Act
var ex = await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
// Assert
Assert.True(context.RouteData.Values.ContainsKey("country"));
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.False(context.RouteData.Values.ContainsKey("id"));
Assert.Same(originalRouteDataValues, context.RouteData.Values);
Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
Assert.False(context.RouteData.DataTokens.ContainsKey("today"));
Assert.Same(originalDataTokens, context.RouteData.DataTokens); Assert.Same(originalDataTokens, context.RouteData.DataTokens);
} }
@ -432,11 +174,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -451,7 +193,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("id")); Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]); Assert.Equal("5", routeValues["id"]);
@ -474,11 +216,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -493,7 +235,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("ssn")); Assert.True(routeValues.ContainsKey("ssn"));
Assert.Equal("123-456-7890", routeValues["ssn"]); Assert.Equal("123-456-7890", routeValues["ssn"]);
@ -516,11 +258,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -535,7 +277,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.NotNull(routeValues); Assert.NotNull(routeValues);
Assert.False(routeValues.ContainsKey("id")); Assert.False(routeValues.ContainsKey("id"));
Assert.False(context.RouteData.Values.ContainsKey("id")); Assert.False(context.RouteData.Values.ContainsKey("id"));
@ -556,14 +298,14 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var constraints = new Dictionary<string, object>(); var constraints = new Dictionary<string, object>();
constraints.Add("id", new RangeRouteConstraint(1, 20)); constraints.Add("id", new RangeRouteConstraint(1, 20));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -585,7 +327,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("id")); Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]); Assert.Equal("5", routeValues["id"]);
@ -608,11 +350,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx => .Callback<RouteContext>(ctx =>
{ {
routeValues = ctx.RouteData.Values; routeValues = ctx.RouteData.Values;
ctx.IsHandled = true; ctx.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true));
var route = new TemplateRoute( var route = new Route(
mockTarget.Object, mockTarget.Object,
template, template,
defaults: null, defaults: null,
@ -627,7 +369,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.False(context.IsHandled); Assert.Null(context.Handler);
} }
#region Route Matching #region Route Matching
@ -644,7 +386,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count); Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]); Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Index", context.RouteData.Values["action"]); Assert.Equal("Index", context.RouteData.Values["action"]);
@ -661,7 +403,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(0, context.RouteData.Values.Count); Assert.Equal(0, context.RouteData.Values.Count);
} }
@ -676,7 +418,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count); Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]); Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Index", context.RouteData.Values["action"]); Assert.Equal("Index", context.RouteData.Values["action"]);
@ -695,7 +437,7 @@ namespace Microsoft.AspNet.Routing.Template
// Act // Act
await route.RouteAsync(context); await route.RouteAsync(context);
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
// This should not affect the route - RouteData.DataTokens is a copy // This should not affect the route - RouteData.DataTokens is a copy
context.RouteData.DataTokens.Add("company", "contoso"); context.RouteData.DataTokens.Add("company", "contoso");
@ -716,7 +458,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.False(context.IsHandled); Assert.Null(context.Handler);
} }
[Fact] [Fact]
@ -730,25 +472,11 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.False(context.IsHandled); Assert.Null(context.Handler);
// Issue #16 tracks this. var value = Assert.Single(context.RouteData.Values);
Assert.Empty(context.RouteData.Values); Assert.Equal("controller", value.Key);
} Assert.Equal("Home", Assert.IsType<string>(value.Value));
[Fact]
public async Task Match_RejectedByHandler_ClearsRouters()
{
// Arrange
var route = CreateRoute("{controller}", handleRequest: false);
var context = CreateRouteContext("/Home");
// Act
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
Assert.Empty(context.RouteData.Routers);
} }
[Fact] [Fact]
@ -763,7 +491,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(1, context.RouteData.Routers.Count); Assert.Equal(1, context.RouteData.Routers.Count);
Assert.Same(target, context.RouteData.Routers[0]); Assert.Same(target, context.RouteData.Routers[0]);
} }
@ -793,7 +521,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(3, context.RouteData.Values.Count); Assert.Equal(3, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]); Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]); Assert.Equal("Create", context.RouteData.Values["action"]);
@ -811,7 +539,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count); Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]); Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]); Assert.Equal("Create", context.RouteData.Values["action"]);
@ -828,7 +556,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(3, context.RouteData.Values.Count); Assert.Equal(3, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]); Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]); Assert.Equal("Create", context.RouteData.Values["action"]);
@ -846,7 +574,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.False(context.IsHandled); Assert.Null(context.Handler);
} }
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null) private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
@ -1021,14 +749,14 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange // Arrange
var context = CreateVirtualPathContext(new { p1 = "abcd" }); var context = CreateVirtualPathContext(new { p1 = "abcd" });
TemplateRoute r = CreateRoute( var route = CreateRoute(
"{p1}/{p2}", "{p1}/{p2}",
new { p2 = "catchall" }, new { p2 = "catchall" },
true, true,
new RouteValueDictionary(new { p2 = "\\d{4}" })); new RouteValueDictionary(new { p2 = "\\d{4}" }));
// Act // Act
var virtualPath = r.GetVirtualPath(context); var virtualPath = route.GetVirtualPath(context);
// Assert // Assert
Assert.Null(virtualPath); Assert.Null(virtualPath);
@ -1062,14 +790,14 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange // Arrange
var context = CreateVirtualPathContext(new { p1 = "abcd" }); var context = CreateVirtualPathContext(new { p1 = "abcd" });
TemplateRoute r = CreateRoute( var route = CreateRoute(
"{p1}/{*p2}", "{p1}/{*p2}",
new { p2 = "catchall" }, new { p2 = "catchall" },
true, true,
new RouteValueDictionary(new { p2 = "\\d{4}" })); new RouteValueDictionary(new { p2 = "\\d{4}" }));
// Act // Act
var virtualPath = r.GetVirtualPath(context); var virtualPath = route.GetVirtualPath(context);
// Assert // Assert
Assert.Null(virtualPath); Assert.Null(virtualPath);
@ -1081,7 +809,7 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange // Arrange
var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" }); var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
TemplateRoute route = CreateRoute( var route = CreateRoute(
"{p1}/{*p2}", "{p1}/{*p2}",
new { p2 = "catchall" }, new { p2 = "catchall" },
true, true,
@ -1112,7 +840,7 @@ namespace Microsoft.AspNet.Routing.Template
.Returns(true) .Returns(true)
.Verifiable(); .Verifiable();
TemplateRoute route = CreateRoute( var route = CreateRoute(
"{p1}/{p2}", "{p1}/{p2}",
new { p2 = "catchall" }, new { p2 = "catchall" },
true, true,
@ -1623,7 +1351,7 @@ namespace Microsoft.AspNet.Routing.Template
dataTokens: dataToken); dataTokens: dataToken);
// Assert // Assert
var templateRoute = (TemplateRoute)routeBuilder.Routes[0]; var templateRoute = (Route)routeBuilder.Routes[0];
// Assert // Assert
Assert.Equal(expectedDictionary.Count, templateRoute.DataTokens.Count); Assert.Equal(expectedDictionary.Count, templateRoute.DataTokens.Count);
@ -1664,7 +1392,7 @@ namespace Microsoft.AspNet.Routing.Template
defaults: null, defaults: null,
constraints: new { controller = "a.*", action = mockConstraint }); constraints: new { controller = "a.*", action = mockConstraint });
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints; var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
// Assert // Assert
Assert.Equal(2, constraints.Count); Assert.Equal(2, constraints.Count);
@ -1685,7 +1413,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: new { id = "1*" }); constraints: new { id = "1*" });
// Assert // Assert
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints; var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
Assert.Equal(1, constraints.Count); Assert.Equal(1, constraints.Count);
var constraint = (CompositeRouteConstraint)constraints["id"]; var constraint = (CompositeRouteConstraint)constraints["id"];
Assert.IsType<CompositeRouteConstraint>(constraint); Assert.IsType<CompositeRouteConstraint>(constraint);
@ -1706,7 +1434,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: null); constraints: null);
// Assert // Assert
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints; var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
Assert.Equal(1, constraints.Count); Assert.Equal(1, constraints.Count);
Assert.IsType<IntRouteConstraint>(constraints["id"]); Assert.IsType<IntRouteConstraint>(constraints["id"]);
} }
@ -1720,7 +1448,7 @@ namespace Microsoft.AspNet.Routing.Template
routeBuilder.MapRoute(name: "RouteName", template: "{controller}/{action}", defaults: null); routeBuilder.MapRoute(name: "RouteName", template: "{controller}/{action}", defaults: null);
// Act // Act
var name = ((TemplateRoute)routeBuilder.Routes[0]).Name; var name = ((Route)routeBuilder.Routes[0]).Name;
// Assert // Assert
Assert.Equal("RouteName", name); Assert.Equal("RouteName", name);
@ -1738,7 +1466,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: null); constraints: null);
// Act // Act
var name = ((TemplateRoute)routeBuilder.Routes[0]).Name; var name = ((Route)routeBuilder.Routes[0]).Name;
// Assert // Assert
Assert.Equal("RouteName", name); Assert.Equal("RouteName", name);
@ -1761,7 +1489,7 @@ namespace Microsoft.AspNet.Routing.Template
{ {
var routeBuilder = new RouteBuilder(); var routeBuilder = new RouteBuilder();
routeBuilder.DefaultHandler = new Mock<IRouter>().Object; routeBuilder.DefaultHandler = new RouteHandler(NullHandler);
var serviceProviderMock = new Mock<IServiceProvider>(); var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock.Setup(o => o.GetService(typeof(IInlineConstraintResolver))) serviceProviderMock.Setup(o => o.GetService(typeof(IInlineConstraintResolver)))
@ -1771,9 +1499,9 @@ namespace Microsoft.AspNet.Routing.Template
return routeBuilder; return routeBuilder;
} }
private static TemplateRoute CreateRoute(string routeName, string template, bool handleRequest = true) private static Route CreateRoute(string routeName, string template, bool handleRequest = true)
{ {
return new TemplateRoute( return new Route(
CreateTarget(handleRequest), CreateTarget(handleRequest),
routeName, routeName,
template, template,
@ -1783,49 +1511,51 @@ namespace Microsoft.AspNet.Routing.Template
inlineConstraintResolver: _inlineConstraintResolver); inlineConstraintResolver: _inlineConstraintResolver);
} }
private static TemplateRoute CreateRoute(string template, bool handleRequest = true) private static Route CreateRoute(string template, bool handleRequest = true)
{ {
return new TemplateRoute(CreateTarget(handleRequest), template, _inlineConstraintResolver); return new Route(CreateTarget(handleRequest), template, _inlineConstraintResolver);
} }
private static TemplateRoute CreateRoute(string template, private static Route CreateRoute(
object defaults, string template,
bool handleRequest = true, object defaults,
object constraints = null, bool handleRequest = true,
object dataTokens = null) object constraints = null,
object dataTokens = null)
{ {
return new TemplateRoute(CreateTarget(handleRequest), return new Route(
template, CreateTarget(handleRequest),
new RouteValueDictionary(defaults), template,
(constraints as IDictionary<string, object>) ?? new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints), new RouteValueDictionary(constraints),
(dataTokens as IDictionary<string, object>) ?? new RouteValueDictionary(dataTokens),
new RouteValueDictionary(dataTokens), _inlineConstraintResolver);
_inlineConstraintResolver);
} }
private static TemplateRoute CreateRoute(IRouter target, string template) private static Route CreateRoute(IRouter target, string template)
{ {
return new TemplateRoute(target, return new Route(
template, target,
new RouteValueDictionary(), template,
constraints: null, new RouteValueDictionary(),
dataTokens: null, constraints: null,
inlineConstraintResolver: _inlineConstraintResolver); dataTokens: null,
inlineConstraintResolver: _inlineConstraintResolver);
} }
private static TemplateRoute CreateRoute( private static Route CreateRoute(
IRouter target, IRouter target,
string template, string template,
object defaults, object defaults,
RouteValueDictionary dataTokens = null) RouteValueDictionary dataTokens = null)
{ {
return new TemplateRoute(target, return new Route(
template, target,
new RouteValueDictionary(defaults), template,
constraints: null, new RouteValueDictionary(defaults),
dataTokens: dataTokens, constraints: null,
inlineConstraintResolver: _inlineConstraintResolver); dataTokens: dataTokens,
inlineConstraintResolver: _inlineConstraintResolver);
} }
private static IRouter CreateTarget(bool handleRequest = true) private static IRouter CreateTarget(bool handleRequest = true)
@ -1837,7 +1567,7 @@ namespace Microsoft.AspNet.Routing.Template
target target
.Setup(e => e.RouteAsync(It.IsAny<RouteContext>())) .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => c.IsHandled = handleRequest) .Callback<RouteContext>((c) => c.Handler = handleRequest ? NullHandler : null)
.Returns(Task.FromResult<object>(null)); .Returns(Task.FromResult<object>(null));
return target.Object; return target.Object;

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Routing
public Task RouteAsync(RouteContext context) public Task RouteAsync(RouteContext context)
{ {
context.IsHandled = _isHandled; context.Handler = _isHandled ? (RequestDelegate)((c) => Task.FromResult(0)) : null;
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
} }

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Routing.Tests
constraints: null); constraints: null);
// Assert // Assert
var defaults = ((Template.TemplateRoute)routeBuilder.Routes[0]).Defaults; var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
Assert.Equal("12", defaults["id"]); Assert.Equal("12", defaults["id"]);
} }
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Routing.Tests
constraints: null); constraints: null);
// Assert // Assert
var defaults = ((Template.TemplateRoute)routeBuilder.Routes[0]).Defaults; var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
Assert.Equal(value, defaults["p1"]); Assert.Equal(value, defaults["p1"]);
} }

View File

@ -17,6 +17,8 @@ namespace Microsoft.AspNet.Routing.Tree
{ {
public class TreeRouterTest public class TreeRouterTest
{ {
private static readonly RequestDelegate NullHandler = (c) => Task.FromResult(0);
[Theory] [Theory]
[InlineData("template/5", "template/{parameter:int}")] [InlineData("template/5", "template/{parameter:int}")]
[InlineData("template/5", "template/{parameter}")] [InlineData("template/5", "template/{parameter}")]
@ -36,15 +38,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, firstTemplate); var expectedRouteGroup = string.Format("{0}&&{1}", 0, firstTemplate);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, firstTemplate, order: 0); var firstRoute = CreateMatchingEntry(next.Object, firstTemplate, order: 0);
var secondRoute = CreateMatchingEntry(next.Object, secondTemplate, order: 0); var secondRoute = CreateMatchingEntry(next.Object, secondTemplate, order: 0);
@ -84,15 +83,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, secondTemplate); var expectedRouteGroup = string.Format("{0}&&{1}", 0, secondTemplate);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, firstTemplate, order: 1); var firstRoute = CreateMatchingEntry(next.Object, firstTemplate, order: 1);
var secondRoute = CreateMatchingEntry(next.Object, secondTemplate, order: 0); var secondRoute = CreateMatchingEntry(next.Object, secondTemplate, order: 0);
@ -126,15 +122,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template); var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 1); var firstRoute = CreateMatchingEntry(next.Object, template, order: 1);
var secondRoute = CreateMatchingEntry(next.Object, template, order: 0); var secondRoute = CreateMatchingEntry(next.Object, template, order: 0);
@ -166,15 +159,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, first); var expectedRouteGroup = string.Format("{0}&&{1}", 0, first);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var secondRouter = new Mock<IRouter>(MockBehavior.Strict); var secondRouter = new Mock<IRouter>(MockBehavior.Strict);
@ -211,15 +201,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template); var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 0); var firstRoute = CreateMatchingEntry(next.Object, template, order: 0);
@ -239,12 +226,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Assert // Assert
if (expectedResult) if (expectedResult)
{ {
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]); Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
} }
else else
{ {
Assert.False(context.IsHandled); Assert.Null(context.Handler);
} }
} }
@ -277,15 +264,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template); var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 0); var firstRoute = CreateMatchingEntry(next.Object, template, order: 0);
@ -300,7 +284,7 @@ namespace Microsoft.AspNet.Routing.Tree
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.True(context.IsHandled); Assert.NotNull(context.Handler);
if (p1 != null) if (p1 != null)
{ {
Assert.Equal(p1, context.RouteData.Values["p1"]); Assert.Equal(p1, context.RouteData.Values["p1"]);
@ -335,15 +319,12 @@ namespace Microsoft.AspNet.Routing.Tree
// Arrange // Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template); var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) next
.Callback(callBack) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Returns(Task.FromResult(true)) .Callback<RouteContext>(c => c.Handler = NullHandler)
.Verifiable(); .Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 0); var firstRoute = CreateMatchingEntry(next.Object, template, order: 0);
@ -359,7 +340,7 @@ namespace Microsoft.AspNet.Routing.Tree
await route.RouteAsync(context); await route.RouteAsync(context);
// Assert // Assert
Assert.False(context.IsHandled); Assert.Null(context.Handler);
} }
[Theory] [Theory]
@ -1358,15 +1339,17 @@ namespace Microsoft.AspNet.Routing.Tree
{ {
// Arrange // Arrange
RouteData nestedRouteData = null; RouteData nestedRouteData = null;
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next next
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => .Callback<RouteContext>(c =>
{ {
nestedRouteData = c.RouteData; nestedRouteData = c.RouteData;
c.IsHandled = true; c.Handler = NullHandler;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true))
.Verifiable();
var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0); var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0);
var route = CreateAttributeRoute(next.Object, entry); var route = CreateAttributeRoute(next.Object, entry);
@ -1399,11 +1382,9 @@ namespace Microsoft.AspNet.Routing.Tree
var router = new Mock<IRouter>(); var router = new Mock<IRouter>();
router router
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => .Callback<RouteContext>(c => c.Handler = NullHandler)
{ .Returns(Task.FromResult(true))
c.IsHandled = true; .Verifiable();
})
.Returns(Task.FromResult(true));
var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0); var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0);
var route = CreateAttributeRoute(router.Object, entry); var route = CreateAttributeRoute(router.Object, entry);
@ -1427,11 +1408,9 @@ namespace Microsoft.AspNet.Routing.Tree
var router = new Mock<IRouter>(); var router = new Mock<IRouter>();
router router
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => .Callback<RouteContext>(c => c.Handler = NullHandler)
{ .Returns(Task.FromResult(true))
c.IsHandled = true; .Verifiable();
})
.Returns(Task.FromResult(true));
var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0); var entry = CreateMatchingEntry(router.Object, "Foo/{*path}", order: 0);
var route = CreateAttributeRoute(router.Object, entry); var route = CreateAttributeRoute(router.Object, entry);
@ -1456,12 +1435,12 @@ namespace Microsoft.AspNet.Routing.Tree
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next next
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => .Callback<RouteContext>(c =>
{ {
nestedRouteData = c.RouteData; nestedRouteData = c.RouteData;
c.IsHandled = false;
}) })
.Returns(Task.FromResult(true)); .Returns(Task.FromResult(true))
.Verifiable();
var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0); var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0);
var route = CreateAttributeRoute(next.Object, entry); var route = CreateAttributeRoute(next.Object, entry);
@ -1499,10 +1478,9 @@ namespace Microsoft.AspNet.Routing.Tree
var next = new Mock<IRouter>(); var next = new Mock<IRouter>();
next next
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>())) .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>((c) => .Callback<RouteContext>(c =>
{ {
nestedRouteData = c.RouteData; nestedRouteData = c.RouteData;
c.IsHandled = false;
}) })
.Throws(new Exception()); .Throws(new Exception());
@ -1755,7 +1733,7 @@ namespace Microsoft.AspNet.Routing.Tree
return builder.Build(version: 1); return builder.Build(version: 1);
} }
private static IReadOnlyDictionary<string, IRouteConstraint> GetRouteConstriants( private static IDictionary<string, IRouteConstraint> GetRouteConstriants(
IInlineConstraintResolver inlineConstraintResolver, IInlineConstraintResolver inlineConstraintResolver,
string template, string template,
RouteTemplate parsedRouteTemplate) RouteTemplate parsedRouteTemplate)
@ -1797,11 +1775,11 @@ namespace Microsoft.AspNet.Routing.Tree
{ {
if (MatchingDelegate == null) if (MatchingDelegate == null)
{ {
context.IsHandled = true; context.Handler = NullHandler;
} }
else else
{ {
context.IsHandled = MatchingDelegate(context); context.Handler = MatchingDelegate(context) ? NullHandler : null;
} }
return Task.FromResult(true); return Task.FromResult(true);