Add documentation for Routing surface area (#26513)

* Add documentation for Routing surface area

* Apply suggestions from code review

Co-authored-by: James Newton-King <james@newtonking.com>

* Fix up some doc strings

Co-authored-by: James Newton-King <james@newtonking.com>
This commit is contained in:
Safia Abdalla 2020-10-02 10:10:56 -07:00 committed by GitHub
parent 63baa06482
commit cd94cd63b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 515 additions and 32 deletions

View File

@ -4,7 +4,7 @@
namespace Microsoft.AspNetCore.Http namespace Microsoft.AspNetCore.Http
{ {
/// <summary> /// <summary>
/// A collection of contants for HTTP status codes. /// A collection of constants for HTTP status codes.
/// ///
/// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml /// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
/// </summary> /// </summary>

View File

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary> /// <summary>
/// Defines a contract for a handler of a route. /// Defines a contract for a handler of a route.
/// </summary> /// </summary>
public interface IRouteHandler public interface IRouteHandler
{ {

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary> /// <summary>
/// /// Interface for implementing a router.
/// </summary> /// </summary>
public interface IRouter public interface IRouter
{ {

View File

@ -205,4 +205,4 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
} }
} }
} }

View File

@ -9,6 +9,9 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
/// <summary>
/// Constains extensions for configuring routing on an <see cref="IApplicationBuilder"/>.
/// </summary>
public static class EndpointRoutingApplicationBuilderExtensions public static class EndpointRoutingApplicationBuilderExtensions
{ {
private const string EndpointRouteBuilder = "__EndpointRouteBuilder"; private const string EndpointRouteBuilder = "__EndpointRouteBuilder";

View File

@ -40,6 +40,11 @@ namespace Microsoft.AspNetCore.Routing
_dataSources = dataSources; _dataSources = dataSources;
} }
/// <summary>
/// Instantiates a <see cref="CompositeEndpointDataSource"/> object from <paramref name="endpointDataSources"/>.
/// </summary>
/// <param name="endpointDataSources">An collection of <see cref="EndpointDataSource" /> objects.</param>
/// <returns>A <see cref="CompositeEndpointDataSource"/> </returns>
public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this() public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
{ {
_dataSources = new List<EndpointDataSource>(); _dataSources = new List<EndpointDataSource>();
@ -62,6 +67,9 @@ namespace Microsoft.AspNetCore.Routing
} }
} }
/// <summary>
/// Returns the collection of <see cref="EndpointDataSource"/> instances associated with the object.
/// </summary>
public IEnumerable<EndpointDataSource> DataSources => _dataSources; public IEnumerable<EndpointDataSource> DataSources => _dataSources;
/// <summary> /// <summary>
@ -123,12 +131,12 @@ namespace Microsoft.AspNetCore.Routing
// Refresh the endpoints from datasource so that callbacks can get the latest endpoints // Refresh the endpoints from datasource so that callbacks can get the latest endpoints
_endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray(); _endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
// Prevent consumers from re-registering callback to inflight events as that can // Prevent consumers from re-registering callback to inflight events as that can
// cause a stackoverflow // cause a stackoverflow
// Example: // Example:
// 1. B registers A // 1. B registers A
// 2. A fires event causing B's callback to get called // 2. A fires event causing B's callback to get called
// 3. B executes some code in its callback, but needs to re-register callback // 3. B executes some code in its callback, but needs to re-register callback
// in the same callback // in the same callback
var oldTokenSource = _cts; var oldTokenSource = _cts;
var oldToken = _consumerChangeToken; var oldToken = _consumerChangeToken;

View File

@ -7,10 +7,14 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Constraints namespace Microsoft.AspNetCore.Routing.Constraints
{ {
/// <summary> /// <summary>
/// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint. /// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
/// </summary> /// </summary>
public class OptionalRouteConstraint : IRouteConstraint public class OptionalRouteConstraint : IRouteConstraint
{ {
/// <summary>
/// Creates a new <see cref="OptionalRouteConstraint"/> instance given the <paramref name="innerConstraint"/>.
/// </summary>
/// <param name="innerConstraint"></param>
public OptionalRouteConstraint(IRouteConstraint innerConstraint) public OptionalRouteConstraint(IRouteConstraint innerConstraint)
{ {
if (innerConstraint == null) if (innerConstraint == null)
@ -21,8 +25,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints
InnerConstraint = innerConstraint; InnerConstraint = innerConstraint;
} }
/// <summary>
/// Gets the <see cref="IRouteConstraint"/> associated with the optional parameter.
/// </summary>
public IRouteConstraint InnerConstraint { get; } public IRouteConstraint InnerConstraint { get; }
/// <inheritdoc />
public bool Match( public bool Match(
HttpContext? httpContext, HttpContext? httpContext,
IRouter? route, IRouter? route,

View File

@ -8,10 +8,17 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Constraints namespace Microsoft.AspNetCore.Routing.Constraints
{ {
/// <summary>
/// Constrains a route parameter to match a regular expression.
/// </summary>
public class RegexRouteConstraint : IRouteConstraint public class RegexRouteConstraint : IRouteConstraint
{ {
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10); private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
/// <summary>
/// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regex"/>.
/// </summary>
/// <param name="regex">A <see cref="Regex"/> instance to use as a constraint.</param>
public RegexRouteConstraint(Regex regex) public RegexRouteConstraint(Regex regex)
{ {
if (regex == null) if (regex == null)
@ -22,6 +29,10 @@ namespace Microsoft.AspNetCore.Routing.Constraints
Constraint = regex; Constraint = regex;
} }
/// <summary>
/// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regexPattern"/>.
/// </summary>
/// <param name="regexPattern">A string containing the regex pattern.</param>
public RegexRouteConstraint(string regexPattern) public RegexRouteConstraint(string regexPattern)
{ {
if (regexPattern == null) if (regexPattern == null)
@ -35,8 +46,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints
RegexMatchTimeout); RegexMatchTimeout);
} }
/// <summary>
/// Gets the regular expression used in the route constraint.
/// </summary>
public Regex Constraint { get; private set; } public Regex Constraint { get; private set; }
/// <inheritdoc />
public bool Match( public bool Match(
HttpContext? httpContext, HttpContext? httpContext,
IRouter? route, IRouter? route,

View File

@ -14,6 +14,10 @@ namespace Microsoft.AspNetCore.Routing
/// </summary> /// </summary>
public sealed class DataTokensMetadata : IDataTokensMetadata public sealed class DataTokensMetadata : IDataTokensMetadata
{ {
/// <summary>
/// Constructor for a new <see cref="DataTokensMetadata"/> given <paramref name="dataTokens"/>.
/// </summary>
/// <param name="dataTokens">The data tokens.</param>
public DataTokensMetadata(IReadOnlyDictionary<string, object> dataTokens) public DataTokensMetadata(IReadOnlyDictionary<string, object> dataTokens)
{ {
if (dataTokens == null) if (dataTokens == null)

View File

@ -3,8 +3,14 @@
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// An interface for an <see cref="IRouter"/> with a name.
/// </summary>
public interface INamedRouter : IRouter public interface INamedRouter : IRouter
{ {
/// <summary>
/// The name of the router. Can be null.
/// </summary>
string? Name { get; } string? Name { get; }
} }
} }

View File

@ -3,8 +3,15 @@
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Interface for a router that supports appending new routes.
/// </summary>
public interface IRouteCollection : IRouter public interface IRouteCollection : IRouter
{ {
/// <summary>
/// Appends the collection of routes defined in <paramref name="router"/>.
/// </summary>
/// <param name="router">A <see cref="IRouter"/> instance.</param>
void Add(IRouter router); void Add(IRouter router);
} }
} }

View File

@ -7,8 +7,16 @@ using Microsoft.AspNetCore.Routing.Template;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Contains methods for parsing processing constraints from a route definition.
/// </summary>
public static class InlineRouteParameterParser public static class InlineRouteParameterParser
{ {
/// <summary>
/// Parses a string representing the provided <paramref name="routeParameter"/> into a <see cref="TemplatePart"/>.
/// </summary>
/// <param name="routeParameter">A string representation of the route parameter.</param>
/// <returns>A <see cref="TemplatePart"/> instance.</returns>
public static TemplatePart ParseRouteParameter(string routeParameter) public static TemplatePart ParseRouteParameter(string routeParameter)
{ {
if (routeParameter == null) if (routeParameter == null)

View File

@ -26,11 +26,20 @@ namespace Microsoft.AspNetCore.Routing.Internal
{ {
private readonly IServiceProvider _services; private readonly IServiceProvider _services;
/// <summary>
/// Constructor for a <see cref="DfaGraphWriter"/> given <paramref name="services"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
public DfaGraphWriter(IServiceProvider services) public DfaGraphWriter(IServiceProvider services)
{ {
_services = services; _services = services;
} }
/// <summary>
/// Displays a graph representation of <paramref name="dataSource"/> in DOT.
/// </summary>
/// <param name="dataSource">The <see cref="EndpointDataSource"/> to extract routes from.</param>
/// <param name="writer">The <see cref="TextWriter"/> to which the content is written.</param>
public void Write(EndpointDataSource dataSource, TextWriter writer) public void Write(EndpointDataSource dataSource, TextWriter writer)
{ {
var builder = _services.GetRequiredService<DfaMatcherBuilder>(); var builder = _services.GetRequiredService<DfaMatcherBuilder>();
@ -46,7 +55,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
// Assign each node a sequential index. // Assign each node a sequential index.
var visited = new Dictionary<DfaNode, int>(); var visited = new Dictionary<DfaNode, int>();
var tree = builder.BuildDfaTree(includeLabel: true); var tree = builder.BuildDfaTree(includeLabel: true);
writer.WriteLine("digraph DFA {"); writer.WriteLine("digraph DFA {");

View File

@ -87,6 +87,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// </typeparam> /// </typeparam>
public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class
{ {
/// <summary>
/// A default instance of the <see cref="EndpointMetadataComparer"/>.
/// </summary>
public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>(); public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>();
/// <summary> /// <summary>

View File

@ -19,8 +19,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
private const string WildcardPrefix = "*."; private const string WildcardPrefix = "*.";
// Run after HTTP methods, but before 'default'. // Run after HTTP methods, but before 'default'.
/// <inheritdoc />
public override int Order { get; } = -100; public override int Order { get; } = -100;
/// <inheritdoc />
public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer(); public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer();
bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints) bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
@ -70,6 +72,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
}); });
} }
/// <inheritdoc />
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{ {
if (httpContext == null) if (httpContext == null)
@ -189,6 +192,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new InvalidOperationException($"Could not parse host: {host}"); throw new InvalidOperationException($"Could not parse host: {host}");
} }
/// <inheritdoc />
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints) public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{ {
if (endpoints == null) if (endpoints == null)
@ -273,6 +277,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
.ToArray(); .ToArray();
} }
/// <inheritdoc />
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges) public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{ {
if (edges == null) if (edges == null)

View File

@ -6,12 +6,31 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
/// <summary>
/// Implements an interface for a matcher policy with support for generating graph representations of the endpoints.
/// </summary>
public interface INodeBuilderPolicy public interface INodeBuilderPolicy
{ {
/// <summary>
/// Evaluates if the policy matches any of the endpoints provided in <paramref name="endpoints"/>.
/// </summary>
/// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
/// <returns><see langword="true"/> if the policy applies to any of the provided <paramref name="endpoints"/>.</returns>
bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints); bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints);
/// <summary>
/// Generates a graph that representations the relationship between endpoints and hosts.
/// </summary>
/// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
/// <returns>A graph representing the relationship between endpoints and hosts.</returns>
IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints); IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints);
/// <summary>
/// Constructs a jump table given the a set of <paramref name="edges"/>.
/// </summary>
/// <param name="exitDestination">The default destination for lookups.</param>
/// <param name="edges">A list of <see cref="PolicyJumpTableEdge"/>.</param>
/// <returns>A <see cref="PolicyJumpTable"/> instance.</returns>
PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges); PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges);
} }
} }

View File

@ -5,8 +5,15 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
/// <summary>
/// Supports retrieving endpoints that fulfill a certain matcher policy.
/// </summary>
public abstract class PolicyJumpTable public abstract class PolicyJumpTable
{ {
/// <summary>
/// Returns the destination for a given <paramref name="httpContext"/> in the current jump table.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
public abstract int GetDestination(HttpContext httpContext); public abstract int GetDestination(HttpContext httpContext);
internal virtual string DebuggerToString() internal virtual string DebuggerToString()

View File

@ -3,16 +3,31 @@
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
/// <summary>
/// Represents an entry in a <see cref="PolicyJumpTable"/>.
/// </summary>
public readonly struct PolicyJumpTableEdge public readonly struct PolicyJumpTableEdge
{ {
/// <summary>
/// Constructs a new <see cref="PolicyJumpTableEdge"/> instance.
/// </summary>
/// <param name="state">Represents the match heuristic of the policy.</param>
/// <param name="destination"></param>
public PolicyJumpTableEdge(object state, int destination) public PolicyJumpTableEdge(object state, int destination)
{ {
State = state ?? throw new System.ArgumentNullException(nameof(state)); State = state ?? throw new System.ArgumentNullException(nameof(state));
Destination = destination; Destination = destination;
} }
/// <summary>
/// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
/// depending on the matcher policy.
/// </summary>
public object State { get; } public object State { get; }
/// <summary>
/// Gets the destination of the current entry.
/// </summary>
public int Destination { get; } public int Destination { get; }
} }
} }

View File

@ -6,16 +6,31 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
/// <summary>
/// Represents an edge in a matcher policy graph.
/// </summary>
public readonly struct PolicyNodeEdge public readonly struct PolicyNodeEdge
{ {
/// <summary>
/// Constructs a new <see cref="PolicyNodeEdge"/> instance.
/// </summary>
/// <param name="state">Represents the match heuristic of the policy.</param>
/// <param name="endpoints">Represents the endpoints that match the policy</param>
public PolicyNodeEdge(object state, IReadOnlyList<Endpoint> endpoints) public PolicyNodeEdge(object state, IReadOnlyList<Endpoint> endpoints)
{ {
State = state ?? throw new System.ArgumentNullException(nameof(state)); State = state ?? throw new System.ArgumentNullException(nameof(state));
Endpoints = endpoints ?? throw new System.ArgumentNullException(nameof(endpoints)); Endpoints = endpoints ?? throw new System.ArgumentNullException(nameof(endpoints));
} }
/// <summary>
/// Gets the endpoints that match the policy defined by <see cref="State"/>.
/// </summary>
public IReadOnlyList<Endpoint> Endpoints { get; } public IReadOnlyList<Endpoint> Endpoints { get; }
/// <summary>
/// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
/// depending on the matcher policy.
/// </summary>
public object State { get; } public object State { get; }
} }
} }

View File

@ -7,7 +7,7 @@ Microsoft.AspNetCore.Routing.Route
Microsoft.AspNetCore.Routing.RouteCollection</Description> Microsoft.AspNetCore.Routing.RouteCollection</Description>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<IsAspNetCoreApp>true</IsAspNetCoreApp> <IsAspNetCoreApp>true</IsAspNetCoreApp>
<NoWarn>$(NoWarn);CS1591</NoWarn> <NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;routing</PackageTags> <PackageTags>aspnetcore;routing</PackageTags>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@ -10,6 +10,9 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Provides extension methods for adding new handlers to a <see cref="IRouteBuilder"/>.
/// </summary>
public static class RequestDelegateRouteBuilderExtensions public static class RequestDelegateRouteBuilderExtensions
{ {
/// <summary> /// <summary>

View File

@ -7,10 +7,19 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Represents an instance of a route.
/// </summary>
public class Route : RouteBase public class Route : RouteBase
{ {
private readonly IRouter _target; private readonly IRouter _target;
/// <summary>
/// Constructs a new <see cref="Route"/> instance.
/// </summary>
/// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
/// <param name="routeTemplate">A string representation of the route template.</param>
/// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
public Route( public Route(
IRouter target, IRouter target,
string routeTemplate, string routeTemplate,
@ -25,6 +34,15 @@ namespace Microsoft.AspNetCore.Routing
{ {
} }
/// <summary>
/// Constructs a new <see cref="Route"/> instance.
/// </summary>
/// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
/// <param name="routeTemplate">A string representation of the route template.</param>
/// <param name="defaults">The default values for parameters in the route.</param>
/// <param name="constraints">The constraints for the route.</param>
/// <param name="dataTokens">The data tokens for the route.</param>
/// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
public Route( public Route(
IRouter target, IRouter target,
string routeTemplate, string routeTemplate,
@ -36,6 +54,16 @@ namespace Microsoft.AspNetCore.Routing
{ {
} }
/// <summary>
/// Constructs a new <see cref="Route"/> instance.
/// </summary>
/// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
/// <param name="routeName">The name of the route.</param>
/// <param name="routeTemplate">A string representation of the route template.</param>
/// <param name="defaults">The default values for parameters in the route.</param>
/// <param name="constraints">The constraints for the route.</param>
/// <param name="dataTokens">The data tokens for the route.</param>
/// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
public Route( public Route(
IRouter target, IRouter target,
string? routeName, string? routeName,
@ -45,11 +73,11 @@ namespace Microsoft.AspNetCore.Routing
RouteValueDictionary? dataTokens, RouteValueDictionary? dataTokens,
IInlineConstraintResolver inlineConstraintResolver) IInlineConstraintResolver inlineConstraintResolver)
: base( : base(
routeTemplate, routeTemplate,
routeName, routeName,
inlineConstraintResolver, inlineConstraintResolver,
defaults, defaults,
constraints, constraints,
dataTokens) dataTokens)
{ {
if (target == null) if (target == null)
@ -60,14 +88,19 @@ namespace Microsoft.AspNetCore.Routing
_target = target; _target = target;
} }
/// <summary>
/// Gets a string representation of the route template.
/// </summary>
public string? RouteTemplate => ParsedTemplate.TemplateText; public string? RouteTemplate => ParsedTemplate.TemplateText;
/// <inheritdoc />
protected override Task OnRouteMatched(RouteContext context) protected override Task OnRouteMatched(RouteContext context)
{ {
context.RouteData.Routers.Add(_target); context.RouteData.Routers.Add(_target);
return _target.RouteAsync(context); return _target.RouteAsync(context);
} }
/// <inheritdoc />
protected override VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context) protected override VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context)
{ {
return _target.GetVirtualPath(context); return _target.GetVirtualPath(context);

View File

@ -14,6 +14,9 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Base class implementation of an <see cref="IRouter"/>.
/// </summary>
public abstract class RouteBase : IRouter, INamedRouter public abstract class RouteBase : IRouter, INamedRouter
{ {
private readonly object _loggersLock = new object(); private readonly object _loggersLock = new object();
@ -23,6 +26,15 @@ namespace Microsoft.AspNetCore.Routing
private ILogger? _logger; private ILogger? _logger;
private ILogger? _constraintLogger; private ILogger? _constraintLogger;
/// <summary>
/// Creates a new <see cref="RouteBase"/> instance.
/// </summary>
/// <param name="template">The route template.</param>
/// <param name="name">The name of the route.</param>
/// <param name="constraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
/// <param name="defaults">The default values for parameters in the route.</param>
/// <param name="constraints">The constraints for the route.</param>
/// <param name="dataTokens">The data tokens for the route.</param>
public RouteBase( public RouteBase(
string? template, string? template,
string? name, string? name,
@ -56,20 +68,45 @@ namespace Microsoft.AspNetCore.Routing
} }
} }
/// <summary>
/// Gets the set of constraints associated with each route.
/// </summary>
public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; } public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
/// <summary>
/// Gets the resolver used for resolving inline constraints.
/// </summary>
protected virtual IInlineConstraintResolver ConstraintResolver { get; set; } protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
/// <summary>
/// Gets the data tokens associated with the route.
/// </summary>
public virtual RouteValueDictionary DataTokens { get; protected set; } public virtual RouteValueDictionary DataTokens { get; protected set; }
/// <summary>
/// Gets the default values for each route parameter.
/// </summary>
public virtual RouteValueDictionary Defaults { get; protected set; } public virtual RouteValueDictionary Defaults { get; protected set; }
/// <inheritdoc />
public virtual string? Name { get; protected set; } public virtual string? Name { get; protected set; }
/// <summary>
/// Gets the <see cref="RouteTemplate"/> associated with the route.
/// </summary>
public virtual RouteTemplate ParsedTemplate { get; protected set; } public virtual RouteTemplate ParsedTemplate { get; protected set; }
/// <summary>
/// Executes asynchronously whenever routing occurs.
/// </summary>
/// <param name="context">A <see cref="RouteContext"/> instance.</param>
protected abstract Task OnRouteMatched(RouteContext context); protected abstract Task OnRouteMatched(RouteContext context);
/// <summary>
/// Executes whenever a virtual path is dervied from a <paramref name="context"/>.
/// </summary>
/// <param name="context">A <see cref="VirtualPathContext"/> instance.</param>
/// <returns>A <see cref="VirtualPathData"/> instance.</returns>
protected abstract VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context); protected abstract VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context);
/// <inheritdoc /> /// <inheritdoc />
@ -168,6 +205,12 @@ namespace Microsoft.AspNetCore.Routing
return pathData; return pathData;
} }
/// <summary>
/// Extracts constatins from a given <see cref="RouteTemplate"/>.
/// </summary>
/// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
/// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
/// <param name="constraints">A collection of constraints on the route template.</param>
protected static IDictionary<string, IRouteConstraint> GetConstraints( protected static IDictionary<string, IRouteConstraint> GetConstraints(
IInlineConstraintResolver inlineConstraintResolver, IInlineConstraintResolver inlineConstraintResolver,
RouteTemplate parsedTemplate, RouteTemplate parsedTemplate,
@ -199,6 +242,11 @@ namespace Microsoft.AspNetCore.Routing
return constraintBuilder.Build(); return constraintBuilder.Build();
} }
/// <summary>
/// Gets the default values for parameters in a templates.
/// </summary>
/// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
/// <param name="defaults">A collection of defaults for each parameter.</param>
protected static RouteValueDictionary GetDefaults( protected static RouteValueDictionary GetDefaults(
RouteTemplate parsedTemplate, RouteTemplate parsedTemplate,
RouteValueDictionary? defaults) RouteValueDictionary? defaults)
@ -296,6 +344,7 @@ namespace Microsoft.AspNetCore.Routing
} }
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return ParsedTemplate.TemplateText!; return ParsedTemplate.TemplateText!;

View File

@ -10,13 +10,26 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Provides support for specifying routes in an application.
/// </summary>
public class RouteBuilder : IRouteBuilder public class RouteBuilder : IRouteBuilder
{ {
/// <summary>
/// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>.
/// </summary>
/// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
public RouteBuilder(IApplicationBuilder applicationBuilder) public RouteBuilder(IApplicationBuilder applicationBuilder)
: this(applicationBuilder, defaultHandler: null) : this(applicationBuilder, defaultHandler: null)
{ {
} }
/// <summary>
/// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>
/// and <paramref name="defaultHandler"/>.
/// </summary>
/// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="defaultHandler">The default <see cref="IRouter"/> used if a new route is added without a handler.</param>
public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter? defaultHandler) public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter? defaultHandler)
{ {
if (applicationBuilder == null) if (applicationBuilder == null)
@ -39,14 +52,19 @@ namespace Microsoft.AspNetCore.Routing
Routes = new List<IRouter>(); Routes = new List<IRouter>();
} }
/// <inheritdoc />
public IApplicationBuilder ApplicationBuilder { get; } public IApplicationBuilder ApplicationBuilder { get; }
/// <inheritdoc />
public IRouter? DefaultHandler { get; set; } public IRouter? DefaultHandler { get; set; }
/// <inheritdoc />
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
/// <inheritdoc />
public IList<IRouter> Routes { get; } public IList<IRouter> Routes { get; }
/// <inheritdoc />
public IRouter Build() public IRouter Build()
{ {
var routeCollection = new RouteCollection(); var routeCollection = new RouteCollection();

View File

@ -14,6 +14,9 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Supports managing a collection fo multiple routes.
/// </summary>
public class RouteCollection : IRouteCollection public class RouteCollection : IRouteCollection
{ {
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' }; private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
@ -24,16 +27,24 @@ namespace Microsoft.AspNetCore.Routing
private RouteOptions? _options; private RouteOptions? _options;
/// <summary>
/// Gets the route at a given index.
/// </summary>
/// <value>The route at the given index.</value>
public IRouter this[int index] public IRouter this[int index]
{ {
get { return _routes[index]; } get { return _routes[index]; }
} }
/// <summary>
/// Gets the total number of routes registered in the collection.
/// </summary>
public int Count public int Count
{ {
get { return _routes.Count; } get { return _routes.Count; }
} }
/// <inheritdoc />
public void Add(IRouter router) public void Add(IRouter router)
{ {
if (router == null) if (router == null)
@ -57,6 +68,7 @@ namespace Microsoft.AspNetCore.Routing
_routes.Add(router); _routes.Add(router);
} }
/// <inheritdoc />
public async virtual Task RouteAsync(RouteContext context) public async virtual Task RouteAsync(RouteContext context)
{ {
// Perf: We want to avoid allocating a new RouteData for each route we need to process. // Perf: We want to avoid allocating a new RouteData for each route we need to process.
@ -88,6 +100,7 @@ namespace Microsoft.AspNetCore.Routing
} }
} }
/// <inheritdoc />
public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context) public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context)
{ {
EnsureOptions(context.HttpContext); EnsureOptions(context.HttpContext);

View File

@ -9,8 +9,24 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Use to evaluate if all route parameter values match their constraints.
/// </summary>
public static class RouteConstraintMatcher public static class RouteConstraintMatcher
{ {
/// <summary>
/// Determines if <paramref name="routeValues"/> match the provided <paramref name="constraints"/>.
/// </summary>
/// <param name="constraints">The constraints for the route.</param>
/// <param name="routeValues">The route parameter values extracted from the matched route.</param>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="route">The router that this constraint belongs to.</param>
/// <param name="routeDirection">
/// Indicates whether the constraint check is performed
/// when the incoming request is handled or when a URL is generated.
/// </param>
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
/// <returns><see langword="true"/> if the all route values match their constraints.</returns>
public static bool Match( public static bool Match(
IDictionary<string, IRouteConstraint> constraints, IDictionary<string, IRouteConstraint> constraints,
RouteValueDictionary routeValues, RouteValueDictionary routeValues,

View File

@ -8,12 +8,27 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Supports building a new <see cref="RouteEndpoint"/>.
/// </summary>
public sealed class RouteEndpointBuilder : EndpointBuilder public sealed class RouteEndpointBuilder : EndpointBuilder
{ {
/// <summary>
/// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.
/// </summary>
public RoutePattern RoutePattern { get; set; } public RoutePattern RoutePattern { get; set; }
/// <summary>
/// Gets or sets the order assigned to the endpoint.
/// </summary>
public int Order { get; set; } public int Order { get; set; }
/// <summary>
/// Constructs a new <see cref="RouteEndpointBuilder"/> instance.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
/// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
/// <param name="order">The order assigned to the endpoint.</param>
public RouteEndpointBuilder( public RouteEndpointBuilder(
RequestDelegate requestDelegate, RequestDelegate requestDelegate,
RoutePattern routePattern, RoutePattern routePattern,
@ -24,6 +39,7 @@ namespace Microsoft.AspNetCore.Routing
Order = order; Order = order;
} }
/// <inheritdoc />
public override Endpoint Build() public override Endpoint Build()
{ {
if (RequestDelegate is null) if (RequestDelegate is null)

View File

@ -8,26 +8,36 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Supports implementing a handler that executes for a given route.
/// </summary>
public class RouteHandler : IRouteHandler, IRouter public class RouteHandler : IRouteHandler, IRouter
{ {
private readonly RequestDelegate _requestDelegate; private readonly RequestDelegate _requestDelegate;
/// <summary>
/// Constructs a new <see cref="RouteHandler"/> instance.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests.</param>
public RouteHandler(RequestDelegate requestDelegate) public RouteHandler(RequestDelegate requestDelegate)
{ {
_requestDelegate = requestDelegate; _requestDelegate = requestDelegate;
} }
/// <inheritdoc />
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData) public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
{ {
return _requestDelegate; return _requestDelegate;
} }
/// <inheritdoc />
public VirtualPathData? GetVirtualPath(VirtualPathContext context) public VirtualPathData? GetVirtualPath(VirtualPathContext context)
{ {
// Nothing to do. // Nothing to do.
return null; return null;
} }
/// <inheritdoc />
public Task RouteAsync(RouteContext context) public Task RouteAsync(RouteContext context)
{ {
context.Handler = _requestDelegate; context.Handler = _requestDelegate;

View File

@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Routing.Constraints;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// Represents the configurable options on a route.
/// </summary>
public class RouteOptions public class RouteOptions
{ {
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap(); private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
@ -64,6 +67,9 @@ namespace Microsoft.AspNetCore.Routing
/// </remarks> /// </remarks>
public bool SuppressCheckForUnhandledSecurityMetadata { get; set; } public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
/// <summary>
/// Gets or sets a collection of constraints on the current route.
/// </summary>
public IDictionary<string, Type> ConstraintMap public IDictionary<string, Type> ConstraintMap
{ {
get get

View File

@ -20,6 +20,9 @@ namespace Microsoft.AspNetCore.Routing
/// </remarks> /// </remarks>
public class RouteValueEqualityComparer : IEqualityComparer<object?> public class RouteValueEqualityComparer : IEqualityComparer<object?>
{ {
/// <summary>
/// A default instance of the <see cref="RouteValueEqualityComparer"/>.
/// </summary>
public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer(); public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer();
/// <inheritdoc /> /// <inheritdoc />

View File

@ -9,12 +9,21 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
/// <summary>
/// Middleware responsible for routing.
/// </summary>
public class RouterMiddleware public class RouterMiddleware
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IRouter _router; private readonly IRouter _router;
/// <summary>
/// Constructs a new <see cref="RouterMiddleware"/> instance with a given <paramref name="router"/>.
/// </summary>
/// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="router">The <see cref="IRouter"/> to use for routing requests.</param>
public RouterMiddleware( public RouterMiddleware(
RequestDelegate next, RequestDelegate next,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
@ -26,6 +35,11 @@ namespace Microsoft.AspNetCore.Builder
_logger = loggerFactory.CreateLogger<RouterMiddleware>(); _logger = loggerFactory.CreateLogger<RouterMiddleware>();
} }
/// <summary>
/// Evaluates the handler associated with the <see cref="RouteContext"/>
/// derived from <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">A <see cref="HttpContext"/> instance.</param>
public async Task Invoke(HttpContext httpContext) public async Task Invoke(HttpContext httpContext)
{ {
var context = new RouteContext(httpContext); var context = new RouteContext(httpContext);

View File

@ -3,8 +3,12 @@
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
/// <summary>
/// A feature for routing functionality.
/// </summary>
public class RoutingFeature : IRoutingFeature public class RoutingFeature : IRoutingFeature
{ {
/// <inheritdoc />
public RouteData? RouteData { get; set; } public RouteData? RouteData { get; set; }
} }
} }

View File

@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Routing.Template
Constraint = constraint; Constraint = constraint;
} }
/// <summary>
/// Creates a new <see cref="InlineConstraint"/> instance given a <see cref="RoutePatternParameterPolicyReference"/>.
/// </summary>
/// <param name="other">A <see cref="RoutePatternParameterPolicyReference"/> instance.</param>
public InlineConstraint(RoutePatternParameterPolicyReference other) public InlineConstraint(RoutePatternParameterPolicyReference other)
{ {
if (other == null) if (other == null)

View File

@ -15,11 +15,17 @@ namespace Microsoft.AspNetCore.Routing.Template
/// </summary> /// </summary>
public static class RoutePrecedence public static class RoutePrecedence
{ {
// Compute the precedence for matching a provided url /// <summary>
// e.g.: /api/template == 1.1 /// Compute the precedence for matching a provided url
// /api/template/{id} == 1.13 /// </summary>
// /api/{id:int} == 1.2 /// <example>
// /api/template/{id:int} == 1.12 /// e.g.: /api/template == 1.1
/// /api/template/{id} == 1.13
/// /api/{id:int} == 1.2
/// /api/template/{id:int} == 1.12
/// </example>
/// <param name="template">The <see cref="RouteTemplate"/> to compute precendence for.</param>
/// <returns>A <see cref="decimal"/> representing the route's precendence.</returns>
public static decimal ComputeInbound(RouteTemplate template) public static decimal ComputeInbound(RouteTemplate template)
{ {
ValidateSegementLength(template.Segments.Count); ValidateSegementLength(template.Segments.Count);
@ -61,11 +67,17 @@ namespace Microsoft.AspNetCore.Routing.Template
return precedence; return precedence;
} }
// Compute the precedence for generating a url /// <summary>
// e.g.: /api/template == 5.5 /// Compute the precedence for generating a url.
// /api/template/{id} == 5.53 /// </summary>
// /api/{id:int} == 5.4 /// <example>
// /api/template/{id:int} == 5.54 /// e.g.: /api/template == 5.5
/// /api/template/{id} == 5.53
/// /api/{id:int} == 5.4
/// /api/template/{id:int} == 5.54
/// </example>
/// <param name="template">The <see cref="RouteTemplate"/> to compute precendence for.</param>
/// <returns>A <see cref="decimal"/> representing the route's precendence.</returns>
public static decimal ComputeOutbound(RouteTemplate template) public static decimal ComputeOutbound(RouteTemplate template)
{ {
ValidateSegementLength(template.Segments.Count); ValidateSegementLength(template.Segments.Count);

View File

@ -11,11 +11,18 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Represents the template for a route.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")] [DebuggerDisplay("{DebuggerToString()}")]
public class RouteTemplate public class RouteTemplate
{ {
private const string SeparatorString = "/"; private const string SeparatorString = "/";
/// <summary>
/// Constructs a new <see cref="RouteTemplate"/> instance given <paramref name="other"/>.
/// </summary>
/// <param name="other">A <see cref="RoutePattern"/> instance.</param>
public RouteTemplate(RoutePattern other) public RouteTemplate(RoutePattern other)
{ {
if (other == null) if (other == null)
@ -42,6 +49,12 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
} }
/// <summary>
/// Constructs a a new <see cref="RouteTemplate" /> instance given the <paramref name="template"/> string
/// and a list of <paramref name="segments"/>. Computes the parameters in the route template.
/// </summary>
/// <param name="template">A string representation of the route template.</param>
/// <param name="segments">A list of <see cref="TemplateSegment"/>.</param>
public RouteTemplate(string template, List<TemplateSegment> segments) public RouteTemplate(string template, List<TemplateSegment> segments)
{ {
if (segments == null) if (segments == null)
@ -68,12 +81,26 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
} }
/// <summary>
/// Gets the string representation of the route template.
/// </summary>
public string? TemplateText { get; } public string? TemplateText { get; }
/// <summary>
/// Gets the list of <see cref="TemplatePart"/> that represent that parameters defined in the route template.
/// </summary>
public IList<TemplatePart> Parameters { get; } public IList<TemplatePart> Parameters { get; }
/// <summary>
/// Gets the list of <see cref="TemplateSegment"/> that compromise the route template.
/// </summary>
public IList<TemplateSegment> Segments { get; } public IList<TemplateSegment> Segments { get; }
/// <summary>
/// Gets the <see cref="TemplateSegment"/> at a given index.
/// </summary>
/// <param name="index">The index of the element to retrieve.</param>
/// <returns>A <see cref="TemplateSegment"/> instance.</returns>
public TemplateSegment? GetSegment(int index) public TemplateSegment? GetSegment(int index)
{ {
if (index < 0) if (index < 0)
@ -109,7 +136,7 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
/// <summary> /// <summary>
/// Converts the <see cref="RouteTemplate"/> to the equivalent /// Converts the <see cref="RouteTemplate"/> to the equivalent
/// <see cref="RoutePattern"/> /// <see cref="RoutePattern"/>
/// </summary> /// </summary>
/// <returns>A <see cref="RoutePattern"/>.</returns> /// <returns>A <see cref="RoutePattern"/>.</returns>

View File

@ -15,6 +15,9 @@ using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Supports processing and binding parameter values in a route template.
/// </summary>
public class TemplateBinder public class TemplateBinder
{ {
private readonly UrlEncoder _urlEncoder; private readonly UrlEncoder _urlEncoder;
@ -159,7 +162,12 @@ namespace Microsoft.AspNetCore.Routing.Template
_slots = AssignSlots(_pattern, _filters); _slots = AssignSlots(_pattern, _filters);
} }
// Step 1: Get the list of values we're going to try to use to match and generate this URI /// <summary>
/// Generates the parameter values in the route.
/// </summary>
/// <param name="ambientValues">The values associated with the current request.</param>
/// <param name="values">The route values to process.</param>
/// <returns>A <see cref="TemplateValuesResult"/> instance. Can be null.</returns>
public TemplateValuesResult? GetValues(RouteValueDictionary? ambientValues, RouteValueDictionary values) public TemplateValuesResult? GetValues(RouteValueDictionary? ambientValues, RouteValueDictionary values)
{ {
// Make a new copy of the slots array, we'll use this as 'scratch' space // Make a new copy of the slots array, we'll use this as 'scratch' space
@ -424,10 +432,14 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
// Step 1.5: Process constraints // Step 1.5: Process constraints
// /// <summary>
// Processes the constraints **if** they were passed in to the TemplateBinder constructor. /// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
// Returns true on success /// </summary>
// Returns false + sets the name/constraint for logging on failure. /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="combinedValues">A dictionary that contains the parameters for the route.</param>
/// <param name="parameterName">The name of the parameter.</param>
/// <param name="constraint">The constraint object.</param>
/// <returns><see langword="true"/> if constraints were processed succesfully and false otherwise.</returns>
public bool TryProcessConstraints(HttpContext? httpContext, RouteValueDictionary combinedValues, out string? parameterName, out IRouteConstraint? constraint) public bool TryProcessConstraints(HttpContext? httpContext, RouteValueDictionary combinedValues, out string? parameterName, out IRouteConstraint? constraint)
{ {
var constraints = _constraints; var constraints = _constraints;
@ -447,6 +459,11 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
// Step 2: If the route is a match generate the appropriate URI // Step 2: If the route is a match generate the appropriate URI
/// <summary>
/// Returns a string representation of the URI associated with the route.
/// </summary>
/// <param name="acceptedValues">A dictionary that contains the parameters for the route.</param>
/// <returns>The string representation of the route.</returns>
public string? BindValues(RouteValueDictionary acceptedValues) public string? BindValues(RouteValueDictionary acceptedValues)
{ {
var context = _pool.Get(); var context = _pool.Get();

View File

@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Supports matching paths to route templates and extracting parameter values.
/// </summary>
public class TemplateMatcher public class TemplateMatcher
{ {
private const string SeparatorString = "/"; private const string SeparatorString = "/";
@ -21,6 +24,11 @@ namespace Microsoft.AspNetCore.Routing.Template
private static readonly char[] Delimiters = new char[] { SeparatorChar }; private static readonly char[] Delimiters = new char[] { SeparatorChar };
private RoutePatternMatcher _routePatternMatcher; private RoutePatternMatcher _routePatternMatcher;
/// <summary>
/// Creates a new <see cref="TemplateMatcher"/> instance given a <paramref name="template"/> and <paramref name="defaults"/>.
/// </summary>
/// <param name="template">The <see cref="RouteTemplate"/> to compare against.</param>
/// <param name="defaults">The default values for parameters in the <paramref name="template"/>.</param>
public TemplateMatcher( public TemplateMatcher(
RouteTemplate template, RouteTemplate template,
RouteValueDictionary defaults) RouteValueDictionary defaults)
@ -62,10 +70,23 @@ namespace Microsoft.AspNetCore.Routing.Template
_routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults); _routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
} }
/// <summary>
/// Gets the default values for parameters in the <see cref="Template"/>.
/// </summary>
public RouteValueDictionary Defaults { get; } public RouteValueDictionary Defaults { get; }
/// <summary>
/// Gets the <see cref="RouteTemplate"/> to match against.
/// </summary>
public RouteTemplate Template { get; } public RouteTemplate Template { get; }
/// <summary>
/// Evaluates if the provided <paramref name="path"/> matches the <see cref="Template"/>. Populates
/// <paramref name="values"/> with parameter values.
/// </summary>
/// <param name="path">A <see cref="PathString"/> representing the route to match.</param>
/// <param name="values">A <see cref="RouteValueDictionary"/> to populate with parameter values.</param>
/// <returns><see langword="true"/> if <paramref name="path"/> matches <see cref="Template"/>.</returns>
public bool TryMatch(PathString path, RouteValueDictionary values) public bool TryMatch(PathString path, RouteValueDictionary values)
{ {
if (values == null) if (values == null)

View File

@ -6,8 +6,16 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Provides methods for parsing route template strings.
/// </summary>
public static class TemplateParser public static class TemplateParser
{ {
/// <summary>
/// Creates a <see cref="RouteTemplate"/> for a given <paramref name="routeTemplate"/> string.
/// </summary>
/// <param name="routeTemplate">A string representation of the route template.</param>
/// <returns>A <see cref="RouteTemplate"/> instance.</returns>
public static RouteTemplate Parse(string routeTemplate) public static RouteTemplate Parse(string routeTemplate)
{ {
if (routeTemplate == null) if (routeTemplate == null)

View File

@ -10,13 +10,23 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Represents a part of a route template segment.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")] [DebuggerDisplay("{DebuggerToString()}")]
public class TemplatePart public class TemplatePart
{ {
/// <summary>
/// Constructs a new <see cref="TemplatePart"/> instance.
/// </summary>
public TemplatePart() public TemplatePart()
{ {
} }
/// <summary>
/// Constructs a new <see cref="TemplatePart"/> instance given a <paramref name="other"/>.
/// </summary>
/// <param name="other">A <see cref="RoutePatternPart"/> instance representing the route part.</param>
public TemplatePart(RoutePatternPart other) public TemplatePart(RoutePatternPart other)
{ {
IsLiteral = other.IsLiteral || other.IsSeparator; IsLiteral = other.IsLiteral || other.IsSeparator;
@ -47,6 +57,11 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
} }
/// <summary>
/// Create a <see cref="TemplatePart"/> representing a literal route part.
/// </summary>
/// <param name="text">The text of the literate route part.</param>
/// <returns>A <see cref="TemplatePart"/> instance.</returns>
public static TemplatePart CreateLiteral(string text) public static TemplatePart CreateLiteral(string text)
{ {
return new TemplatePart() return new TemplatePart()
@ -56,6 +71,15 @@ namespace Microsoft.AspNetCore.Routing.Template
}; };
} }
/// <summary>
/// Creates a <see cref="TemplatePart"/> representing a paramter part.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="isCatchAll"><see langword="true"/> if the parameter is a catch-all parameter.</param>
/// <param name="isOptional"><see langword="true"/> if the parameter is an optional parameter.</param>
/// <param name="defaultValue">The default value of the parameter.</param>
/// <param name="inlineConstraints">A collection of constraints associated with the parameter.</param>
/// <returns>A <see cref="TemplatePart"/> instance.</returns>
public static TemplatePart CreateParameter( public static TemplatePart CreateParameter(
string name, string name,
bool isCatchAll, bool isCatchAll,
@ -79,14 +103,41 @@ namespace Microsoft.AspNetCore.Routing.Template
}; };
} }
/// <summary>
/// <see langword="true"/> if the route part is is a catch-all part (e.g. /*).
/// </summary>
public bool IsCatchAll { get; private set; } public bool IsCatchAll { get; private set; }
/// <summary>
/// <see langword="true"/> if the route part is represents a literal value.
/// </summary>
public bool IsLiteral { get; private set; } public bool IsLiteral { get; private set; }
/// <summary>
/// <see langword="true"/> if the route part represents a parameterized value.
/// </summary>
public bool IsParameter { get; private set; } public bool IsParameter { get; private set; }
/// <summary>
/// <see langword="true"/> if the route part represents an optional part.
/// </summary>
public bool IsOptional { get; private set; } public bool IsOptional { get; private set; }
/// <summary>
/// <see langword="true"/> if the route part represents an optional seperator.
/// </summary>
public bool IsOptionalSeperator { get; set; } public bool IsOptionalSeperator { get; set; }
/// <summary>
/// The name of the route parameter. Can be null.
/// </summary>
public string? Name { get; private set; } public string? Name { get; private set; }
/// <summary>
/// The textual representation of the route paramter. Can be null. Used to represent route seperators and literal parts.
/// </summary>
public string? Text { get; private set; } public string? Text { get; private set; }
/// <summary>
/// The default value for route paramters. Can be null.
/// </summary>
public object? DefaultValue { get; private set; } public object? DefaultValue { get; private set; }
/// <summary>
/// The constraints associates with a route paramter.
/// </summary>
public IEnumerable<InlineConstraint> InlineConstraints { get; private set; } = Enumerable.Empty<InlineConstraint>(); public IEnumerable<InlineConstraint> InlineConstraints { get; private set; } = Enumerable.Empty<InlineConstraint>();
internal string? DebuggerToString() internal string? DebuggerToString()
@ -101,6 +152,10 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
} }
/// <summary>
/// Creates a <see cref="RoutePatternPart"/> for the route part designated by the <see cref="TemplatePart"/>.
/// </summary>
/// <returns>A <see cref="RoutePatternPart"/> instance.</returns>
public RoutePatternPart ToRoutePatternPart() public RoutePatternPart ToRoutePatternPart()
{ {
if (IsLiteral && IsOptionalSeperator) if (IsLiteral && IsOptionalSeperator)

View File

@ -9,14 +9,24 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template namespace Microsoft.AspNetCore.Routing.Template
{ {
/// <summary>
/// Represents a segment of a route template.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")] [DebuggerDisplay("{DebuggerToString()}")]
public class TemplateSegment public class TemplateSegment
{ {
/// <summary>
/// Constructs a new <see cref="TemplateSegment"/> instance.
/// </summary>
public TemplateSegment() public TemplateSegment()
{ {
Parts = new List<TemplatePart>(); Parts = new List<TemplatePart>();
} }
/// <summary>
/// Constructs a new <see cref="TemplateSegment"/> instance given another <see cref="RoutePatternPathSegment"/>.
/// </summary>
/// <param name="other">A <see cref="RoutePatternPathSegment"/> instance.</param>
public TemplateSegment(RoutePatternPathSegment other) public TemplateSegment(RoutePatternPathSegment other)
{ {
if (other == null) if (other == null)
@ -32,8 +42,14 @@ namespace Microsoft.AspNetCore.Routing.Template
} }
} }
/// <summary>
/// <see langword="true"/> if the segment contains a single entry.
/// </summary>
public bool IsSimple => Parts.Count == 1; public bool IsSimple => Parts.Count == 1;
/// <summary>
/// Gets the list of individual parts in the template segment.
/// </summary>
public List<TemplatePart> Parts { get; } public List<TemplatePart> Parts { get; }
internal string DebuggerToString() internal string DebuggerToString()
@ -41,6 +57,10 @@ namespace Microsoft.AspNetCore.Routing.Template
return string.Join(string.Empty, Parts.Select(p => p.DebuggerToString())); return string.Join(string.Empty, Parts.Select(p => p.DebuggerToString()));
} }
/// <summary>
/// Returns a <see cref="RoutePatternPathSegment"/> for the template segment.
/// </summary>
/// <returns>A <see cref="RoutePatternPathSegment"/> instance.</returns>
public RoutePatternPathSegment ToRoutePatternPathSegment() public RoutePatternPathSegment ToRoutePatternPathSegment()
{ {
var parts = Parts.Select(p => p.ToRoutePatternPart()); var parts = Parts.Select(p => p.ToRoutePatternPart());

View File

@ -19,8 +19,10 @@ namespace Microsoft.AspNetCore.Routing.Tree
/// </summary> /// </summary>
public class TreeRouter : IRouter public class TreeRouter : IRouter
{ {
// Key used by routing and action selection to match an attribute route entry to a /// <summary>
// group of action descriptors. /// Key used by routing and action selection to match an attribute
/// route entry to agroup of action descriptors.
/// </summary>
public static readonly string RouteGroupKey = "!__route_group"; public static readonly string RouteGroupKey = "!__route_group";
private readonly LinkGenerationDecisionTree _linkGenerationTree; private readonly LinkGenerationDecisionTree _linkGenerationTree;