Adding API for consuming url generation
This commit is contained in:
parent
89eb6e6445
commit
cd73fac433
|
|
@ -50,5 +50,10 @@ namespace RoutingSample
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public RouteBindResult Bind(RouteBindContext context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ 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);
|
||||
var rb1 = new RouteBuilder(endpoint1, routes.Routes);
|
||||
rb1.AddPrefixRoute("api/store");
|
||||
rb1.AddTemplateRoute("api/{controller}/{*extra}", new { controller = "Store" });
|
||||
|
||||
var rb2 = new RouteBuilder(endpoint2, routes);
|
||||
var rb2 = new RouteBuilder(endpoint2, routes.Routes);
|
||||
rb2.AddPrefixRoute("hello/world");
|
||||
rb2.AddPrefixRoute("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
|
|
@ -20,13 +21,13 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
public async Task<bool> Invoke(HttpContext context)
|
||||
{
|
||||
RouteContext routeContext = new RouteContext(context);
|
||||
var routeContext = new RouteContext(context);
|
||||
|
||||
for (int i = 0; i < Routes.Count; i++)
|
||||
for (var i = 0; i < Routes.Count; i++)
|
||||
{
|
||||
IRoute route = Routes[i];
|
||||
var route = Routes[i];
|
||||
|
||||
RouteMatch match = route.Match(routeContext);
|
||||
var match = route.Match(routeContext);
|
||||
if (match != null)
|
||||
{
|
||||
context.SetFeature<IRouteValues>(new RouteValues(match.Values));
|
||||
|
|
@ -41,5 +42,23 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ namespace Microsoft.AspNet.Routing
|
|||
public interface IRoute
|
||||
{
|
||||
RouteMatch Match(RouteContext context);
|
||||
|
||||
RouteBindResult Bind(RouteBindContext context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
|
|
@ -7,6 +8,10 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
public interface IRouteEngine
|
||||
{
|
||||
IRouteCollection Routes { get; }
|
||||
|
||||
Task<bool> Invoke(HttpContext context);
|
||||
|
||||
string GetUrl(HttpContext context, IDictionary<string, object> values);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ namespace Microsoft.AspNet.Routing.Owin
|
|||
{
|
||||
public static class BuilderExtensions
|
||||
{
|
||||
public static IRouteCollection UseRouter(this IBuilder builder)
|
||||
public static IRouteEngine UseRouter(this IBuilder builder)
|
||||
{
|
||||
var routes = new DefaultRouteCollection();
|
||||
var engine = new DefaultRouteEngine(routes);
|
||||
|
||||
builder.Use((next) => new RouterMiddleware(next, engine).Invoke);
|
||||
|
||||
return routes;
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
// 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,9 +12,6 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
private const string SeparatorString = "/";
|
||||
|
||||
private readonly TemplateMatcher _matcher;
|
||||
private readonly TemplateBinder _binder;
|
||||
|
||||
public Template(List<TemplateSegment> segments)
|
||||
{
|
||||
if (segments == null)
|
||||
|
|
@ -37,20 +34,12 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
_matcher = new TemplateMatcher(this);
|
||||
_binder = new TemplateBinder(this);
|
||||
}
|
||||
|
||||
public List<TemplatePart> Parameters { get; private set; }
|
||||
|
||||
public List<TemplateSegment> Segments { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Match(string requestPath, IDictionary<string, object> defaults)
|
||||
{
|
||||
return _matcher.Match(requestPath, defaults);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
public Template Template { get; private set; }
|
||||
|
||||
public BoundRouteTemplate Bind(IDictionary<string, object> defaults, IDictionary<string, object> ambientValues, IDictionary<string, object> values)
|
||||
public string Bind(IDictionary<string, object> defaults, IDictionary<string, object> ambientValues, IDictionary<string, object> values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
|
|
@ -184,7 +184,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
private BoundRouteTemplate BindValues(TemplateBindingContext bindingContext)
|
||||
private string BindValues(TemplateBindingContext bindingContext)
|
||||
{
|
||||
var context = new UriBuildingContext();
|
||||
|
||||
|
|
@ -261,10 +261,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
encoded.Append(Uri.EscapeDataString(converted));
|
||||
}
|
||||
|
||||
return new BoundRouteTemplate()
|
||||
{
|
||||
Path = encoded.ToString(),
|
||||
};
|
||||
return encoded.ToString();
|
||||
}
|
||||
|
||||
private static string UriEncode(string str)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
values.Add(part.Name, defaultValue);
|
||||
}
|
||||
else if (part.IsOptional)
|
||||
{
|
||||
// This is optional (with no default value) - there's nothing to capture here, so just move on.
|
||||
}
|
||||
else
|
||||
{
|
||||
// There's no default for this parameter
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private readonly IRouteEndpoint _endpoint;
|
||||
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)
|
||||
|
|
@ -25,11 +27,14 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
_endpoint = endpoint;
|
||||
_routeTemplate = routeTemplate ?? String.Empty;
|
||||
_routeTemplate = routeTemplate ?? string.Empty;
|
||||
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// The parser will throw for invalid routes.
|
||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||
|
||||
_matcher = new TemplateMatcher(_parsedTemplate);
|
||||
_binder = new TemplateBinder(_parsedTemplate);
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Defaults
|
||||
|
|
@ -55,12 +60,12 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
var requestPath = context.RequestPath;
|
||||
if (!String.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
|
||||
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
|
||||
{
|
||||
requestPath = requestPath.Substring(1);
|
||||
}
|
||||
|
||||
IDictionary<string, object> values = _parsedTemplate.Match(requestPath, _defaults);
|
||||
var values = _matcher.Match(requestPath, _defaults);
|
||||
if (values == null)
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
|
|
@ -71,5 +76,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return new RouteMatch(_endpoint, values);
|
||||
}
|
||||
}
|
||||
|
||||
public RouteBindResult Bind(RouteBindContext context)
|
||||
{
|
||||
var path = _binder.Bind(_defaults, context.AmbientValues, context.Values);
|
||||
return path == null ? null : new RouteBindResult(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template.Tests
|
||||
{
|
||||
// This is just a placeholder
|
||||
public class RouteValueDictionary : Dictionary<string, object>
|
||||
{
|
||||
public RouteValueDictionary()
|
||||
: base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValueDictionary(object obj)
|
||||
: base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
foreach (var property in obj.GetType().GetTypeInfo().GetProperties())
|
||||
{
|
||||
Add(property.Name, property.GetValue(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
else
|
||||
{
|
||||
Assert.NotNull(boundTemplate);
|
||||
Assert.Equal(expected, boundTemplate.Path);
|
||||
Assert.Equal(expected, boundTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -989,7 +989,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
// We want to chop off the query string and compare that using an unordered comparison
|
||||
var expectedParts = new PathAndQuery(expected);
|
||||
var actualParts = new PathAndQuery(boundTemplate.Path);
|
||||
var actualParts = new PathAndQuery(boundTemplate);
|
||||
|
||||
Assert.Equal(expectedParts.Path, actualParts.Path);
|
||||
|
||||
|
|
|
|||
|
|
@ -733,6 +733,37 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
Assert.False(match.ContainsKey("action"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchDoesNotSetOptionalParameter_EmptyString()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateMatcher("{controller?}");
|
||||
var url = "";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(0, match.Values.Count);
|
||||
Assert.False(match.ContainsKey("controller"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Match_EmptyRouteWith_EmptyString()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateMatcher("");
|
||||
var url = "";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(0, match.Values.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchMultipleOptionalParameters()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
// 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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template.Tests
|
||||
{
|
||||
public class TemplateRouteTests
|
||||
{
|
||||
#region Route Matching
|
||||
|
||||
// PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
|
||||
[Fact]
|
||||
public void Match_Success_LeadingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}");
|
||||
var context = CreateRouteContext("/Home/Index");
|
||||
|
||||
// Act
|
||||
var match = route.Match(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(2, match.Values.Count);
|
||||
Assert.Equal("Home", match.Values["controller"]);
|
||||
Assert.Equal("Index", match.Values["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Match_Success_RootUrl()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("");
|
||||
var context = CreateRouteContext("/");
|
||||
|
||||
// Act
|
||||
var match = route.Match(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(0, match.Values.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Match_Success_Defaults()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}", new { action = "Index" });
|
||||
var context = CreateRouteContext("/Home");
|
||||
|
||||
// Act
|
||||
var match = route.Match(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(2, match.Values.Count);
|
||||
Assert.Equal("Home", match.Values["controller"]);
|
||||
Assert.Equal("Index", match.Values["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Match_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}");
|
||||
var context = CreateRouteContext("/Home");
|
||||
|
||||
// Act
|
||||
var match = route.Match(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
private static RouteContext CreateRouteContext(string requestPath)
|
||||
{
|
||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
|
||||
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
||||
|
||||
return new RouteContext(context.Object);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Route Binding
|
||||
|
||||
[Fact]
|
||||
public void Bind_Success()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}");
|
||||
var context = CreateRouteBindContext(new {controller = "Home"});
|
||||
|
||||
// Act
|
||||
var bind = route.Bind(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bind);
|
||||
Assert.Equal("Home", bind.Url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bind_Fail()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}");
|
||||
var context = CreateRouteBindContext(new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
var bind = route.Bind(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(bind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bind_Success_AmbientValues()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}");
|
||||
var context = CreateRouteBindContext(new { action = "Index"}, new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
var bind = route.Bind(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bind);
|
||||
Assert.Equal("Home/Index", bind.Url);
|
||||
}
|
||||
|
||||
private static RouteBindContext CreateRouteBindContext(object values)
|
||||
{
|
||||
return CreateRouteBindContext(new RouteValueDictionary(values), null);
|
||||
}
|
||||
|
||||
private static RouteBindContext CreateRouteBindContext(object values, object ambientValues)
|
||||
{
|
||||
return CreateRouteBindContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
|
||||
}
|
||||
|
||||
private static RouteBindContext CreateRouteBindContext(IDictionary<string, object> values, IDictionary<string, object> ambientValues)
|
||||
{
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.Setup(c => c.GetFeature<IRouteValues>()).Returns(new RouteValues(ambientValues));
|
||||
|
||||
return new RouteBindContext(context.Object, values);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static TemplateRoute CreateRoute(string template)
|
||||
{
|
||||
return new TemplateRoute(CreateEndpoint(), template);
|
||||
}
|
||||
|
||||
private static TemplateRoute CreateRoute(string template, object defaults)
|
||||
{
|
||||
return new TemplateRoute(CreateEndpoint(), template, new RouteValueDictionary(defaults));
|
||||
}
|
||||
|
||||
private static IRouteEndpoint CreateEndpoint()
|
||||
{
|
||||
return new Mock<IRouteEndpoint>(MockBehavior.Strict).Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
"configurations": {
|
||||
"net45": {
|
||||
"dependencies": {
|
||||
"Moq": "4.2.1402.2112",
|
||||
"Owin": "1.0",
|
||||
"xunit": "1.9.2",
|
||||
"xunit.extensions": "1.9.2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue