Merge pull request #461 from aspnet/rynowak/endpoints-and-addresses

Making endpoints and addresses easier
This commit is contained in:
Ryan Nowak 2017-09-29 11:08:39 -07:00 committed by GitHub
commit 9c30dd1256
19 changed files with 189 additions and 248 deletions

View File

@ -23,11 +23,11 @@ namespace DispatcherSample
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
{
var endpoint = context.Endpoints[i];
IHttpMethodMetadata metadata = null;
ITemplateEndpoint metadata = null;
for (var j = endpoint.Metadata.Count - 1; j >= 0; j--)
{
metadata = endpoint.Metadata[j] as IHttpMethodMetadata;
metadata = endpoint.Metadata[j] as ITemplateEndpoint;
if (metadata != null)
{
break;
@ -70,17 +70,9 @@ namespace DispatcherSample
}
}
private bool Matches(IHttpMethodMetadata metadata, string httpMethod)
private bool Matches(ITemplateEndpoint endpoint, string httpMethod)
{
for (var i = 0; i < metadata.AllowedMethods.Count; i++)
{
if (string.Equals(metadata.AllowedMethods[i], httpMethod, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
return string.Equals(endpoint.HttpMethod, httpMethod, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -1,23 +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;
using System.Collections.Generic;
namespace DispatcherSample
{
public class HttpMethodMetadata : IHttpMethodMetadata
{
public HttpMethodMetadata(string httpMethod)
{
if (httpMethod == null)
{
throw new ArgumentNullException(nameof(httpMethod));
}
AllowedMethods = new[] { httpMethod, };
}
public IReadOnlyList<string> AllowedMethods { get; }
}
}

View File

@ -1,12 +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;
namespace DispatcherSample
{
public interface IHttpMethodMetadata
{
IReadOnlyList<string> AllowedMethods { get; }
}
}

View File

@ -1,7 +1,6 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
@ -32,27 +31,27 @@ namespace DispatcherSample
{
Addresses =
{
new DispatcherValueAddress(new { controller = "Home", action = "Index", }, new object[]{ new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, "Home:Index()"),
new DispatcherValueAddress(new { controller = "Home", action = "About", }, new object[]{ new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, "Home:About()"),
new DispatcherValueAddress(new { controller = "Admin", action = "Index", }, new object[]{ new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, "Admin:Index()"),
new DispatcherValueAddress(new { controller = "Admin", action = "Users", }, new object[]{ new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, "Admin:GetUsers()/Admin:EditUsers()"),
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, "Admin:Index()"),
new TemplateAddress("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "Admin:GetUsers()/Admin:EditUsers()"),
},
Endpoints =
{
new SimpleEndpoint(Home_Index, new object[] { new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, new { controller = "Home", action = "Index", }, "Home:Index()"),
new SimpleEndpoint(Home_About, new object[] { new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, new { controller = "Home", action = "About", }, "Home:About()"),
new SimpleEndpoint(Admin_Index, new object[] { new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), }, new { controller = "Admin", action = "Index", }, "Admin:Index()"),
new SimpleEndpoint(Admin_GetUsers, new object[] { new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), new HttpMethodMetadata("GET"), new AuthorizationPolicyMetadata("Admin"), }, new { controller = "Admin", action = "Users", }, "Admin:GetUsers()"),
new SimpleEndpoint(Admin_EditUsers, new object[] { new RouteTemplateMetadata("{controller=Home}/{action=Index}/{id?}"), new HttpMethodMetadata("POST"), new AuthorizationPolicyMetadata("Admin"), }, new { controller = "Admin", action = "Users", }, "Admin:EditUsers()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Home", action = "About", }, Home_About, "Home:About()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Index", }, Admin_Index, "Admin:Index()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "GET", Admin_GetUsers, "Admin:GetUsers()", new AuthorizationPolicyMetadata("Admin")),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Admin", action = "Users", }, "POST", Admin_EditUsers, "Admin:EditUsers()", new AuthorizationPolicyMetadata("Admin")),
},
Selectors =
{
new DispatcherValueEndpointSelector(),
new TemplateEndpointSelector(),
new HttpMethodEndpointSelector(),
}
});
options.HandlerFactories.Add((endpoint) => (endpoint as SimpleEndpoint)?.HandlerFactory);
options.HandlerFactories.Add((endpoint) => (endpoint as TemplateEndpoint)?.HandlerFactory);
});
services.AddDispatcher();

View File

@ -7,12 +7,12 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Dispatcher
{
public class SimpleDispatcherDataSource : DispatcherDataSource
public class DefaultDispatcherDataSource : DispatcherDataSource
{
private readonly List<Address> _addresses;
private readonly List<Endpoint> _endpoints;
public SimpleDispatcherDataSource()
public DefaultDispatcherDataSource()
{
_addresses = new List<Address>();
_endpoints = new List<Endpoint>();

View File

@ -18,7 +18,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IStartupFilter, DispatcherEndpointStartupFilter>();
services.AddSingleton<AddressTable, DefaultAddressTable>();
services.AddSingleton<DispatcherValueAddressSelector>();
services.AddSingleton<TemplateAddressSelector>();
return services;
}

View File

@ -3,8 +3,10 @@
namespace Microsoft.AspNetCore.Dispatcher
{
public interface IDispatcherValueSelectableEndpoint
public interface ITemplateAddress
{
string Template { get; }
DispatcherValueCollection Values { get; }
}
}

View File

@ -0,0 +1,14 @@
// 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.AspNetCore.Dispatcher
{
public interface ITemplateEndpoint
{
string HttpMethod { get; }
string Template { get; }
DispatcherValueCollection Values { get; }
}
}

View File

@ -1,82 +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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
public class SimpleEndpoint : Endpoint, IDispatcherValueSelectableEndpoint
{
public SimpleEndpoint(RequestDelegate requestDelegate)
: this(requestDelegate, Array.Empty<object>(), null, null)
{
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory)
: this(delegateFactory, Array.Empty<object>(), null, null)
{
}
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata)
: this(requestDelegate, metadata, null, null)
{
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata)
: this(delegateFactory, metadata, null, null)
{
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, object values)
: this(delegateFactory, metadata, null, null)
{
}
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata, object values, string displayName)
{
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
}
HandlerFactory = (next) => requestDelegate;
Metadata = metadata.ToArray();
Values = new DispatcherValueCollection(values);
DisplayName = displayName;
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, object values, string displayName)
{
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (delegateFactory == null)
{
throw new ArgumentNullException(nameof(delegateFactory));
}
HandlerFactory = delegateFactory;
Metadata = metadata.ToArray();
Values = new DispatcherValueCollection(values);
DisplayName = displayName;
}
public override string DisplayName { get; }
public override IReadOnlyList<object> Metadata { get; }
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; }
public DispatcherValueCollection Values { get; }
}
}

View File

@ -7,24 +7,18 @@ using System.Linq;
namespace Microsoft.AspNetCore.Dispatcher
{
public class DispatcherValueAddress : Address
public class TemplateAddress : Address, ITemplateAddress
{
public DispatcherValueAddress(object values)
: this(values, Array.Empty<object>(), null)
public TemplateAddress(string template, object values, params object[] metadata)
: this(template, values, null, metadata)
{
}
public DispatcherValueAddress(object values, IEnumerable<object> metadata)
: this(values, metadata, null)
public TemplateAddress(string template, object values, string displayName, params object[] metadata)
{
}
public DispatcherValueAddress(object values, IEnumerable<object> metadata, string displayName)
{
if (values == null)
if (template == null)
{
throw new ArgumentNullException(nameof(values));
throw new ArgumentNullException(nameof(template));
}
if (metadata == null)
@ -32,15 +26,18 @@ namespace Microsoft.AspNetCore.Dispatcher
throw new ArgumentNullException(nameof(metadata));
}
Template = template;
Values = new DispatcherValueCollection(values);
Metadata = metadata.ToArray();
DisplayName = displayName;
Metadata = metadata.ToArray();
}
public override string DisplayName { get; }
public override IReadOnlyList<object> Metadata { get; }
public string Template { get; }
public DispatcherValueCollection Values { get; }
}
}

View File

@ -7,11 +7,11 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Dispatcher
{
// This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly.
public class DispatcherValueAddressSelector
public class TemplateAddressSelector
{
private readonly AddressTable _addressTable;
public DispatcherValueAddressSelector(AddressTable addressTable)
public TemplateAddressSelector(AddressTable addressTable)
{
if (addressTable == null)
{
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Dispatcher
for (var j = 0; j < group.Count; j++)
{
var address = group[j] as DispatcherValueAddress;
var address = group[j] as ITemplateAddress;
if (address == null)
{
continue;
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Dispatcher
if (IsMatch(address, values))
{
matches.Add(address);
matches.Add(group[j]);
}
}
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Dispatcher
return null;
}
private bool IsMatch(DispatcherValueAddress address, DispatcherValueCollection values)
private bool IsMatch(ITemplateAddress address, DispatcherValueCollection values)
{
foreach (var kvp in address.Values)
{

View File

@ -0,0 +1,127 @@
// 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.Linq;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
public class TemplateEndpoint : Endpoint, ITemplateEndpoint
{
public TemplateEndpoint(string template, RequestDelegate requestDelegate, params object[] metadata)
: this(template, (object)null, (string)null, requestDelegate, null, metadata)
{
}
public TemplateEndpoint(string template, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
: this(template, (object)null, (string)null, delegateFactory, null, metadata)
{
}
public TemplateEndpoint(string template, object values, RequestDelegate requestDelegate, params object[] metadata)
: this(template, values, null, requestDelegate, null, metadata)
{
}
public TemplateEndpoint(string template, object values, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
: this(template, values, null, delegateFactory, null, metadata)
{
}
public TemplateEndpoint(string template, object values, RequestDelegate requestDelegate, string displayName, params object[] metadata)
: this(template, values, null, requestDelegate, displayName, metadata)
{
}
public TemplateEndpoint(string template, object values, Func<RequestDelegate, RequestDelegate> delegateFactory, string displayName, params object[] metadata)
: this(template, values, null, delegateFactory, displayName, metadata)
{
}
public TemplateEndpoint(string template, object values, string httpMethod, RequestDelegate requestDelegate, params object[] metadata)
: this(template, values, httpMethod, requestDelegate, null, metadata)
{
}
public TemplateEndpoint(string template, object values, string httpMethod, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
: this(template, values, httpMethod, delegateFactory, null, metadata)
{
}
public TemplateEndpoint(
string template,
object values,
string httpMethod,
RequestDelegate requestDelegate,
string displayName,
params object[] metadata)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
Template = template;
Values = new DispatcherValueCollection(values);
HttpMethod = httpMethod;
HandlerFactory = (next) => requestDelegate;
DisplayName = displayName;
Metadata = metadata.ToArray();
}
public TemplateEndpoint(
string template,
object values,
string httpMethod,
Func<RequestDelegate, RequestDelegate> delegateFactory,
string displayName,
params object[] metadata)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
if (delegateFactory == null)
{
throw new ArgumentNullException(nameof(delegateFactory));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
Template = template;
Values = new DispatcherValueCollection(values);
HttpMethod = httpMethod;
HandlerFactory = delegateFactory;
DisplayName = displayName;
Metadata = metadata.ToArray();
}
public override string DisplayName { get; }
public string HttpMethod { get; }
public override IReadOnlyList<object> Metadata { get; }
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; }
public string Template { get; }
public DispatcherValueCollection Values { get; }
}
}

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Dispatcher
{
public class DispatcherValueEndpointSelector : EndpointSelector
public class TemplateEndpointSelector : EndpointSelector
{
public override Task SelectAsync(EndpointSelectorContext context)
{
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Dispatcher
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
{
var endpoint = context.Endpoints[i] as IDispatcherValueSelectableEndpoint;
var endpoint = context.Endpoints[i] as ITemplateEndpoint;
if (!CompareRouteValues(dispatcherFeature.Values, endpoint.Values))
{
context.Endpoints.RemoveAt(i);

View File

@ -1,14 +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 Microsoft.AspNetCore.Dispatcher;
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
public interface IRouteTemplateMetadata
{
string RouteTemplate { get; }
DispatcherValueCollection Defaults { get; }
}
}

View File

@ -1,12 +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.
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
public interface ITreeDispatcherMetadata
{
int Order { get; }
string RouteTemplate { get; }
}
}

View File

@ -1,33 +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;
using Microsoft.AspNetCore.Dispatcher;
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
public class RouteTemplateMetadata : IRouteTemplateMetadata, ITreeDispatcherMetadata
{
public RouteTemplateMetadata(string routeTemplate)
: this(routeTemplate, null)
{
}
public RouteTemplateMetadata(string routeTemplate, object defaults)
{
if (routeTemplate == null)
{
throw new ArgumentNullException(nameof(routeTemplate));
}
RouteTemplate = routeTemplate;
Defaults = new DispatcherValueCollection(defaults);
}
public string RouteTemplate { get; }
public DispatcherValueCollection Defaults { get; }
public int Order { get; set; }
}
}

View File

@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
// This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly.
public class RouteTemplateUrlGenerator
{
private readonly DispatcherValueAddressSelector _addressSelector;
private readonly TemplateAddressSelector _addressSelector;
private readonly ObjectPool<UriBuildingContext> _pool;
private readonly UrlEncoder _urlEncoder;
public RouteTemplateUrlGenerator(DispatcherValueAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> pool)
public RouteTemplateUrlGenerator(TemplateAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> pool)
{
_addressSelector = addressSelector;
_urlEncoder = urlEncoder;
@ -37,14 +37,13 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
throw new ArgumentNullException(nameof(values));
}
var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values));
var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values)) as ITemplateAddress;
if (address == null)
{
throw new InvalidOperationException("Can't find address");
}
var (template, defaults) = GetRouteTemplate(address);
var binder = new TemplateBinder(_urlEncoder, _pool, TemplateParser.Parse(template), defaults.AsRouteValueDictionary());
var binder = new TemplateBinder(_urlEncoder, _pool, TemplateParser.Parse(address.Template), new RouteValueDictionary());
var feature = httpContext.Features.Get<IDispatcherFeature>();
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
@ -55,18 +54,5 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
return binder.BindValues(result.AcceptedValues);
}
private (string, DispatcherValueCollection) GetRouteTemplate(Address address)
{
for (var i = address.Metadata.Count - 1; i >= 0; i--)
{
var metadata = address.Metadata[i] as IRouteTemplateMetadata;
if (metadata != null)
{
return (metadata.RouteTemplate, metadata.Defaults);
}
}
return (null, null);
}
}
}

View File

@ -142,16 +142,16 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
{
var endpoint = endpoints[i];
var metadata = endpoint.Metadata.OfType<ITreeDispatcherMetadata>().LastOrDefault();
if (metadata == null)
var templateEndpoint = endpoint as ITemplateEndpoint;
if (templateEndpoint == null)
{
continue;
}
if (!groups.TryGetValue(new Key(metadata.Order, metadata.RouteTemplate), out var group))
if (!groups.TryGetValue(new Key(0, templateEndpoint.Template), out var group))
{
group = new List<Endpoint>();
groups.Add(new Key(metadata.Order, metadata.RouteTemplate), group);
groups.Add(new Key(0, templateEndpoint.Template), group);
}
group.Add(endpoint);

View File

@ -58,11 +58,11 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
{
Endpoints =
{
new SimpleEndpoint(Products_Get, new object[]{ new RouteTemplateMetadata("api/products"), }),
new TemplateEndpoint("api/products", Products_Get),
},
});
options.HandlerFactories.Add(endpoint => (endpoint as SimpleEndpoint)?.HandlerFactory);
options.HandlerFactories.Add(endpoint => (endpoint as TemplateEndpoint)?.HandlerFactory);
}
private Task Products_Get(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Get");