API changes for Routing

This commit is contained in:
Ryan Nowak 2014-03-05 16:48:10 -08:00
parent cd73fac433
commit 5d34a61bd9
27 changed files with 299 additions and 406 deletions

View File

@ -1,13 +1,12 @@
// 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.
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
namespace RoutingSample namespace RoutingSample
{ {
public class HttpContextRouteEndpoint : IRouteEndpoint public class HttpContextRouteEndpoint : IRouter
{ {
private readonly RequestDelegate _appFunc; private readonly RequestDelegate _appFunc;
@ -16,10 +15,16 @@ namespace RoutingSample
_appFunc = appFunc; _appFunc = appFunc;
} }
public async Task<bool> Send(HttpContext context) public async Task RouteAsync(RouteContext context)
{ {
await _appFunc(context); await _appFunc(context.HttpContext);
return true; context.IsHandled = true;
}
public void BindPath(BindPathContext context)
{
// We can generate a url for anything that the parent route deems OK.
context.IsBound = true;
} }
} }
} }

View File

@ -1,18 +1,19 @@
// 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.
using System; using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
namespace RoutingSample namespace RoutingSample
{ {
internal class PrefixRoute : IRoute internal class PrefixRoute : IRouter
{ {
private readonly IRouteEndpoint _endpoint; private readonly IRouter _next;
private readonly string _prefix; private readonly string _prefix;
public PrefixRoute(IRouteEndpoint endpoint, string prefix) public PrefixRoute(IRouter next, string prefix)
{ {
_endpoint = endpoint; _next = next;
if (prefix == null) if (prefix == null)
{ {
@ -32,28 +33,26 @@ namespace RoutingSample
_prefix = prefix; _prefix = prefix;
} }
public RouteMatch Match(RouteContext context) public async Task RouteAsync(RouteContext context)
{ {
if (context.RequestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)) if (context.RequestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase))
{ {
if (context.RequestPath.Length > _prefix.Length) if (context.RequestPath.Length > _prefix.Length)
{ {
char next = context.RequestPath[_prefix.Length]; var lastCharacter = context.RequestPath[_prefix.Length];
if (next != '/' && next != '#' && next != '?') 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
} }
} }
} }

View File

@ -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));
}
}
}

View File

@ -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;
}
}
}

View File

@ -26,13 +26,12 @@ namespace RoutingSample
var endpoint1 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("match1")); var endpoint1 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("match1"));
var endpoint2 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("Hello, World!")); var endpoint2 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("Hello, World!"));
var rb1 = new RouteBuilder(endpoint1, routes.Routes); routes.DefaultHandler = endpoint1;
rb1.AddPrefixRoute("api/store"); routes.AddPrefixRoute("api/store");
rb1.AddTemplateRoute("api/{controller}/{*extra}", new { controller = "Store" }); routes.AddTemplateRoute("api/{controller}/{*extra}", new { controller = "Store" });
var rb2 = new RouteBuilder(endpoint2, routes.Routes); routes.AddPrefixRoute("hello/world", endpoint2);
rb2.AddPrefixRoute("hello/world"); routes.AddPrefixRoute("", endpoint2);
rb2.AddPrefixRoute("");
} }
} }
} }

View File

@ -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<string, object> ambientValues, IDictionary<string, object> values)
{
Context = context;
AmbientValues = ambientValues;
Values = values;
}
public IDictionary<string, object> AmbientValues { get; private set; }
public HttpContext Context { get; private set; }
public bool IsBound { get; set; }
public string Path { get; set; }
public IDictionary<string, object> Values { get; private set; }
}
}

View File

@ -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<IRoute> _routes = new List<IRoute>();
public IRoute this[int index]
{
get { return _routes[index]; }
}
public int Count
{
get { return _routes.Count; }
}
public void Add(IRoute route)
{
_routes.Add(route);
}
}
}

View File

@ -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<bool> 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<IRouteValues>(new RouteValues(match.Values));
var accepted = await match.Endpoint.Send(context);
if (accepted)
{
return true;
}
}
}
return false;
}
public string GetUrl(HttpContext context, IDictionary<string, object> 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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 namespace Microsoft.AspNet.Routing
{ {
public interface IRouteCollection public interface IRouteCollection : IRouter
{ {
IRoute this[int index] IRouter DefaultHandler { get; set; }
{
get;
}
int Count void Add(IRouter router);
{
get;
}
void Add(IRoute route);
} }
} }

View File

@ -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<bool> Send(HttpContext context);
}
}

View File

@ -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<bool> Invoke(HttpContext context);
string GetUrl(HttpContext context, IDictionary<string, object> values);
}
}

View File

@ -1,11 +1,13 @@
// 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.
using System.Threading.Tasks;
namespace Microsoft.AspNet.Routing 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);
} }
} }

View File

@ -6,14 +6,15 @@ namespace Microsoft.AspNet.Routing.Owin
{ {
public static class BuilderExtensions public static class BuilderExtensions
{ {
public static IRouteEngine UseRouter(this IBuilder builder) public static IRouteCollection UseRouter(this IBuilder builder)
{ {
var routes = new DefaultRouteCollection(); return UseRouter(builder, new RouteCollection());
var engine = new DefaultRouteEngine(routes); }
builder.Use((next) => new RouterMiddleware(next, engine).Invoke); public static IRouteCollection UseRouter(this IBuilder builder, IRouteCollection routes)
{
return engine; builder.Use((next) => new RouterMiddleware(next, routes).Invoke);
return routes;
} }
} }
} }

View File

@ -1,5 +1,6 @@
// 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.
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions;
@ -7,13 +8,13 @@ namespace Microsoft.AspNet.Routing.Owin
{ {
public class RouterMiddleware public class RouterMiddleware
{ {
public RouterMiddleware(RequestDelegate next, IRouteEngine engine) public RouterMiddleware(RequestDelegate next, IRouter route)
{ {
Next = next; Next = next;
Engine = engine; Route = route;
} }
private IRouteEngine Engine private IRouter Route
{ {
get; get;
set; set;
@ -25,11 +26,14 @@ namespace Microsoft.AspNet.Routing.Owin
set; 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);
} }
} }
} }

View File

@ -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<string, object> values)
{
Context = context;
Values = values;
if (Context != null)
{
var ambientValues = context.GetFeature<IRouteValues>();
AmbientValues = ambientValues == null ? null : ambientValues.Values;
}
}
public IDictionary<string, object> AmbientValues { get; private set; }
public HttpContext Context { get; private set; }
public IDictionary<string, object> Values { get; private set; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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<IRouter> _routes = new List<IRouter>();
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;
}
}
}
}
}

View File

@ -1,28 +1,25 @@
// 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.
using System.Collections.Generic;
using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Routing namespace Microsoft.AspNet.Routing
{ {
public class RouteContext 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 public HttpContext HttpContext { get; private set; }
{
get;
private set;
}
public string RequestPath public bool IsHandled { get; set; }
{
get; public string RequestPath { get; private set; }
private set;
} public IDictionary<string, object> Values { get; set; }
} }
} }

View File

@ -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
{
/// <summary>
/// The result of matching a route. Includes an <see cref="IRouteEndpoint"/> to invoke and an optional collection of
/// captured values.
/// </summary>
public class RouteMatch
{
public RouteMatch(IRouteEndpoint endpoint)
: this(endpoint, null)
{
}
public RouteMatch(IRouteEndpoint endpoint, IDictionary<string, object> values)
{
Endpoint = endpoint;
Values = values;
}
public IRouteEndpoint Endpoint
{
get;
private set;
}
public IDictionary<string, object> Values
{
get;
private set;
}
}
}

View File

@ -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; }
}
}

View File

@ -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<string, object> 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)));
}
}
}

View File

@ -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<string, object> defaults)
{
routes.Add(new TemplateRoute(routes.DefaultHandler, template, defaults));
return routes;
}
}
}

View File

@ -2,31 +2,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Routing.Template namespace Microsoft.AspNet.Routing.Template
{ {
public class TemplateRoute : IRoute public class TemplateRoute : IRouter
{ {
private readonly IDictionary<string, object> _defaults; private readonly IDictionary<string, object> _defaults;
private readonly IRouteEndpoint _endpoint; private readonly IRouter _next;
private readonly Template _parsedTemplate; private readonly Template _parsedTemplate;
private readonly string _routeTemplate; private readonly string _routeTemplate;
private readonly TemplateMatcher _matcher; private readonly TemplateMatcher _matcher;
private readonly TemplateBinder _binder; private readonly TemplateBinder _binder;
public TemplateRoute(IRouteEndpoint endpoint, string routeTemplate) public TemplateRoute(IRouter next, string routeTemplate)
: this(endpoint, routeTemplate, null) : this(next, routeTemplate, null)
{ {
} }
public TemplateRoute(IRouteEndpoint endpoint, string routeTemplate, IDictionary<string, object> defaults) public TemplateRoute(IRouter next, string routeTemplate, IDictionary<string, object> defaults)
{ {
if (endpoint == null) if (next == null)
{ {
throw new ArgumentNullException("endpoint"); throw new ArgumentNullException("next");
} }
_endpoint = endpoint; _next = next;
_routeTemplate = routeTemplate ?? string.Empty; _routeTemplate = routeTemplate ?? string.Empty;
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
@ -42,17 +44,12 @@ namespace Microsoft.AspNet.Routing.Template
get { return _defaults; } get { return _defaults; }
} }
public IRouteEndpoint Endpoint
{
get { return _endpoint; }
}
public string RouteTemplate public string RouteTemplate
{ {
get { return _routeTemplate; } get { return _routeTemplate; }
} }
public virtual RouteMatch Match(RouteContext context) public async virtual Task RouteAsync(RouteContext context)
{ {
if (context == null) if (context == null)
{ {
@ -69,18 +66,29 @@ namespace Microsoft.AspNet.Routing.Template
if (values == null) if (values == null)
{ {
// If we got back a null value set, that means the URI did not match // If we got back a null value set, that means the URI did not match
return null; return;
} }
else 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); 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;
}
} }
} }
} }

View File

@ -439,7 +439,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
[Fact] [Fact]
public void GetUrlWithEmptyStringForMiddleParameterShouldUseDefaultValue() 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(); var ambientValues = new RouteValueDictionary();
ambientValues.Add("Controller", "Test"); ambientValues.Add("Controller", "Test");
ambientValues.Add("Action", "Fallback"); ambientValues.Add("Action", "Fallback");

View File

@ -13,66 +13,81 @@ namespace Microsoft.AspNet.Routing.Template.Tests
// PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases. // PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
[Fact] [Fact]
public void Match_Success_LeadingSlash() public async void Match_Success_LeadingSlash()
{ {
// Arrange // Arrange
var route = CreateRoute("{controller}/{action}"); var route = CreateRoute("{controller}/{action}");
var context = CreateRouteContext("/Home/Index"); var context = CreateRouteContext("/Home/Index");
// Act // Act
var match = route.Match(context); await route.RouteAsync(context);
// Assert // Assert
Assert.NotNull(match); Assert.True(context.IsHandled);
Assert.Equal(2, match.Values.Count); Assert.Equal(2, context.Values.Count);
Assert.Equal("Home", match.Values["controller"]); Assert.Equal("Home", context.Values["controller"]);
Assert.Equal("Index", match.Values["action"]); Assert.Equal("Index", context.Values["action"]);
} }
[Fact] [Fact]
public void Match_Success_RootUrl() public async void Match_Success_RootUrl()
{ {
// Arrange // Arrange
var route = CreateRoute(""); var route = CreateRoute("");
var context = CreateRouteContext("/"); var context = CreateRouteContext("/");
// Act // Act
var match = route.Match(context); await route.RouteAsync(context);
// Assert // Assert
Assert.NotNull(match); Assert.True(context.IsHandled);
Assert.Equal(0, match.Values.Count); Assert.Equal(0, context.Values.Count);
} }
[Fact] [Fact]
public void Match_Success_Defaults() public async void Match_Success_Defaults()
{ {
// Arrange // Arrange
var route = CreateRoute("{controller}/{action}", new { action = "Index" }); var route = CreateRoute("{controller}/{action}", new { action = "Index" });
var context = CreateRouteContext("/Home"); var context = CreateRouteContext("/Home");
// Act // Act
var match = route.Match(context); await route.RouteAsync(context);
// Assert // Assert
Assert.NotNull(match); Assert.True(context.IsHandled);
Assert.Equal(2, match.Values.Count); Assert.Equal(2, context.Values.Count);
Assert.Equal("Home", match.Values["controller"]); Assert.Equal("Home", context.Values["controller"]);
Assert.Equal("Index", match.Values["action"]); Assert.Equal("Index", context.Values["action"]);
} }
[Fact] [Fact]
public void Match_Fails() public async void Match_Fails()
{ {
// Arrange // Arrange
var route = CreateRoute("{controller}/{action}"); var route = CreateRoute("{controller}/{action}");
var context = CreateRouteContext("/Home"); var context = CreateRouteContext("/Home");
// Act // Act
var match = route.Match(context); await route.RouteAsync(context);
// Assert // 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) private static RouteContext CreateRouteContext(string requestPath)
@ -98,11 +113,11 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var context = CreateRouteBindContext(new {controller = "Home"}); var context = CreateRouteBindContext(new {controller = "Home"});
// Act // Act
var bind = route.Bind(context); route.BindPath(context);
// Assert // Assert
Assert.NotNull(bind); Assert.True(context.IsBound);
Assert.Equal("Home", bind.Url); Assert.Equal("Home", context.Path);
} }
[Fact] [Fact]
@ -113,10 +128,26 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var context = CreateRouteBindContext(new { controller = "Home" }); var context = CreateRouteBindContext(new { controller = "Home" });
// Act // Act
var bind = route.Bind(context); route.BindPath(context);
// Assert // 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] [Fact]
@ -127,46 +158,54 @@ namespace Microsoft.AspNet.Routing.Template.Tests
var context = CreateRouteBindContext(new { action = "Index"}, new { controller = "Home" }); var context = CreateRouteBindContext(new { action = "Index"}, new { controller = "Home" });
// Act // Act
var bind = route.Bind(context); route.BindPath(context);
// Assert // Assert
Assert.NotNull(bind); Assert.True(context.IsBound);
Assert.Equal("Home/Index", bind.Url); Assert.Equal("Home/Index", context.Path);
} }
private static RouteBindContext CreateRouteBindContext(object values) private static BindPathContext CreateRouteBindContext(object values)
{ {
return CreateRouteBindContext(new RouteValueDictionary(values), null); 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)); return CreateRouteBindContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
} }
private static RouteBindContext CreateRouteBindContext(IDictionary<string, object> values, IDictionary<string, object> ambientValues) private static BindPathContext CreateRouteBindContext(IDictionary<string, object> values, IDictionary<string, object> ambientValues)
{ {
var context = new Mock<HttpContext>(MockBehavior.Strict); var context = new Mock<HttpContext>(MockBehavior.Strict);
context.Setup(c => c.GetFeature<IRouteValues>()).Returns(new RouteValues(ambientValues));
return new RouteBindContext(context.Object, values); return new BindPathContext(context.Object, ambientValues, values);
} }
#endregion #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<IRouteEndpoint>(MockBehavior.Strict).Object; var endpoint = new Mock<IRouter>(MockBehavior.Strict);
endpoint
.Setup(e => e.BindPath(It.IsAny<BindPathContext>()))
.Callback<BindPathContext>(c => c.IsBound = accept);
endpoint
.Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
.Callback<RouteContext>(async (c) => c.IsHandled = accept);
return endpoint.Object;
} }
} }
} }