Add Addresses and link generation
This commit is contained in:
parent
475712d613
commit
7685e17e80
|
|
@ -1,21 +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;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace DispatcherSample
|
|
||||||
{
|
|
||||||
public class RouteValueAddress : Address
|
|
||||||
{
|
|
||||||
public RouteValueAddress(string displayName, RouteValueDictionary dictionary)
|
|
||||||
{
|
|
||||||
DisplayName = displayName;
|
|
||||||
RouteValueDictionary = dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string DisplayName { get; }
|
|
||||||
|
|
||||||
public RouteValueDictionary RouteValueDictionary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +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;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace DispatcherSample
|
|
||||||
{
|
|
||||||
public class RouteValueAddressTable
|
|
||||||
{
|
|
||||||
public IList<RouteValueAddress> Addresses
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var addresses = new List<RouteValueAddress>
|
|
||||||
{
|
|
||||||
new RouteValueAddress("Mickey", new RouteValueDictionary (new { Character = "Mickey" })),
|
|
||||||
new RouteValueAddress("Hakuna Matata", new RouteValueDictionary (new { Movie = "The Lion King"})),
|
|
||||||
new RouteValueAddress("Simba", new RouteValueDictionary (new { Movie = "The Lion King", Character = "Simba" })),
|
|
||||||
new RouteValueAddress("Mufasa", new RouteValueDictionary (new { Movie = "The Lion King", Character = "Mufasa" })),
|
|
||||||
new RouteValueAddress("Aladdin", new RouteValueDictionary (new { Movie = "Aladdin", Character = "Genie" })),
|
|
||||||
};
|
|
||||||
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -30,6 +30,13 @@ namespace DispatcherSample
|
||||||
{
|
{
|
||||||
options.Dispatchers.Add(new RouteTemplateDispatcher("{controller=Home}/{action=Index}/{id?}", ConstraintResolver)
|
options.Dispatchers.Add(new RouteTemplateDispatcher("{controller=Home}/{action=Index}/{id?}", ConstraintResolver)
|
||||||
{
|
{
|
||||||
|
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()"),
|
||||||
|
},
|
||||||
Endpoints =
|
Endpoints =
|
||||||
{
|
{
|
||||||
new SimpleEndpoint(Home_Index, Array.Empty<object>(), new { controller = "Home", action = "Index", }, "Home:Index()"),
|
new SimpleEndpoint(Home_Index, Array.Empty<object>(), new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||||
|
|
@ -43,14 +50,14 @@ namespace DispatcherSample
|
||||||
new DispatcherValueEndpointSelector(),
|
new DispatcherValueEndpointSelector(),
|
||||||
new HttpMethodEndpointSelector(),
|
new HttpMethodEndpointSelector(),
|
||||||
}
|
}
|
||||||
}.InvokeAsync);
|
});
|
||||||
|
|
||||||
options.HandlerFactories.Add((endpoint) => (endpoint as SimpleEndpoint)?.HandlerFactory);
|
options.HandlerFactories.Add((endpoint) => (endpoint as SimpleEndpoint)?.HandlerFactory);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<UrlGenerator>();
|
|
||||||
services.AddSingleton<RouteValueAddressTable>();
|
|
||||||
services.AddDispatcher();
|
services.AddDispatcher();
|
||||||
|
services.AddRouting();
|
||||||
|
services.AddSingleton<RouteTemplateUrlGenerator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
|
||||||
|
|
@ -85,12 +92,15 @@ namespace DispatcherSample
|
||||||
|
|
||||||
public static Task Home_Index(HttpContext httpContext)
|
public static Task Home_Index(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var urlGenerator = httpContext.RequestServices.GetService<UrlGenerator>();
|
var url = httpContext.RequestServices.GetService<RouteTemplateUrlGenerator>();
|
||||||
var url = urlGenerator.GenerateURL(new RouteValueDictionary(new { Movie = "The Lion King", Character = "Mufasa" }), httpContext);
|
|
||||||
return httpContext.Response.WriteAsync(
|
return httpContext.Response.WriteAsync(
|
||||||
$"<html>" +
|
$"<html>" +
|
||||||
$"<body>" +
|
$"<body>" +
|
||||||
$"<p>Generated url: {url}</p>" +
|
$"<h1>Some links you can visit</h1>" +
|
||||||
|
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Home", action = "Index", })}\">Home:Index()</a></p>" +
|
||||||
|
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Home", action = "About", })}\">Home:About()</a></p>" +
|
||||||
|
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Admin", action = "Index", })}\">Admin:Index()</a></p>" +
|
||||||
|
$"<p><a href=\"{url.GenerateUrl(httpContext, new { controller = "Admin", action = "Users", })}\">Admin:GetUsers()/Admin:EditUsers()</a></p>" +
|
||||||
$"</body>" +
|
$"</body>" +
|
||||||
$"</html>");
|
$"</html>");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +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.Http;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace DispatcherSample
|
|
||||||
{
|
|
||||||
public class UrlGenerator
|
|
||||||
{
|
|
||||||
private readonly RouteValueAddressTable _addressTable;
|
|
||||||
|
|
||||||
public UrlGenerator(RouteValueAddressTable addressTable)
|
|
||||||
{
|
|
||||||
_addressTable = addressTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Find match from values to a template
|
|
||||||
public string GenerateURL(RouteValueDictionary routeValues, HttpContext context)
|
|
||||||
{
|
|
||||||
var address = FindAddress(_addressTable, routeValues);
|
|
||||||
return $"RouteName: {address.DisplayName} URL: /{address.RouteValueDictionary["Character"]}/{address.RouteValueDictionary["Movie"]}";
|
|
||||||
}
|
|
||||||
|
|
||||||
//Look up the Addresses table
|
|
||||||
private RouteValueAddress FindAddress(RouteValueAddressTable addressTable, RouteValueDictionary routeValues)
|
|
||||||
{
|
|
||||||
var addressMatch = new RouteValueAddress(null, new RouteValueDictionary());
|
|
||||||
foreach (var address in addressTable.Addresses)
|
|
||||||
{
|
|
||||||
foreach (var key in address.RouteValueDictionary.Keys)
|
|
||||||
{
|
|
||||||
if (!routeValues.Keys.Contains(key))
|
|
||||||
{
|
|
||||||
addressMatch.RouteValueDictionary.Clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeValues.Values.Contains(address.RouteValueDictionary[key]))
|
|
||||||
{
|
|
||||||
addressMatch.RouteValueDictionary[key] = routeValues[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addressMatch.RouteValueDictionary.Count == routeValues.Count)
|
|
||||||
{
|
|
||||||
return new RouteValueAddress(address.DisplayName, address.RouteValueDictionary);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
addressMatch.RouteValueDictionary.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addressMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
// 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.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay("{DisplayName,nq}")]
|
||||||
public abstract class Address
|
public abstract class Address
|
||||||
{
|
{
|
||||||
public abstract string DisplayName { get; }
|
public abstract string DisplayName { get; }
|
||||||
|
|
||||||
|
public abstract IReadOnlyList<object> Metadata { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Abstractions
|
||||||
|
{
|
||||||
|
class AddressGroup
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public abstract class AddressTable
|
||||||
|
{
|
||||||
|
public abstract IReadOnlyList<IReadOnlyList<Address>> AddressGroups { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay("{DisplayName,nq}")]
|
||||||
public abstract class Endpoint
|
public abstract class Endpoint
|
||||||
{
|
{
|
||||||
public abstract string DisplayName { get; }
|
public abstract string DisplayName { get; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public interface IAddressCollectionProvider
|
||||||
|
{
|
||||||
|
IReadOnlyList<Address> Addresses { get; }
|
||||||
|
|
||||||
|
IChangeToken ChangeToken { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public interface IEndpointCollectionProvider
|
||||||
|
{
|
||||||
|
IReadOnlyList<Endpoint> Endpoints { get; }
|
||||||
|
|
||||||
|
IChangeToken ChangeToken { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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 Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public class DefaultAddressTable : AddressTable
|
||||||
|
{
|
||||||
|
private readonly DispatcherOptions _options;
|
||||||
|
private readonly List<Address>[] _groups;
|
||||||
|
|
||||||
|
public DefaultAddressTable(IOptions<DispatcherOptions> options)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
_options = options.Value;
|
||||||
|
|
||||||
|
_groups = new List<Address>[options.Value.Dispatchers.Count];
|
||||||
|
for (var i = 0; i < options.Value.Dispatchers.Count; i++)
|
||||||
|
{
|
||||||
|
_groups[i] = new List<Address>(options.Value.Dispatchers[i].AddressProvider?.Addresses ?? Array.Empty<Address>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<IReadOnlyList<Address>> AddressGroups => _groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,13 +6,29 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public abstract class DispatcherBase
|
public abstract class DispatcherBase : IAddressCollectionProvider, IEndpointCollectionProvider
|
||||||
{
|
{
|
||||||
private IList<Endpoint> _endpoints;
|
private List<Address> _addresses;
|
||||||
private IList<EndpointSelector> _endpointSelectors;
|
private List<Endpoint> _endpoints;
|
||||||
|
private List<EndpointSelector> _endpointSelectors;
|
||||||
|
|
||||||
|
public virtual IList<Address> Addresses
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_addresses == null)
|
||||||
|
{
|
||||||
|
_addresses = new List<Address>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _addresses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual IList<Endpoint> Endpoints
|
public virtual IList<Endpoint> Endpoints
|
||||||
{
|
{
|
||||||
|
|
@ -40,6 +56,12 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IChangeToken ChangeToken => NullChangeToken.Singleton;
|
||||||
|
|
||||||
|
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => _addresses;
|
||||||
|
|
||||||
|
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => _endpoints;
|
||||||
|
|
||||||
public virtual async Task InvokeAsync(HttpContext httpContext)
|
public virtual async Task InvokeAsync(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (httpContext == null)
|
if (httpContext == null)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.ObjectModel;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public class DispatcherCollection : Collection<DispatcherEntry>
|
||||||
|
{
|
||||||
|
public void Add(DispatcherBase dispatcher)
|
||||||
|
{
|
||||||
|
if (dispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(new DispatcherEntry()
|
||||||
|
{
|
||||||
|
Dispatcher = dispatcher.InvokeAsync,
|
||||||
|
AddressProvider = dispatcher,
|
||||||
|
EndpointProvider = dispatcher,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(RequestDelegate dispatcher)
|
||||||
|
{
|
||||||
|
if (dispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(new DispatcherEntry()
|
||||||
|
{
|
||||||
|
Dispatcher = dispatcher,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public class DispatcherEntry
|
||||||
|
{
|
||||||
|
public RequestDelegate Dispatcher { get; set; }
|
||||||
|
|
||||||
|
public IAddressCollectionProvider AddressProvider { get; set; }
|
||||||
|
|
||||||
|
public IEndpointCollectionProvider EndpointProvider { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
foreach (var entry in _options.Dispatchers)
|
foreach (var entry in _options.Dispatchers)
|
||||||
{
|
{
|
||||||
await entry(httpContext);
|
await entry.Dispatcher(httpContext);
|
||||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Matched endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
_logger.LogInformation("Matched endpoint {Endpoint}", feature.Endpoint.DisplayName);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
// 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 Microsoft.AspNetCore.Http;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public class DispatcherOptions
|
public class DispatcherOptions
|
||||||
{
|
{
|
||||||
public IList<RequestDelegate> Dispatchers { get; } = new List<RequestDelegate>();
|
public DispatcherCollection Dispatchers { get; } = new DispatcherCollection();
|
||||||
|
|
||||||
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddSingleton<IStartupFilter, DispatcherEndpointStartupFilter>();
|
services.AddSingleton<IStartupFilter, DispatcherEndpointStartupFilter>();
|
||||||
|
services.AddSingleton<AddressTable, DefaultAddressTable>();
|
||||||
|
services.AddSingleton<DispatcherValueAddressSelector>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
public class DispatcherValueAddress : Address
|
||||||
|
{
|
||||||
|
public DispatcherValueAddress(object values)
|
||||||
|
: this(values, Array.Empty<object>(), null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DispatcherValueAddress(object values, IEnumerable<object> metadata)
|
||||||
|
: this(values, metadata, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DispatcherValueAddress(object values, IEnumerable<object> metadata, string displayName)
|
||||||
|
{
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
Values = new DispatcherValueCollection(values);
|
||||||
|
Metadata = metadata.ToArray();
|
||||||
|
DisplayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string DisplayName { get; }
|
||||||
|
|
||||||
|
public override IReadOnlyList<object> Metadata { get; }
|
||||||
|
|
||||||
|
public DispatcherValueCollection Values { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Dispatcher
|
||||||
|
{
|
||||||
|
// This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly.
|
||||||
|
public class DispatcherValueAddressSelector
|
||||||
|
{
|
||||||
|
private readonly AddressTable _addressTable;
|
||||||
|
|
||||||
|
public DispatcherValueAddressSelector(AddressTable addressTable)
|
||||||
|
{
|
||||||
|
if (addressTable == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(addressTable));
|
||||||
|
}
|
||||||
|
|
||||||
|
_addressTable = addressTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address SelectAddress(DispatcherValueCollection values)
|
||||||
|
{
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the current state so we don't see partial updates.
|
||||||
|
var groups = _addressTable.AddressGroups;
|
||||||
|
for (var i = 0; i < groups.Count; i++)
|
||||||
|
{
|
||||||
|
var matches = new List<Address>();
|
||||||
|
var group = groups[i];
|
||||||
|
|
||||||
|
for (var j = 0; j < group.Count; j++)
|
||||||
|
{
|
||||||
|
var address = group[j] as DispatcherValueAddress;
|
||||||
|
if (address == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsMatch(address, values))
|
||||||
|
{
|
||||||
|
matches.Add(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (matches.Count)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// No match, keep going.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return matches[0];
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("Ambiguous bro!");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsMatch(DispatcherValueAddress address, DispatcherValueCollection values)
|
||||||
|
{
|
||||||
|
foreach (var kvp in address.Values)
|
||||||
|
{
|
||||||
|
values.TryGetValue(kvp.Key, out var value);
|
||||||
|
|
||||||
|
if (!string.Equals(Convert.ToString(kvp.Value) ?? string.Empty, Convert.ToString(value) ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Dispatcher;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||||
|
{
|
||||||
|
public interface IRouteTemplateMetadata
|
||||||
|
{
|
||||||
|
string RouteTemplate { get; }
|
||||||
|
|
||||||
|
DispatcherValueCollection Defaults { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
// 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.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Dispatcher;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
|
||||||
|
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 ObjectPool<UriBuildingContext> _pool;
|
||||||
|
private readonly UrlEncoder _urlEncoder;
|
||||||
|
|
||||||
|
public RouteTemplateUrlGenerator(DispatcherValueAddressSelector addressSelector, UrlEncoder urlEncoder, ObjectPool<UriBuildingContext> pool)
|
||||||
|
{
|
||||||
|
_addressSelector = addressSelector;
|
||||||
|
_urlEncoder = urlEncoder;
|
||||||
|
_pool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateUrl(HttpContext httpContext, object values)
|
||||||
|
{
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = _addressSelector.SelectAddress(new DispatcherValueCollection(values));
|
||||||
|
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 feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||||
|
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue