diff --git a/samples/RoutingSample/HttpContextRouteEndpoint.cs b/samples/RoutingSample/HttpContextRouteEndpoint.cs index 87511eaacf..da72d0b11f 100644 --- a/samples/RoutingSample/HttpContextRouteEndpoint.cs +++ b/samples/RoutingSample/HttpContextRouteEndpoint.cs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -using System; using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Routing; namespace RoutingSample { - public class HttpContextRouteEndpoint : IRouteEndpoint + public class HttpContextRouteEndpoint : IRouter { private readonly RequestDelegate _appFunc; @@ -16,10 +15,16 @@ namespace RoutingSample _appFunc = appFunc; } - public async Task Send(HttpContext context) + public async Task RouteAsync(RouteContext context) { - await _appFunc(context); - return true; + await _appFunc(context.HttpContext); + context.IsHandled = true; + } + + public void BindPath(BindPathContext context) + { + // We can generate a url for anything that the parent route deems OK. + context.IsBound = true; } } } diff --git a/samples/RoutingSample/PrefixRoute.cs b/samples/RoutingSample/PrefixRoute.cs index 735cf4f48d..7b89fcf975 100644 --- a/samples/RoutingSample/PrefixRoute.cs +++ b/samples/RoutingSample/PrefixRoute.cs @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.AspNet.Routing; namespace RoutingSample { - internal class PrefixRoute : IRoute + internal class PrefixRoute : IRouter { - private readonly IRouteEndpoint _endpoint; + private readonly IRouter _next; private readonly string _prefix; - public PrefixRoute(IRouteEndpoint endpoint, string prefix) + public PrefixRoute(IRouter next, string prefix) { - _endpoint = endpoint; + _next = next; if (prefix == null) { @@ -32,28 +33,26 @@ namespace RoutingSample _prefix = prefix; } - public RouteMatch Match(RouteContext context) + public async Task RouteAsync(RouteContext context) { if (context.RequestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)) { if (context.RequestPath.Length > _prefix.Length) { - char next = context.RequestPath[_prefix.Length]; - if (next != '/' && next != '#' && next != '?') + var lastCharacter = context.RequestPath[_prefix.Length]; + if (lastCharacter != '/' && lastCharacter != '#' && lastCharacter != '?') { - return null; + return; } } - return new RouteMatch(_endpoint); + await _next.RouteAsync(context); } - - return null; } - public RouteBindResult Bind(RouteBindContext context) + public void BindPath(BindPathContext context) { - return null; + // Do nothing } } } diff --git a/samples/RoutingSample/RouteBuilderExtensions.cs b/samples/RoutingSample/RouteBuilderExtensions.cs deleted file mode 100644 index 2848864774..0000000000 --- a/samples/RoutingSample/RouteBuilderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using Microsoft.AspNet.Routing; - -namespace RoutingSample -{ - public static class RouteBuilderExtensions - { - public static void AddPrefixRoute(this IRouteBuilder builder, string prefix) - { - builder.Routes.Add(new PrefixRoute(builder.Endpoint, prefix)); - } - } -} diff --git a/samples/RoutingSample/RouteCollectionExtensions.cs b/samples/RoutingSample/RouteCollectionExtensions.cs new file mode 100644 index 0000000000..68b07774dd --- /dev/null +++ b/samples/RoutingSample/RouteCollectionExtensions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Routing; + +namespace RoutingSample +{ + public static class RouteCollectionExtensions + { + public static IRouteCollection AddPrefixRoute(this IRouteCollection routes, string prefix) + { + return AddPrefixRoute(routes, prefix, routes.DefaultHandler); + } + + public static IRouteCollection AddPrefixRoute(this IRouteCollection routes, string prefix, IRouter handler) + { + routes.Add(new PrefixRoute(handler, prefix)); + return routes; + } + } +} diff --git a/samples/RoutingSample/Startup.cs b/samples/RoutingSample/Startup.cs index d77cc7530b..238390715a 100644 --- a/samples/RoutingSample/Startup.cs +++ b/samples/RoutingSample/Startup.cs @@ -26,13 +26,12 @@ namespace RoutingSample var endpoint1 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("match1")); var endpoint2 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("Hello, World!")); - var rb1 = new RouteBuilder(endpoint1, routes.Routes); - rb1.AddPrefixRoute("api/store"); - rb1.AddTemplateRoute("api/{controller}/{*extra}", new { controller = "Store" }); + routes.DefaultHandler = endpoint1; + routes.AddPrefixRoute("api/store"); + routes.AddTemplateRoute("api/{controller}/{*extra}", new { controller = "Store" }); - var rb2 = new RouteBuilder(endpoint2, routes.Routes); - rb2.AddPrefixRoute("hello/world"); - rb2.AddPrefixRoute(""); + routes.AddPrefixRoute("hello/world", endpoint2); + routes.AddPrefixRoute("", endpoint2); } } } diff --git a/src/Microsoft.AspNet.Routing/BindPathContext.cs b/src/Microsoft.AspNet.Routing/BindPathContext.cs new file mode 100644 index 0000000000..c2101fd4bf --- /dev/null +++ b/src/Microsoft.AspNet.Routing/BindPathContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Routing +{ + public class BindPathContext + { + public BindPathContext(HttpContext context, IDictionary ambientValues, IDictionary values) + { + Context = context; + AmbientValues = ambientValues; + Values = values; + } + + public IDictionary AmbientValues { get; private set; } + + public HttpContext Context { get; private set; } + + public bool IsBound { get; set; } + + public string Path { get; set; } + + public IDictionary Values { get; private set; } + } +} diff --git a/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs b/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs deleted file mode 100644 index 81616ed126..0000000000 --- a/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.AspNet.Routing -{ - public class DefaultRouteCollection : IRouteCollection - { - private readonly List _routes = new List(); - - public IRoute this[int index] - { - get { return _routes[index]; } - } - - public int Count - { - get { return _routes.Count; } - } - - public void Add(IRoute route) - { - _routes.Add(route); - } - } -} diff --git a/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs b/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs deleted file mode 100644 index 623a7a4776..0000000000 --- a/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Routing -{ - public class DefaultRouteEngine : IRouteEngine - { - public DefaultRouteEngine(IRouteCollection routes) - { - Routes = routes; - } - - public IRouteCollection Routes - { - get; - private set; - } - - public async Task Invoke(HttpContext context) - { - var routeContext = new RouteContext(context); - - for (var i = 0; i < Routes.Count; i++) - { - var route = Routes[i]; - - var match = route.Match(routeContext); - if (match != null) - { - context.SetFeature(new RouteValues(match.Values)); - - var accepted = await match.Endpoint.Send(context); - if (accepted) - { - return true; - } - } - } - - return false; - } - - public string GetUrl(HttpContext context, IDictionary values) - { - var routeBindContext = new RouteBindContext(context, values); - - for (var i = 0; i < Routes.Count; i++) - { - var route = Routes[i]; - - var result = route.Bind(routeBindContext); - if (result != null) - { - return result.Url; - } - } - - return null; - } - } -} diff --git a/src/Microsoft.AspNet.Routing/IRouteBuilder.cs b/src/Microsoft.AspNet.Routing/IRouteBuilder.cs deleted file mode 100644 index 87f67506cc..0000000000 --- a/src/Microsoft.AspNet.Routing/IRouteBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Routing -{ - public interface IRouteBuilder - { - IRouteEndpoint Endpoint - { - get; - } - - IRouteCollection Routes - { - get; - } - } -} diff --git a/src/Microsoft.AspNet.Routing/IRouteCollection.cs b/src/Microsoft.AspNet.Routing/IRouteCollection.cs index 85b9faf950..4290ac4751 100644 --- a/src/Microsoft.AspNet.Routing/IRouteCollection.cs +++ b/src/Microsoft.AspNet.Routing/IRouteCollection.cs @@ -1,19 +1,11 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. namespace Microsoft.AspNet.Routing { - public interface IRouteCollection + public interface IRouteCollection : IRouter { - IRoute this[int index] - { - get; - } + IRouter DefaultHandler { get; set; } - int Count - { - get; - } - - void Add(IRoute route); + void Add(IRouter router); } } diff --git a/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs b/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs deleted file mode 100644 index df74ee0259..0000000000 --- a/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Routing -{ - public interface IRouteEndpoint - { - Task Send(HttpContext context); - } -} diff --git a/src/Microsoft.AspNet.Routing/IRouteEngine.cs b/src/Microsoft.AspNet.Routing/IRouteEngine.cs deleted file mode 100644 index b2aa0b42af..0000000000 --- a/src/Microsoft.AspNet.Routing/IRouteEngine.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Routing -{ - public interface IRouteEngine - { - IRouteCollection Routes { get; } - - Task Invoke(HttpContext context); - - string GetUrl(HttpContext context, IDictionary values); - } -} diff --git a/src/Microsoft.AspNet.Routing/IRoute.cs b/src/Microsoft.AspNet.Routing/IRouter.cs similarity index 54% rename from src/Microsoft.AspNet.Routing/IRoute.cs rename to src/Microsoft.AspNet.Routing/IRouter.cs index d503837afc..d83dc7a1ec 100644 --- a/src/Microsoft.AspNet.Routing/IRoute.cs +++ b/src/Microsoft.AspNet.Routing/IRouter.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System.Threading.Tasks; + namespace Microsoft.AspNet.Routing { - public interface IRoute + public interface IRouter { - RouteMatch Match(RouteContext context); + Task RouteAsync(RouteContext context); - RouteBindResult Bind(RouteBindContext context); + void BindPath(BindPathContext context); } } diff --git a/src/Microsoft.AspNet.Routing/Owin/BuilderExtensions.cs b/src/Microsoft.AspNet.Routing/Owin/BuilderExtensions.cs index 032f990abf..8bb5c67e0a 100644 --- a/src/Microsoft.AspNet.Routing/Owin/BuilderExtensions.cs +++ b/src/Microsoft.AspNet.Routing/Owin/BuilderExtensions.cs @@ -6,14 +6,15 @@ namespace Microsoft.AspNet.Routing.Owin { public static class BuilderExtensions { - public static IRouteEngine UseRouter(this IBuilder builder) + public static IRouteCollection UseRouter(this IBuilder builder) { - var routes = new DefaultRouteCollection(); - var engine = new DefaultRouteEngine(routes); + return UseRouter(builder, new RouteCollection()); + } - builder.Use((next) => new RouterMiddleware(next, engine).Invoke); - - return engine; + public static IRouteCollection UseRouter(this IBuilder builder, IRouteCollection routes) + { + builder.Use((next) => new RouterMiddleware(next, routes).Invoke); + return routes; } } } diff --git a/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs b/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs index d1298faf09..b45578742f 100644 --- a/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs +++ b/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System.Net; using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; @@ -7,13 +8,13 @@ namespace Microsoft.AspNet.Routing.Owin { public class RouterMiddleware { - public RouterMiddleware(RequestDelegate next, IRouteEngine engine) + public RouterMiddleware(RequestDelegate next, IRouter route) { Next = next; - Engine = engine; + Route = route; } - private IRouteEngine Engine + private IRouter Route { get; set; @@ -25,11 +26,14 @@ namespace Microsoft.AspNet.Routing.Owin set; } - public async Task Invoke(HttpContext context) + public async Task Invoke(HttpContext httpContext) { - if (!(await Engine.Invoke(context))) + var context = new RouteContext(httpContext); + + await Route.RouteAsync(context); + if (!context.IsHandled) { - await Next.Invoke(context); + await Next.Invoke(httpContext); } } } diff --git a/src/Microsoft.AspNet.Routing/RouteBindContext.cs b/src/Microsoft.AspNet.Routing/RouteBindContext.cs deleted file mode 100644 index 6f9677f524..0000000000 --- a/src/Microsoft.AspNet.Routing/RouteBindContext.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Routing -{ - public class RouteBindContext - { - public RouteBindContext(HttpContext context, IDictionary values) - { - Context = context; - Values = values; - - if (Context != null) - { - var ambientValues = context.GetFeature(); - AmbientValues = ambientValues == null ? null : ambientValues.Values; - } - } - - public IDictionary AmbientValues { get; private set; } - - public HttpContext Context { get; private set; } - - public IDictionary Values { get; private set; } - } -} diff --git a/src/Microsoft.AspNet.Routing/RouteBindResult.cs b/src/Microsoft.AspNet.Routing/RouteBindResult.cs deleted file mode 100644 index ae81820bde..0000000000 --- a/src/Microsoft.AspNet.Routing/RouteBindResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Routing -{ - public class RouteBindResult - { - public RouteBindResult(string url) - { - Url = url; - } - - public string Url { get; private set; } - } -} diff --git a/src/Microsoft.AspNet.Routing/RouteBuilder.cs b/src/Microsoft.AspNet.Routing/RouteBuilder.cs deleted file mode 100644 index 638c983276..0000000000 --- a/src/Microsoft.AspNet.Routing/RouteBuilder.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Routing -{ - public class RouteBuilder : IRouteBuilder - { - public RouteBuilder(IRouteEndpoint endpoint, IRouteCollection routes) - { - Endpoint = endpoint; - Routes = routes; - } - public IRouteEndpoint Endpoint - { - get; - private set; - } - - public IRouteCollection Routes - { - get; - private set; - } - } -} diff --git a/src/Microsoft.AspNet.Routing/RouteCollection.cs b/src/Microsoft.AspNet.Routing/RouteCollection.cs new file mode 100644 index 0000000000..c85876f95e --- /dev/null +++ b/src/Microsoft.AspNet.Routing/RouteCollection.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Routing +{ + public class RouteCollection : IRouteCollection + { + private readonly List _routes = new List(); + + public IRouter this[int index] + { + get { return _routes[index]; } + } + + public int Count + { + get { return _routes.Count; } + } + + public IRouter DefaultHandler { get; set; } + + public void Add(IRouter router) + { + _routes.Add(router); + } + + public async virtual Task RouteAsync(RouteContext context) + { + for (var i = 0; i < Count; i++) + { + var route = this[i]; + + await route.RouteAsync(context); + if (context.IsHandled) + { + return; + } + } + } + + public virtual void BindPath(BindPathContext context) + { + for (var i = 0; i < Count; i++) + { + var route = this[i]; + + route.BindPath(context); + if (context.IsBound) + { + return; + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Routing/RouteContext.cs b/src/Microsoft.AspNet.Routing/RouteContext.cs index 48eb5157aa..cb1f20798d 100644 --- a/src/Microsoft.AspNet.Routing/RouteContext.cs +++ b/src/Microsoft.AspNet.Routing/RouteContext.cs @@ -1,28 +1,25 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System.Collections.Generic; using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Routing { public class RouteContext { - public RouteContext(HttpContext context) + public RouteContext(HttpContext httpContext) { - Context = context; + HttpContext = httpContext; - RequestPath = context.Request.Path.Value; + RequestPath = httpContext.Request.Path.Value; } - public HttpContext Context - { - get; - private set; - } + public HttpContext HttpContext { get; private set; } - public string RequestPath - { - get; - private set; - } + public bool IsHandled { get; set; } + + public string RequestPath { get; private set; } + + public IDictionary Values { get; set; } } } diff --git a/src/Microsoft.AspNet.Routing/RouteMatch.cs b/src/Microsoft.AspNet.Routing/RouteMatch.cs deleted file mode 100644 index 92d5ce20c0..0000000000 --- a/src/Microsoft.AspNet.Routing/RouteMatch.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.AspNet.Routing -{ - /// - /// The result of matching a route. Includes an to invoke and an optional collection of - /// captured values. - /// - public class RouteMatch - { - public RouteMatch(IRouteEndpoint endpoint) - : this(endpoint, null) - { - } - - public RouteMatch(IRouteEndpoint endpoint, IDictionary values) - { - Endpoint = endpoint; - Values = values; - } - - public IRouteEndpoint Endpoint - { - get; - private set; - } - - public IDictionary Values - { - get; - private set; - } - } -} diff --git a/src/Microsoft.AspNet.Routing/Template/BoundRouteTemplate.cs b/src/Microsoft.AspNet.Routing/Template/BoundRouteTemplate.cs deleted file mode 100644 index f2a3671f82..0000000000 --- a/src/Microsoft.AspNet.Routing/Template/BoundRouteTemplate.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Routing.Template -{ - public class BoundRouteTemplate - { - public string Path { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Routing/Template/RouteBuilderExtensions.cs b/src/Microsoft.AspNet.Routing/Template/RouteBuilderExtensions.cs deleted file mode 100644 index 2434c19489..0000000000 --- a/src/Microsoft.AspNet.Routing/Template/RouteBuilderExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.AspNet.Routing.Template -{ - public static class RouteBuilderExtensions - { - public static void AddTemplateRoute(this IRouteBuilder builder, string template) - { - AddTemplateRoute(builder, template, null); - } - - public static void AddTemplateRoute(this IRouteBuilder builder, string template, IDictionary defaults) - { - builder.Routes.Add(new TemplateRoute(builder.Endpoint, template, defaults)); - } - - public static void AddTemplateRoute(this IRouteBuilder builder, string template, object defaults) - { - builder.Routes.Add(new TemplateRoute(builder.Endpoint, template, new RouteValueDictionary(defaults))); - } - } -} diff --git a/src/Microsoft.AspNet.Routing/Template/RouteCollectionExtensions.cs b/src/Microsoft.AspNet.Routing/Template/RouteCollectionExtensions.cs new file mode 100644 index 0000000000..e33ba3a0cc --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Template/RouteCollectionExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Routing.Template +{ + public static class RouteCollectionExtensions + { + public static IRouteCollection AddTemplateRoute(this IRouteCollection routes, string template) + { + AddTemplateRoute(routes, template, null); + return routes; + } + + public static IRouteCollection AddTemplateRoute(this IRouteCollection routes, string template, object defaults) + { + AddTemplateRoute(routes, template, new RouteValueDictionary(defaults)); + return routes; + } + + public static IRouteCollection AddTemplateRoute(this IRouteCollection routes, string template, IDictionary defaults) + { + routes.Add(new TemplateRoute(routes.DefaultHandler, template, defaults)); + return routes; + } + } +} diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index a3a47d98e2..0d6673eeb6 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -2,31 +2,33 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Routing.Template { - public class TemplateRoute : IRoute + public class TemplateRoute : IRouter { private readonly IDictionary _defaults; - private readonly IRouteEndpoint _endpoint; + private readonly IRouter _next; private readonly Template _parsedTemplate; private readonly string _routeTemplate; private readonly TemplateMatcher _matcher; private readonly TemplateBinder _binder; - public TemplateRoute(IRouteEndpoint endpoint, string routeTemplate) - : this(endpoint, routeTemplate, null) + public TemplateRoute(IRouter next, string routeTemplate) + : this(next, routeTemplate, null) { } - public TemplateRoute(IRouteEndpoint endpoint, string routeTemplate, IDictionary defaults) + public TemplateRoute(IRouter next, string routeTemplate, IDictionary defaults) { - if (endpoint == null) + if (next == null) { - throw new ArgumentNullException("endpoint"); + throw new ArgumentNullException("next"); } - _endpoint = endpoint; + _next = next; _routeTemplate = routeTemplate ?? string.Empty; _defaults = defaults ?? new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -42,17 +44,12 @@ namespace Microsoft.AspNet.Routing.Template get { return _defaults; } } - public IRouteEndpoint Endpoint - { - get { return _endpoint; } - } - public string RouteTemplate { get { return _routeTemplate; } } - public virtual RouteMatch Match(RouteContext context) + public async virtual Task RouteAsync(RouteContext context) { if (context == null) { @@ -69,18 +66,29 @@ namespace Microsoft.AspNet.Routing.Template if (values == null) { // If we got back a null value set, that means the URI did not match - return null; + return; } else { - return new RouteMatch(_endpoint, values); + await _next.RouteAsync(new RouteContext(context.HttpContext){ Values = values }); } } - public RouteBindResult Bind(RouteBindContext context) + public void BindPath(BindPathContext context) { + // This could be optimized more heavily - right now we try to do the full url + // generation before validating, but we could do it in two phases. var path = _binder.Bind(_defaults, context.AmbientValues, context.Values); - return path == null ? null : new RouteBindResult(path); + if (path == null) + { + return; + } + + _next.BindPath(context); + if (context.IsBound && context.Path == null) + { + context.Path = path; + } } } } diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs index 68711bb630..18d52c5aae 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs @@ -439,7 +439,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests [Fact] public void GetUrlWithEmptyStringForMiddleParameterShouldUseDefaultValue() { - // DevDiv Bugs 172084: UrlRouting: Route.GetUrl generates the wrong route of new values has a different controller and route has an action parameter with default + // DevDiv Bugs 172084: UrlRouting: Route.BindPath generates the wrong route of new values has a different controller and route has an action parameter with default var ambientValues = new RouteValueDictionary(); ambientValues.Add("Controller", "Test"); ambientValues.Add("Action", "Fallback"); diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs index a2da4d343d..f6b6e5489c 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTests.cs @@ -13,66 +13,81 @@ namespace Microsoft.AspNet.Routing.Template.Tests // PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases. [Fact] - public void Match_Success_LeadingSlash() + public async void Match_Success_LeadingSlash() { // Arrange var route = CreateRoute("{controller}/{action}"); var context = CreateRouteContext("/Home/Index"); // Act - var match = route.Match(context); + await route.RouteAsync(context); // Assert - Assert.NotNull(match); - Assert.Equal(2, match.Values.Count); - Assert.Equal("Home", match.Values["controller"]); - Assert.Equal("Index", match.Values["action"]); + Assert.True(context.IsHandled); + Assert.Equal(2, context.Values.Count); + Assert.Equal("Home", context.Values["controller"]); + Assert.Equal("Index", context.Values["action"]); } [Fact] - public void Match_Success_RootUrl() + public async void Match_Success_RootUrl() { // Arrange var route = CreateRoute(""); var context = CreateRouteContext("/"); // Act - var match = route.Match(context); + await route.RouteAsync(context); // Assert - Assert.NotNull(match); - Assert.Equal(0, match.Values.Count); + Assert.True(context.IsHandled); + Assert.Equal(0, context.Values.Count); } [Fact] - public void Match_Success_Defaults() + public async void Match_Success_Defaults() { // Arrange var route = CreateRoute("{controller}/{action}", new { action = "Index" }); var context = CreateRouteContext("/Home"); // Act - var match = route.Match(context); + await route.RouteAsync(context); // Assert - Assert.NotNull(match); - Assert.Equal(2, match.Values.Count); - Assert.Equal("Home", match.Values["controller"]); - Assert.Equal("Index", match.Values["action"]); + Assert.True(context.IsHandled); + Assert.Equal(2, context.Values.Count); + Assert.Equal("Home", context.Values["controller"]); + Assert.Equal("Index", context.Values["action"]); } [Fact] - public void Match_Fails() + public async void Match_Fails() { // Arrange var route = CreateRoute("{controller}/{action}"); var context = CreateRouteContext("/Home"); // Act - var match = route.Match(context); + await route.RouteAsync(context); // Assert - Assert.Null(match); + Assert.False(context.IsHandled); + } + + [Fact] + public async void Match_RejectedByHanlder() + { + // Arrange + var route = CreateRoute("{controller}", accept: false); + var context = CreateRouteContext("/Home"); + + // Act + await route.RouteAsync(context); + + // Assert + Assert.False(context.IsHandled); + Assert.Null(context.Values); } private static RouteContext CreateRouteContext(string requestPath) @@ -98,11 +113,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests var context = CreateRouteBindContext(new {controller = "Home"}); // Act - var bind = route.Bind(context); + route.BindPath(context); // Assert - Assert.NotNull(bind); - Assert.Equal("Home", bind.Url); + Assert.True(context.IsBound); + Assert.Equal("Home", context.Path); } [Fact] @@ -113,10 +128,26 @@ namespace Microsoft.AspNet.Routing.Template.Tests var context = CreateRouteBindContext(new { controller = "Home" }); // Act - var bind = route.Bind(context); + route.BindPath(context); // Assert - Assert.Null(bind); + Assert.False(context.IsBound); + Assert.Null(context.Path); + } + + [Fact] + public void Bind_RejectedByHandler() + { + // Arrange + var route = CreateRoute("{controller}", accept: false); + var context = CreateRouteBindContext(new { controller = "Home" }); + + // Act + route.BindPath(context); + + // Assert + Assert.False(context.IsBound); + Assert.Null(context.Path); } [Fact] @@ -127,46 +158,54 @@ namespace Microsoft.AspNet.Routing.Template.Tests var context = CreateRouteBindContext(new { action = "Index"}, new { controller = "Home" }); // Act - var bind = route.Bind(context); + route.BindPath(context); // Assert - Assert.NotNull(bind); - Assert.Equal("Home/Index", bind.Url); + Assert.True(context.IsBound); + Assert.Equal("Home/Index", context.Path); } - private static RouteBindContext CreateRouteBindContext(object values) + private static BindPathContext CreateRouteBindContext(object values) { return CreateRouteBindContext(new RouteValueDictionary(values), null); } - private static RouteBindContext CreateRouteBindContext(object values, object ambientValues) + private static BindPathContext CreateRouteBindContext(object values, object ambientValues) { return CreateRouteBindContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues)); } - private static RouteBindContext CreateRouteBindContext(IDictionary values, IDictionary ambientValues) + private static BindPathContext CreateRouteBindContext(IDictionary values, IDictionary ambientValues) { var context = new Mock(MockBehavior.Strict); - context.Setup(c => c.GetFeature()).Returns(new RouteValues(ambientValues)); - return new RouteBindContext(context.Object, values); + return new BindPathContext(context.Object, ambientValues, values); } #endregion - private static TemplateRoute CreateRoute(string template) + private static TemplateRoute CreateRoute(string template, bool accept = true) { - return new TemplateRoute(CreateEndpoint(), template); + return new TemplateRoute(CreateEndpoint(accept), template); } - private static TemplateRoute CreateRoute(string template, object defaults) + private static TemplateRoute CreateRoute(string template, object defaults, bool accept = true) { - return new TemplateRoute(CreateEndpoint(), template, new RouteValueDictionary(defaults)); + return new TemplateRoute(CreateEndpoint(accept), template, new RouteValueDictionary(defaults)); } - private static IRouteEndpoint CreateEndpoint() + private static IRouter CreateEndpoint(bool accept = true) { - return new Mock(MockBehavior.Strict).Object; + var endpoint = new Mock(MockBehavior.Strict); + endpoint + .Setup(e => e.BindPath(It.IsAny())) + .Callback(c => c.IsBound = accept); + + endpoint + .Setup(e => e.RouteAsync(It.IsAny())) + .Callback(async (c) => c.IsHandled = accept); + + return endpoint.Object; } } }