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
{
/// <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
/// </summary>

View File

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

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
///
/// Interface for implementing a router.
/// </summary>
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
{
/// <summary>
/// Constains extensions for configuring routing on an <see cref="IApplicationBuilder"/>.
/// </summary>
public static class EndpointRoutingApplicationBuilderExtensions
{
private const string EndpointRouteBuilder = "__EndpointRouteBuilder";

View File

@ -40,6 +40,11 @@ namespace Microsoft.AspNetCore.Routing
_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()
{
_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;
/// <summary>
@ -123,12 +131,12 @@ namespace Microsoft.AspNetCore.Routing
// Refresh the endpoints from datasource so that callbacks can get the latest endpoints
_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
// Example:
// 1. B registers A
// 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
var oldTokenSource = _cts;
var oldToken = _consumerChangeToken;

View File

@ -7,10 +7,14 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Constraints
{
/// <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>
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)
{
if (innerConstraint == null)
@ -21,8 +25,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints
InnerConstraint = innerConstraint;
}
/// <summary>
/// Gets the <see cref="IRouteConstraint"/> associated with the optional parameter.
/// </summary>
public IRouteConstraint InnerConstraint { get; }
/// <inheritdoc />
public bool Match(
HttpContext? httpContext,
IRouter? route,

View File

@ -8,10 +8,17 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Constraints
{
/// <summary>
/// Constrains a route parameter to match a regular expression.
/// </summary>
public class RegexRouteConstraint : IRouteConstraint
{
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)
{
if (regex == null)
@ -22,6 +29,10 @@ namespace Microsoft.AspNetCore.Routing.Constraints
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)
{
if (regexPattern == null)
@ -35,8 +46,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints
RegexMatchTimeout);
}
/// <summary>
/// Gets the regular expression used in the route constraint.
/// </summary>
public Regex Constraint { get; private set; }
/// <inheritdoc />
public bool Match(
HttpContext? httpContext,
IRouter? route,

View File

@ -14,6 +14,10 @@ namespace Microsoft.AspNetCore.Routing
/// </summary>
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)
{
if (dataTokens == null)

View File

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

View File

@ -3,8 +3,15 @@
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Interface for a router that supports appending new routes.
/// </summary>
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);
}
}

View File

@ -7,8 +7,16 @@ using Microsoft.AspNetCore.Routing.Template;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Contains methods for parsing processing constraints from a route definition.
/// </summary>
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)
{
if (routeParameter == null)

View File

@ -26,11 +26,20 @@ namespace Microsoft.AspNetCore.Routing.Internal
{
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)
{
_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)
{
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
@ -46,7 +55,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
// Assign each node a sequential index.
var visited = new Dictionary<DfaNode, int>();
var tree = builder.BuildDfaTree(includeLabel: true);
writer.WriteLine("digraph DFA {");

View File

@ -87,6 +87,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// </typeparam>
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>();
/// <summary>

View File

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

View File

@ -6,12 +6,31 @@ using Microsoft.AspNetCore.Http;
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
{
/// <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);
/// <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);
/// <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);
}
}

View File

@ -5,8 +5,15 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching
{
/// <summary>
/// Supports retrieving endpoints that fulfill a certain matcher policy.
/// </summary>
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);
internal virtual string DebuggerToString()

View File

@ -3,16 +3,31 @@
namespace Microsoft.AspNetCore.Routing.Matching
{
/// <summary>
/// Represents an entry in a <see cref="PolicyJumpTable"/>.
/// </summary>
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)
{
State = state ?? throw new System.ArgumentNullException(nameof(state));
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; }
/// <summary>
/// Gets the destination of the current entry.
/// </summary>
public int Destination { get; }
}
}

View File

@ -6,16 +6,31 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching
{
/// <summary>
/// Represents an edge in a matcher policy graph.
/// </summary>
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)
{
State = state ?? throw new System.ArgumentNullException(nameof(state));
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; }
/// <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; }
}
}

View File

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

View File

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

View File

@ -7,10 +7,19 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Represents an instance of a route.
/// </summary>
public class Route : RouteBase
{
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(
IRouter target,
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(
IRouter target,
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(
IRouter target,
string? routeName,
@ -45,11 +73,11 @@ namespace Microsoft.AspNetCore.Routing
RouteValueDictionary? dataTokens,
IInlineConstraintResolver inlineConstraintResolver)
: base(
routeTemplate,
routeName,
inlineConstraintResolver,
defaults,
constraints,
routeTemplate,
routeName,
inlineConstraintResolver,
defaults,
constraints,
dataTokens)
{
if (target == null)
@ -60,14 +88,19 @@ namespace Microsoft.AspNetCore.Routing
_target = target;
}
/// <summary>
/// Gets a string representation of the route template.
/// </summary>
public string? RouteTemplate => ParsedTemplate.TemplateText;
/// <inheritdoc />
protected override Task OnRouteMatched(RouteContext context)
{
context.RouteData.Routers.Add(_target);
return _target.RouteAsync(context);
}
/// <inheritdoc />
protected override VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context)
{
return _target.GetVirtualPath(context);

View File

@ -14,6 +14,9 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Base class implementation of an <see cref="IRouter"/>.
/// </summary>
public abstract class RouteBase : IRouter, INamedRouter
{
private readonly object _loggersLock = new object();
@ -23,6 +26,15 @@ namespace Microsoft.AspNetCore.Routing
private ILogger? _logger;
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(
string? template,
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; }
/// <summary>
/// Gets the resolver used for resolving inline constraints.
/// </summary>
protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
/// <summary>
/// Gets the data tokens associated with the route.
/// </summary>
public virtual RouteValueDictionary DataTokens { get; protected set; }
/// <summary>
/// Gets the default values for each route parameter.
/// </summary>
public virtual RouteValueDictionary Defaults { get; protected set; }
/// <inheritdoc />
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; }
/// <summary>
/// Executes asynchronously whenever routing occurs.
/// </summary>
/// <param name="context">A <see cref="RouteContext"/> instance.</param>
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);
/// <inheritdoc />
@ -168,6 +205,12 @@ namespace Microsoft.AspNetCore.Routing
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(
IInlineConstraintResolver inlineConstraintResolver,
RouteTemplate parsedTemplate,
@ -199,6 +242,11 @@ namespace Microsoft.AspNetCore.Routing
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(
RouteTemplate parsedTemplate,
RouteValueDictionary? defaults)
@ -296,6 +344,7 @@ namespace Microsoft.AspNetCore.Routing
}
}
/// <inheritdoc />
public override string ToString()
{
return ParsedTemplate.TemplateText!;

View File

@ -10,13 +10,26 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Provides support for specifying routes in an application.
/// </summary>
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)
: 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)
{
if (applicationBuilder == null)
@ -39,14 +52,19 @@ namespace Microsoft.AspNetCore.Routing
Routes = new List<IRouter>();
}
/// <inheritdoc />
public IApplicationBuilder ApplicationBuilder { get; }
/// <inheritdoc />
public IRouter? DefaultHandler { get; set; }
/// <inheritdoc />
public IServiceProvider ServiceProvider { get; }
/// <inheritdoc />
public IList<IRouter> Routes { get; }
/// <inheritdoc />
public IRouter Build()
{
var routeCollection = new RouteCollection();

View File

@ -14,6 +14,9 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Supports managing a collection fo multiple routes.
/// </summary>
public class RouteCollection : IRouteCollection
{
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
@ -24,16 +27,24 @@ namespace Microsoft.AspNetCore.Routing
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]
{
get { return _routes[index]; }
}
/// <summary>
/// Gets the total number of routes registered in the collection.
/// </summary>
public int Count
{
get { return _routes.Count; }
}
/// <inheritdoc />
public void Add(IRouter router)
{
if (router == null)
@ -57,6 +68,7 @@ namespace Microsoft.AspNetCore.Routing
_routes.Add(router);
}
/// <inheritdoc />
public async virtual Task RouteAsync(RouteContext context)
{
// 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)
{
EnsureOptions(context.HttpContext);

View File

@ -9,8 +9,24 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Use to evaluate if all route parameter values match their constraints.
/// </summary>
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(
IDictionary<string, IRouteConstraint> constraints,
RouteValueDictionary routeValues,

View File

@ -8,12 +8,27 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Supports building a new <see cref="RouteEndpoint"/>.
/// </summary>
public sealed class RouteEndpointBuilder : EndpointBuilder
{
/// <summary>
/// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.
/// </summary>
public RoutePattern RoutePattern { get; set; }
/// <summary>
/// Gets or sets the order assigned to the endpoint.
/// </summary>
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(
RequestDelegate requestDelegate,
RoutePattern routePattern,
@ -24,6 +39,7 @@ namespace Microsoft.AspNetCore.Routing
Order = order;
}
/// <inheritdoc />
public override Endpoint Build()
{
if (RequestDelegate is null)

View File

@ -8,26 +8,36 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// Supports implementing a handler that executes for a given route.
/// </summary>
public class RouteHandler : IRouteHandler, IRouter
{
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)
{
_requestDelegate = requestDelegate;
}
/// <inheritdoc />
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
{
return _requestDelegate;
}
/// <inheritdoc />
public VirtualPathData? GetVirtualPath(VirtualPathContext context)
{
// Nothing to do.
return null;
}
/// <inheritdoc />
public Task RouteAsync(RouteContext context)
{
context.Handler = _requestDelegate;

View File

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

View File

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

View File

@ -9,12 +9,21 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Middleware responsible for routing.
/// </summary>
public class RouterMiddleware
{
private readonly ILogger _logger;
private readonly RequestDelegate _next;
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(
RequestDelegate next,
ILoggerFactory loggerFactory,
@ -26,6 +35,11 @@ namespace Microsoft.AspNetCore.Builder
_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)
{
var context = new RouteContext(httpContext);

View File

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

View File

@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Routing.Template
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)
{
if (other == null)

View File

@ -15,11 +15,17 @@ namespace Microsoft.AspNetCore.Routing.Template
/// </summary>
public static class RoutePrecedence
{
// Compute the precedence for matching a provided url
// e.g.: /api/template == 1.1
// /api/template/{id} == 1.13
// /api/{id:int} == 1.2
// /api/template/{id:int} == 1.12
/// <summary>
/// Compute the precedence for matching a provided url
/// </summary>
/// <example>
/// 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)
{
ValidateSegementLength(template.Segments.Count);
@ -61,11 +67,17 @@ namespace Microsoft.AspNetCore.Routing.Template
return precedence;
}
// Compute the precedence for generating a url
// e.g.: /api/template == 5.5
// /api/template/{id} == 5.53
// /api/{id:int} == 5.4
// /api/template/{id:int} == 5.54
/// <summary>
/// Compute the precedence for generating a url.
/// </summary>
/// <example>
/// 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)
{
ValidateSegementLength(template.Segments.Count);

View File

@ -11,11 +11,18 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Represents the template for a route.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")]
public class RouteTemplate
{
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)
{
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)
{
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; }
/// <summary>
/// Gets the list of <see cref="TemplatePart"/> that represent that parameters defined in the route template.
/// </summary>
public IList<TemplatePart> Parameters { get; }
/// <summary>
/// Gets the list of <see cref="TemplateSegment"/> that compromise the route template.
/// </summary>
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)
{
if (index < 0)
@ -109,7 +136,7 @@ namespace Microsoft.AspNetCore.Routing.Template
}
/// <summary>
/// Converts the <see cref="RouteTemplate"/> to the equivalent
/// Converts the <see cref="RouteTemplate"/> to the equivalent
/// <see cref="RoutePattern"/>
/// </summary>
/// <returns>A <see cref="RoutePattern"/>.</returns>

View File

@ -15,6 +15,9 @@ using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Supports processing and binding parameter values in a route template.
/// </summary>
public class TemplateBinder
{
private readonly UrlEncoder _urlEncoder;
@ -159,7 +162,12 @@ namespace Microsoft.AspNetCore.Routing.Template
_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)
{
// 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
//
// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
// Returns true on success
// Returns false + sets the name/constraint for logging on failure.
/// <summary>
/// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
/// </summary>
/// <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)
{
var constraints = _constraints;
@ -447,6 +459,11 @@ namespace Microsoft.AspNetCore.Routing.Template
}
// 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)
{
var context = _pool.Get();

View File

@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Supports matching paths to route templates and extracting parameter values.
/// </summary>
public class TemplateMatcher
{
private const string SeparatorString = "/";
@ -21,6 +24,11 @@ namespace Microsoft.AspNetCore.Routing.Template
private static readonly char[] Delimiters = new char[] { SeparatorChar };
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(
RouteTemplate template,
RouteValueDictionary defaults)
@ -62,10 +70,23 @@ namespace Microsoft.AspNetCore.Routing.Template
_routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
}
/// <summary>
/// Gets the default values for parameters in the <see cref="Template"/>.
/// </summary>
public RouteValueDictionary Defaults { get; }
/// <summary>
/// Gets the <see cref="RouteTemplate"/> to match against.
/// </summary>
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)
{
if (values == null)

View File

@ -6,8 +6,16 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Provides methods for parsing route template strings.
/// </summary>
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)
{
if (routeTemplate == null)

View File

@ -10,13 +10,23 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Represents a part of a route template segment.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")]
public class TemplatePart
{
/// <summary>
/// Constructs a new <see cref="TemplatePart"/> instance.
/// </summary>
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)
{
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)
{
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(
string name,
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; }
/// <summary>
/// <see langword="true"/> if the route part is represents a literal value.
/// </summary>
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; }
/// <summary>
/// <see langword="true"/> if the route part represents an optional part.
/// </summary>
public bool IsOptional { get; private set; }
/// <summary>
/// <see langword="true"/> if the route part represents an optional seperator.
/// </summary>
public bool IsOptionalSeperator { get; set; }
/// <summary>
/// The name of the route parameter. Can be null.
/// </summary>
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; }
/// <summary>
/// The default value for route paramters. Can be null.
/// </summary>
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>();
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()
{
if (IsLiteral && IsOptionalSeperator)

View File

@ -9,14 +9,24 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Represents a segment of a route template.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")]
public class TemplateSegment
{
/// <summary>
/// Constructs a new <see cref="TemplateSegment"/> instance.
/// </summary>
public TemplateSegment()
{
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)
{
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;
/// <summary>
/// Gets the list of individual parts in the template segment.
/// </summary>
public List<TemplatePart> Parts { get; }
internal string DebuggerToString()
@ -41,6 +57,10 @@ namespace Microsoft.AspNetCore.Routing.Template
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()
{
var parts = Parts.Select(p => p.ToRoutePatternPart());

View File

@ -19,8 +19,10 @@ namespace Microsoft.AspNetCore.Routing.Tree
/// </summary>
public class TreeRouter : IRouter
{
// Key used by routing and action selection to match an attribute route entry to a
// group of action descriptors.
/// <summary>
/// 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";
private readonly LinkGenerationDecisionTree _linkGenerationTree;