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
{
internal class PrefixRoute : IRouter
public class PrefixRoute : IRouter
{
private readonly IRouter _target;
private readonly IRouteHandler _target;
private readonly string _prefix;
public PrefixRoute(IRouter target, string prefix)
public PrefixRoute(IRouteHandler target, string prefix)
{
_target = target;
@ -34,7 +34,7 @@ namespace RoutingSample.Web
_prefix = prefix;
}
public async Task RouteAsync(RouteContext context)
public Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;
if (requestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase))
@ -44,12 +44,14 @@ namespace RoutingSample.Web
var lastCharacter = requestPath[_prefix.Length];
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)

View File

@ -1,34 +1,17 @@
// 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.Routing;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
namespace RoutingSample.Web
{
public static class RouteBuilderExtensions
{
public static IRouteBuilder AddPrefixRoute(this IRouteBuilder routeBuilder,
string prefix)
{
if (routeBuilder.DefaultHandler == null)
{
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)
public static IRouteBuilder AddPrefixRoute(
this IRouteBuilder routeBuilder,
string prefix,
IRouteHandler handler)
{
routeBuilder.Routes.Add(new PrefixRoute(handler, prefix));
return routeBuilder;
@ -45,7 +28,7 @@ namespace RoutingSample.Web
var constraintResolver = routeBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
var route = new TemplateRoute(
var route = new Route(
target: routeBuilder.DefaultHandler,
routeTemplate: routeTemplate,
defaults: defaultsDictionary,

View File

@ -1,12 +1,9 @@
// 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.Text.RegularExpressions;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
namespace RoutingSample.Web
@ -20,46 +17,20 @@ namespace RoutingSample.Web
public void Configure(IApplicationBuilder builder)
{
var endpoint1 = new DelegateRouteEndpoint(async (context) =>
await context
.HttpContext
.Response
.WriteAsync(
"match1, route values -" + context.RouteData.Values.Print()));
var endpoint1 = new RouteHandler((c) =>
{
return c.Response.WriteAsync($"match1, route values - {string.Join(", ", c.GetRouteData().Values)}");
});
var endpoint2 = new DelegateRouteEndpoint(async (context) =>
await context
.HttpContext
.Response
.WriteAsync("Hello, World!"));
var endpoint2 = new RouteHandler((c) => c.Response.WriteAsync("Hello, World!"));
var routeBuilder = new RouteBuilder();
routeBuilder.DefaultHandler = endpoint1;
routeBuilder.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" });
var routeBuilder = new RouteBuilder()
{
DefaultHandler = endpoint1,
ServiceProvider = builder.ApplicationServices,
};
routeBuilder.AddPrefixRoute("api/store", endpoint1);
routeBuilder.AddPrefixRoute("hello/world", endpoint2);
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
{
/// <summary>
/// A context object for <see cref="IRouter.RouteAsync(RouteContext)"/>.
/// </summary>
public class RouteContext
{
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)
{
HttpContext = httpContext;
@ -17,10 +24,20 @@ namespace Microsoft.AspNet.Routing
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
{
get

View File

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

View File

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

View File

@ -7,15 +7,20 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Routing
{
/// <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>
public interface IRouteBuilder
{
/// <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>
IRouter DefaultHandler { get; set; }
/// <summary>
/// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <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.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Internal;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
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 readonly IReadOnlyDictionary<string, object> _dataTokens;
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 TemplateMatcher _matcher;
private TemplateBinder _binder;
private ILogger _logger;
private ILogger _constraintLogger;
public TemplateRoute(
IRouter target,
string routeTemplate,
IInlineConstraintResolver inlineConstraintResolver)
: this(
target,
routeTemplate,
defaults: null,
constraints: null,
dataTokens: null,
inlineConstraintResolver: inlineConstraintResolver)
{
}
public TemplateRoute(
IRouter target,
string routeTemplate,
IDictionary<string, object> defaults,
public RouteBase(
string template,
string name,
IInlineConstraintResolver constraintResolver,
RouteValueDictionary defaults,
IDictionary<string, object> constraints,
IDictionary<string, object> dataTokens,
IInlineConstraintResolver inlineConstraintResolver)
: this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
RouteValueDictionary dataTokens)
{
}
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)
if (constraintResolver == null)
{
throw new ArgumentNullException(nameof(target));
throw new ArgumentNullException(nameof(constraintResolver));
}
_target = target;
_routeTemplate = routeTemplate ?? string.Empty;
Name = routeName;
_dataTokens = dataTokens == null ? new RouteValueDictionary() : new RouteValueDictionary(dataTokens);
template = template ?? string.Empty;
Name = name;
ConstraintResolver = constraintResolver;
DataTokens = dataTokens ?? new RouteValueDictionary();
// 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.
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
ParsedTemplate = TemplateParser.Parse(template);
_constraints = GetConstraints(inlineConstraintResolver, RouteTemplate, _parsedTemplate, constraints);
_defaults = GetDefaults(_parsedTemplate, defaults);
_matcher = new TemplateMatcher(_parsedTemplate, Defaults);
_binder = new TemplateBinder(_parsedTemplate, Defaults);
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
Defaults = GetDefaults(ParsedTemplate, defaults);
}
public string Name { get; private set; }
public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
public IReadOnlyDictionary<string, object> Defaults
{
get { return _defaults; }
}
protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
public IReadOnlyDictionary<string, object> DataTokens
{
get { return _dataTokens; }
}
public virtual RouteValueDictionary DataTokens { get; protected set; }
public RouteTemplate ParsedTemplate
{
get { return _parsedTemplate; }
}
public virtual RouteValueDictionary Defaults { get; protected set; }
public string RouteTemplate
{
get { return _routeTemplate; }
}
public virtual string Name { get; protected set; }
public IReadOnlyDictionary<string, IRouteConstraint> Constraints
{
get { return _constraints; }
}
public virtual RouteTemplate ParsedTemplate { get; protected set; }
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)
{
throw new ArgumentNullException(nameof(context));
}
EnsureMatcher();
EnsureLoggers(context.HttpContext);
var requestPath = context.HttpContext.Request.Path;
@ -121,57 +78,46 @@ namespace Microsoft.AspNet.Routing.Template
if (values == null)
{
// If we got back a null value set, that means the URI did not match
return;
return TaskCache.CompletedTask;
}
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
// Perf: Avoid accessing data tokens if you don't need to write to it, these dictionaries are all
// Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all
// created lazily.
if (_dataTokens.Count > 0)
if (DataTokens.Count > 0)
{
MergeValues(newRouteData.DataTokens, _dataTokens);
MergeValues(context.RouteData.DataTokens, DataTokens);
}
newRouteData.Routers.Add(_target);
MergeValues(newRouteData.Values, values);
if (values.Count > 0)
{
MergeValues(context.RouteData.Values, values);
}
if (!RouteConstraintMatcher.Match(
Constraints,
newRouteData.Values,
context.RouteData.Values,
context.HttpContext,
this,
RouteDirection.IncomingRequest,
_constraintLogger))
{
return;
return TaskCache.CompletedTask;
}
_logger.LogDebug(
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.",
Name,
RouteTemplate);
ParsedTemplate.TemplateText);
try
{
context.RouteData = newRouteData;
await _target.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
return OnRouteMatched(context);
}
/// <inheritdoc />
public virtual VirtualPathData GetVirtualPath(VirtualPathContext context)
{
EnsureBinder();
EnsureLoggers(context.HttpContext);
var values = _binder.GetValues(context.AmbientValues, context.Values);
if (values == null)
{
@ -179,7 +125,6 @@ namespace Microsoft.AspNet.Routing.Template
return null;
}
EnsureLoggers(context.HttpContext);
if (!RouteConstraintMatcher.Match(
Constraints,
values.CombinedValues,
@ -191,7 +136,9 @@ namespace Microsoft.AspNet.Routing.Template
return null;
}
var pathData = _target.GetVirtualPath(context);
context.Values = values.CombinedValues;
var pathData = OnVirtualPathGenerated(context);
if (pathData != null)
{
// 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.
// When we still cannot produce a value, this should return null.
var tempPath = _binder.BindValues(values.AcceptedValues);
if (tempPath == null)
var virtualPath = _binder.BindValues(values.AcceptedValues);
if (virtualPath == null)
{
return null;
}
pathData = new VirtualPathData(this, tempPath);
pathData = new VirtualPathData(this, virtualPath);
if (DataTokens != null)
{
foreach (var dataToken in DataTokens)
@ -220,13 +167,12 @@ namespace Microsoft.AspNet.Routing.Template
return pathData;
}
private static IReadOnlyDictionary<string, IRouteConstraint> GetConstraints(
protected static IDictionary<string, IRouteConstraint> GetConstraints(
IInlineConstraintResolver inlineConstraintResolver,
string template,
RouteTemplate parsedTemplate,
IDictionary<string, object> constraints)
{
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, template);
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
if (constraints != null)
{
@ -252,12 +198,10 @@ namespace Microsoft.AspNet.Routing.Template
return constraintBuilder.Build();
}
private static RouteValueDictionary GetDefaults(
protected static RouteValueDictionary GetDefaults(
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);
foreach (var parameter in parsedTemplate.Parameters)
@ -293,17 +237,11 @@ namespace Microsoft.AspNet.Routing.Template
}
}
// Needed because IDictionary<> is not an IReadOnlyDictionary<>
private static void MergeValues(
IDictionary<string, object> destination,
IReadOnlyDictionary<string, object> values)
private void EnsureBinder()
{
foreach (var kvp in values)
if (_binder == null)
{
// This will replace the original value for the specified key.
// Values from the matched route will take preference over previous
// data in the route context.
destination[kvp.Key] = kvp.Value;
_binder = new TemplateBinder(ParsedTemplate, Defaults);
}
}
@ -312,14 +250,22 @@ namespace Microsoft.AspNet.Routing.Template
if (_logger == null)
{
var factory = context.RequestServices.GetRequiredService<ILoggerFactory>();
_logger = factory.CreateLogger<TemplateRoute>();
_logger = factory.CreateLogger(typeof(RouteBase).FullName);
_constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
}
}
private void EnsureMatcher()
{
if (_matcher == null)
{
_matcher = new TemplateMatcher(ParsedTemplate, Defaults);
}
}
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 IList<IRouter> Routes
{
get;
private set;
}
public IList<IRouter> Routes { get; }
public IRouter Build()
{

View File

@ -21,9 +21,10 @@ namespace Microsoft.AspNet.Builder
/// <param name="name">The name 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>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder,
string name,
string template)
public static IRouteBuilder MapRoute(
this IRouteBuilder routeBuilder,
string name,
string template)
{
MapRoute(routeBuilder, name, template, defaults: null);
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="name">The name 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>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder,
string name,
string template,
object defaults)
public static IRouteBuilder MapRoute(
this IRouteBuilder routeBuilder,
string name,
string template,
object defaults)
{
return MapRoute(routeBuilder, name, template, defaults, constraints: null);
}
/// <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>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name 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="constraints">An object that contains constraints for the route. The object's properties represent the names and values of the constraints.</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="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>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints)
public static IRouteBuilder MapRoute(
this IRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints)
{
return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
}
/// <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>
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
/// <param name="name">The name 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="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>
/// <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="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>
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints,
object dataTokens)
public static IRouteBuilder MapRoute(
this IRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints,
object dataTokens)
{
if (routeBuilder.DefaultHandler == null)
{
@ -88,26 +112,17 @@ namespace Microsoft.AspNet.Builder
var inlineConstraintResolver = routeBuilder
.ServiceProvider
.GetRequiredService<IInlineConstraintResolver>();
routeBuilder.Routes.Add(new TemplateRoute(routeBuilder.DefaultHandler,
name,
template,
ObjectToDictionary(defaults),
ObjectToDictionary(constraints),
ObjectToDictionary(dataTokens),
inlineConstraintResolver));
routeBuilder.Routes.Add(new Route(
routeBuilder.DefaultHandler,
name,
template,
new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints),
new RouteValueDictionary(dataTokens),
inlineConstraintResolver));
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.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection;
@ -69,14 +68,15 @@ namespace Microsoft.AspNet.Routing
context.RouteData = newRouteData;
await route.RouteAsync(context);
if (context.IsHandled)
if (context.Handler != null)
{
break;
}
}
finally
{
if (!context.IsHandled)
if (context.Handler == null)
{
context.RouteData = oldRouteData;
}

View File

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

View File

@ -10,12 +10,13 @@ namespace Microsoft.AspNet.Routing
{
public static class RouteConstraintMatcher
{
public static bool Match(IReadOnlyDictionary<string, IRouteConstraint> constraints,
IDictionary<string, object> routeValues,
HttpContext httpContext,
IRouter route,
RouteDirection routeDirection,
ILogger logger)
public static bool Match(
IDictionary<string, IRouteConstraint> constraints,
IDictionary<string, object> routeValues,
HttpContext httpContext,
IRouter route,
RouteDirection routeDirection,
ILogger logger)
{
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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Routing.Logging.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Builder
@ -35,12 +33,26 @@ namespace Microsoft.AspNet.Builder
await _router.RouteAsync(context);
if (!context.IsHandled)
if (context.Handler == null)
{
_logger.LogDebug("Request did not match any routes.");
httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
{
RouteData = context.RouteData,
};
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
{
/// <summary>
/// The <see cref="TemplateBinder"/>.
/// Gets or sets the <see cref="TemplateBinder"/>.
/// </summary>
public TemplateBinder Binder { get; set; }
/// <summary>
/// The route constraints.
/// Gets or sets the route constraints.
/// </summary>
public IReadOnlyDictionary<string, IRouteConstraint> Constraints { get; set; }
public IDictionary<string, IRouteConstraint> Constraints { get; set; }
/// <summary>
/// The route defaults.
/// Gets or sets the route defaults.
/// </summary>
public IReadOnlyDictionary<string, object> Defaults { get; set; }
public IDictionary<string, object> Defaults { get; set; }
/// <summary>
/// The order of the template.
/// Gets or sets the order of the template.
/// </summary>
public int Order { get; set; }
/// <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>
public decimal GenerationPrecedence { get; set; }
/// <summary>
/// The name of the route.
/// Gets or sets the name of the route.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The route group.
/// Gets or sets the route group.
/// </summary>
public string RouteGroup { get; set; }
/// <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>
public IDictionary<string, object> RequiredLinkValues { get; set; }
/// <summary>
/// The <see cref="Template"/>.
/// Gets or sets the <see cref="Template"/>.
/// </summary>
public RouteTemplate Template { get; set; }
}

View File

@ -45,6 +45,6 @@ namespace Microsoft.AspNet.Routing.Tree
/// <summary>
/// The route constraints.
/// </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
{
await match.Entry.Target.RouteAsync(context);
if (context.Handler != null)
{
return;
}
}
finally
{
if (!context.IsHandled)
if (context.Handler == null)
{
// Restore the original values to prevent polluting the route data.
context.RouteData = oldRouteData;
}
}
if (context.IsHandled)
{
return;
}
}
}
}

View File

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

View File

@ -8,54 +8,19 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Testing;
using Microsoft.Extensions.Logging.Internal;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Logging;
using Moq;
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 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]
public void CreateTemplate_InlineConstraint_Regex_Malformed()
{
@ -67,7 +32,7 @@ namespace Microsoft.AspNet.Routing.Template
"'IInlineConstraintResolverProxy'.";
var exception = Assert.Throws<InvalidOperationException>(
() => new TemplateRoute(
() => new Route(
mockTarget.Object,
template,
defaults: null,
@ -78,67 +43,6 @@ namespace Microsoft.AspNet.Routing.Template
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]
public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches()
{
@ -160,11 +64,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -187,12 +91,11 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.True(context.RouteData.Values.ContainsKey("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("Friday", context.RouteData.DataTokens["today"]);
Assert.NotSame(originalDataTokens, context.RouteData.DataTokens);
Assert.NotSame(route.DataTokens, context.RouteData.DataTokens);
Assert.Same(originalDataTokens, context.RouteData.DataTokens);
}
[Fact]
@ -215,13 +118,13 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var constraint = new CapturingConstraint();
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -249,171 +152,10 @@ namespace Microsoft.AspNet.Routing.Template
Assert.Equal("USA", context.RouteData.Values["country"]);
Assert.True(context.RouteData.Values.ContainsKey("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("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);
}
@ -432,11 +174,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -451,7 +193,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
@ -474,11 +216,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -493,7 +235,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("ssn"));
Assert.Equal("123-456-7890", routeValues["ssn"]);
@ -516,11 +258,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -535,7 +277,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.NotNull(routeValues);
Assert.False(routeValues.ContainsKey("id"));
Assert.False(context.RouteData.Values.ContainsKey("id"));
@ -556,14 +298,14 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var constraints = new Dictionary<string, object>();
constraints.Add("id", new RangeRouteConstraint(1, 20));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -585,7 +327,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.True(routeValues.ContainsKey("id"));
Assert.Equal("5", routeValues["id"]);
@ -608,11 +350,11 @@ namespace Microsoft.AspNet.Routing.Template
.Callback<RouteContext>(ctx =>
{
routeValues = ctx.RouteData.Values;
ctx.IsHandled = true;
ctx.Handler = NullHandler;
})
.Returns(Task.FromResult(true));
var route = new TemplateRoute(
var route = new Route(
mockTarget.Object,
template,
defaults: null,
@ -627,7 +369,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
Assert.Null(context.Handler);
}
#region Route Matching
@ -644,7 +386,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Index", context.RouteData.Values["action"]);
@ -661,7 +403,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(0, context.RouteData.Values.Count);
}
@ -676,7 +418,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Index", context.RouteData.Values["action"]);
@ -695,7 +437,7 @@ namespace Microsoft.AspNet.Routing.Template
// Act
await route.RouteAsync(context);
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
// This should not affect the route - RouteData.DataTokens is a copy
context.RouteData.DataTokens.Add("company", "contoso");
@ -716,7 +458,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
Assert.Null(context.Handler);
}
[Fact]
@ -730,25 +472,11 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
Assert.Null(context.Handler);
// Issue #16 tracks this.
Assert.Empty(context.RouteData.Values);
}
[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);
var value = Assert.Single(context.RouteData.Values);
Assert.Equal("controller", value.Key);
Assert.Equal("Home", Assert.IsType<string>(value.Value));
}
[Fact]
@ -763,7 +491,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(1, context.RouteData.Routers.Count);
Assert.Same(target, context.RouteData.Routers[0]);
}
@ -793,7 +521,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(3, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]);
@ -811,7 +539,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(2, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]);
@ -828,7 +556,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
Assert.NotNull(context.Handler);
Assert.Equal(3, context.RouteData.Values.Count);
Assert.Equal("Home", context.RouteData.Values["controller"]);
Assert.Equal("Create", context.RouteData.Values["action"]);
@ -846,7 +574,7 @@ namespace Microsoft.AspNet.Routing.Template
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
Assert.Null(context.Handler);
}
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
@ -1021,14 +749,14 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange
var context = CreateVirtualPathContext(new { p1 = "abcd" });
TemplateRoute r = CreateRoute(
var route = CreateRoute(
"{p1}/{p2}",
new { p2 = "catchall" },
true,
new RouteValueDictionary(new { p2 = "\\d{4}" }));
// Act
var virtualPath = r.GetVirtualPath(context);
var virtualPath = route.GetVirtualPath(context);
// Assert
Assert.Null(virtualPath);
@ -1062,14 +790,14 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange
var context = CreateVirtualPathContext(new { p1 = "abcd" });
TemplateRoute r = CreateRoute(
var route = CreateRoute(
"{p1}/{*p2}",
new { p2 = "catchall" },
true,
new RouteValueDictionary(new { p2 = "\\d{4}" }));
// Act
var virtualPath = r.GetVirtualPath(context);
var virtualPath = route.GetVirtualPath(context);
// Assert
Assert.Null(virtualPath);
@ -1081,7 +809,7 @@ namespace Microsoft.AspNet.Routing.Template
// Arrange
var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
TemplateRoute route = CreateRoute(
var route = CreateRoute(
"{p1}/{*p2}",
new { p2 = "catchall" },
true,
@ -1112,7 +840,7 @@ namespace Microsoft.AspNet.Routing.Template
.Returns(true)
.Verifiable();
TemplateRoute route = CreateRoute(
var route = CreateRoute(
"{p1}/{p2}",
new { p2 = "catchall" },
true,
@ -1623,7 +1351,7 @@ namespace Microsoft.AspNet.Routing.Template
dataTokens: dataToken);
// Assert
var templateRoute = (TemplateRoute)routeBuilder.Routes[0];
var templateRoute = (Route)routeBuilder.Routes[0];
// Assert
Assert.Equal(expectedDictionary.Count, templateRoute.DataTokens.Count);
@ -1664,7 +1392,7 @@ namespace Microsoft.AspNet.Routing.Template
defaults: null,
constraints: new { controller = "a.*", action = mockConstraint });
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints;
var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
// Assert
Assert.Equal(2, constraints.Count);
@ -1685,7 +1413,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: new { id = "1*" });
// Assert
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints;
var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
Assert.Equal(1, constraints.Count);
var constraint = (CompositeRouteConstraint)constraints["id"];
Assert.IsType<CompositeRouteConstraint>(constraint);
@ -1706,7 +1434,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: null);
// Assert
var constraints = ((TemplateRoute)routeBuilder.Routes[0]).Constraints;
var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
Assert.Equal(1, constraints.Count);
Assert.IsType<IntRouteConstraint>(constraints["id"]);
}
@ -1720,7 +1448,7 @@ namespace Microsoft.AspNet.Routing.Template
routeBuilder.MapRoute(name: "RouteName", template: "{controller}/{action}", defaults: null);
// Act
var name = ((TemplateRoute)routeBuilder.Routes[0]).Name;
var name = ((Route)routeBuilder.Routes[0]).Name;
// Assert
Assert.Equal("RouteName", name);
@ -1738,7 +1466,7 @@ namespace Microsoft.AspNet.Routing.Template
constraints: null);
// Act
var name = ((TemplateRoute)routeBuilder.Routes[0]).Name;
var name = ((Route)routeBuilder.Routes[0]).Name;
// Assert
Assert.Equal("RouteName", name);
@ -1761,7 +1489,7 @@ namespace Microsoft.AspNet.Routing.Template
{
var routeBuilder = new RouteBuilder();
routeBuilder.DefaultHandler = new Mock<IRouter>().Object;
routeBuilder.DefaultHandler = new RouteHandler(NullHandler);
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock.Setup(o => o.GetService(typeof(IInlineConstraintResolver)))
@ -1771,9 +1499,9 @@ namespace Microsoft.AspNet.Routing.Template
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),
routeName,
template,
@ -1783,49 +1511,51 @@ namespace Microsoft.AspNet.Routing.Template
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,
object defaults,
bool handleRequest = true,
object constraints = null,
object dataTokens = null)
private static Route CreateRoute(
string template,
object defaults,
bool handleRequest = true,
object constraints = null,
object dataTokens = null)
{
return new TemplateRoute(CreateTarget(handleRequest),
template,
new RouteValueDictionary(defaults),
(constraints as IDictionary<string, object>) ??
new RouteValueDictionary(constraints),
(dataTokens as IDictionary<string, object>) ??
new RouteValueDictionary(dataTokens),
_inlineConstraintResolver);
return new Route(
CreateTarget(handleRequest),
template,
new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints),
new RouteValueDictionary(dataTokens),
_inlineConstraintResolver);
}
private static TemplateRoute CreateRoute(IRouter target, string template)
private static Route CreateRoute(IRouter target, string template)
{
return new TemplateRoute(target,
template,
new RouteValueDictionary(),
constraints: null,
dataTokens: null,
inlineConstraintResolver: _inlineConstraintResolver);
return new Route(
target,
template,
new RouteValueDictionary(),
constraints: null,
dataTokens: null,
inlineConstraintResolver: _inlineConstraintResolver);
}
private static TemplateRoute CreateRoute(
private static Route CreateRoute(
IRouter target,
string template,
object defaults,
RouteValueDictionary dataTokens = null)
{
return new TemplateRoute(target,
template,
new RouteValueDictionary(defaults),
constraints: null,
dataTokens: dataTokens,
inlineConstraintResolver: _inlineConstraintResolver);
return new Route(
target,
template,
new RouteValueDictionary(defaults),
constraints: null,
dataTokens: dataTokens,
inlineConstraintResolver: _inlineConstraintResolver);
}
private static IRouter CreateTarget(bool handleRequest = true)
@ -1837,7 +1567,7 @@ namespace Microsoft.AspNet.Routing.Template
target
.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));
return target.Object;

View File

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

View File

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

View File

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