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--) for (var i = context.Endpoints.Count - 1; i >= 0; i--)
{ {
var endpoint = context.Endpoints[i]; var endpoint = context.Endpoints[i];
IHttpMethodMetadata metadata = null; ITemplateEndpoint metadata = null;
for (var j = endpoint.Metadata.Count - 1; j >= 0; j--) 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) if (metadata != null)
{ {
break; 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++) return string.Equals(endpoint.HttpMethod, httpMethod, StringComparison.OrdinalIgnoreCase);
{
if (string.Equals(metadata.AllowedMethods[i], httpMethod, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
} }
} }
} }

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

View File

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

View File

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

View File

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

View File

@ -7,11 +7,11 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Dispatcher namespace Microsoft.AspNetCore.Dispatcher
{ {
// This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly. // 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; private readonly AddressTable _addressTable;
public DispatcherValueAddressSelector(AddressTable addressTable) public TemplateAddressSelector(AddressTable addressTable)
{ {
if (addressTable == null) if (addressTable == null)
{ {
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Dispatcher
for (var j = 0; j < group.Count; j++) for (var j = 0; j < group.Count; j++)
{ {
var address = group[j] as DispatcherValueAddress; var address = group[j] as ITemplateAddress;
if (address == null) if (address == null)
{ {
continue; continue;
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Dispatcher
if (IsMatch(address, values)) if (IsMatch(address, values))
{ {
matches.Add(address); matches.Add(group[j]);
} }
} }
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Dispatcher
return null; return null;
} }
private bool IsMatch(DispatcherValueAddress address, DispatcherValueCollection values) private bool IsMatch(ITemplateAddress address, DispatcherValueCollection values)
{ {
foreach (var kvp in address.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 namespace Microsoft.AspNetCore.Dispatcher
{ {
public class DispatcherValueEndpointSelector : EndpointSelector public class TemplateEndpointSelector : EndpointSelector
{ {
public override Task SelectAsync(EndpointSelectorContext context) public override Task SelectAsync(EndpointSelectorContext context)
{ {
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Dispatcher
for (var i = context.Endpoints.Count - 1; i >= 0; i--) 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)) if (!CompareRouteValues(dispatcherFeature.Values, endpoint.Values))
{ {
context.Endpoints.RemoveAt(i); 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. // This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly.
public class RouteTemplateUrlGenerator public class RouteTemplateUrlGenerator
{ {
private readonly DispatcherValueAddressSelector _addressSelector; private readonly TemplateAddressSelector _addressSelector;
private readonly ObjectPool<UriBuildingContext> _pool; private readonly ObjectPool<UriBuildingContext> _pool;
private readonly UrlEncoder _urlEncoder; private readonly UrlEncoder _urlEncoder;
public RouteTemplateUrlGenerator(DispatcherValueAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> pool) public RouteTemplateUrlGenerator(TemplateAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> pool)
{ {
_addressSelector = addressSelector; _addressSelector = addressSelector;
_urlEncoder = urlEncoder; _urlEncoder = urlEncoder;
@ -37,14 +37,13 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
throw new ArgumentNullException(nameof(values)); throw new ArgumentNullException(nameof(values));
} }
var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values)); var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values)) as ITemplateAddress;
if (address == null) if (address == null)
{ {
throw new InvalidOperationException("Can't find address"); throw new InvalidOperationException("Can't find address");
} }
var (template, defaults) = GetRouteTemplate(address); var binder = new TemplateBinder(_urlEncoder, _pool, TemplateParser.Parse(address.Template), new RouteValueDictionary());
var binder = new TemplateBinder(_urlEncoder, _pool, TemplateParser.Parse(template), defaults.AsRouteValueDictionary());
var feature = httpContext.Features.Get<IDispatcherFeature>(); var feature = httpContext.Features.Get<IDispatcherFeature>();
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values)); var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
@ -55,18 +54,5 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
return binder.BindValues(result.AcceptedValues); 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 endpoint = endpoints[i];
var metadata = endpoint.Metadata.OfType<ITreeDispatcherMetadata>().LastOrDefault(); var templateEndpoint = endpoint as ITemplateEndpoint;
if (metadata == null) if (templateEndpoint == null)
{ {
continue; 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>(); group = new List<Endpoint>();
groups.Add(new Key(metadata.Order, metadata.RouteTemplate), group); groups.Add(new Key(0, templateEndpoint.Template), group);
} }
group.Add(endpoint); group.Add(endpoint);

View File

@ -58,11 +58,11 @@ namespace Microsoft.AspNetCore.Dispatcher.FunctionalTest
{ {
Endpoints = 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"); private Task Products_Get(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Get");