private VirtualPathData GenerateVirtualPath( VirtualPathContext context, OutboundRouteEntry entry, TemplateBinder binder) { // In attribute the context includes the values that are used to select this entry - typically // these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't // want to pass these to the link generation code, or else they will end up as query parameters. // // So, we need to exclude from here any values that are 'required link values', but aren't // parameters in the template. // // Ex: // template: api/Products/{action} // required values: { id = "5", action = "Buy", Controller = "CoolProducts" } // // result: { id = "5", action = "Buy" } var inputValues = new RouteValueDictionary(); foreach (var kvp in context.Values) { if (entry.RequiredLinkValues.ContainsKey(kvp.Key)) { var parameter = entry.RouteTemplate.GetParameter(kvp.Key); if (parameter == null) { continue; } } inputValues.Add(kvp.Key, kvp.Value); } var bindingResult = binder.GetValues(context.AmbientValues, inputValues); if (bindingResult == null) { // A required parameter in the template didn't get a value. return(null); } var matched = RouteConstraintMatcher.Match( entry.Constraints, bindingResult.CombinedValues, context.HttpContext, this, RouteDirection.UrlGeneration, _constraintLogger); if (!matched) { // A constraint rejected this link. return(null); } var pathData = entry.Handler.GetVirtualPath(context); if (pathData != null) { // If path is non-null then the target router short-circuited, we don't expect this // in typical MVC scenarios. return(pathData); } var path = binder.BindValues(bindingResult.AcceptedValues); if (path == null) { return(null); } return(new VirtualPathData(this, path)); }
private VirtualPathData GenerateVirtualPath(VirtualPathContext context, AttributeRouteLinkGenerationEntry entry) { // In attribute the context includes the values that are used to select this entry - typically // these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't // want to pass these to the link generation code, or else they will end up as query parameters. // // So, we need to exclude from here any values that are 'required link values', but aren't // parameters in the template. // // Ex: // template: api/Products/{action} // required values: { id = "5", action = "Buy", Controller = "CoolProducts" } // // result: { id = "5", action = "Buy" } var inputValues = new Dictionary <string, object>(StringComparer.OrdinalIgnoreCase); foreach (var kvp in context.Values) { if (entry.RequiredLinkValues.ContainsKey(kvp.Key)) { var parameter = entry.Template.Parameters .FirstOrDefault(p => string.Equals(p.Name, kvp.Key, StringComparison.OrdinalIgnoreCase)); if (parameter == null) { continue; } } inputValues.Add(kvp.Key, kvp.Value); } var bindingResult = entry.Binder.GetValues(context.AmbientValues, inputValues); if (bindingResult == null) { // A required parameter in the template didn't get a value. return(null); } var matched = RouteConstraintMatcher.Match( entry.Constraints, bindingResult.CombinedValues, context.Context, this, RouteDirection.UrlGeneration, _constraintLogger); if (!matched) { // A constraint rejected this link. return(null); } // These values are used to signal to the next route what we would produce if we round-tripped // (generate a link and then parse). In MVC the 'next route' is typically the MvcRouteHandler. var providedValues = new Dictionary <string, object>( bindingResult.AcceptedValues, StringComparer.OrdinalIgnoreCase); providedValues.Add(AttributeRouting.RouteGroupKey, entry.RouteGroup); var childContext = new VirtualPathContext(context.Context, context.AmbientValues, context.Values) { ProvidedValues = providedValues, }; var pathData = _next.GetVirtualPath(childContext); if (pathData != null) { // If path is non-null then the target router short-circuited, we don't expect this // in typical MVC scenarios. return(pathData); } else if (!childContext.IsBound) { // The target router has rejected these values. We don't expect this in typical MVC scenarios. return(null); } var path = entry.Binder.BindValues(bindingResult.AcceptedValues); if (path == null) { return(null); } return(new VirtualPathData(this, path)); }
/// <inheritdoc /> public async Task RouteAsync(RouteContext context) { foreach (var tree in _trees) { var tokenizer = new PathTokenizer(context.HttpContext.Request.Path); var root = tree.Root; var treeEnumerator = new TreeEnumerator(root, tokenizer); // Create a snapshot before processing the route. We'll restore this snapshot before running each // to restore the state. This is likely an "empty" snapshot, which doesn't allocate. var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null); while (treeEnumerator.MoveNext()) { var node = treeEnumerator.Current; foreach (var item in node.Matches) { var entry = item.Entry; var matcher = item.TemplateMatcher; try { if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values)) { continue; } if (!RouteConstraintMatcher.Match( entry.Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { continue; } _logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText); context.RouteData.Routers.Add(entry.Handler); await entry.Handler.RouteAsync(context); if (context.Handler != null) { return; } } finally { if (context.Handler == null) { // Restore the original values to prevent polluting the route data. snapshot.Restore(); } } } } } }
/// <inheritdoc /> public async Task RouteAsync([NotNull] RouteContext context) { foreach (var matchingEntry in _matchingEntries) { var requestPath = context.HttpContext.Request.Path.Value; if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') { requestPath = requestPath.Substring(1); } var values = matchingEntry.TemplateMatcher.Match(requestPath); if (values == null) { // If we got back a null value set, that means the URI did not match continue; } var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); newRouteData.Routers.Add(matchingEntry.Target); MergeValues(newRouteData.Values, values); if (!RouteConstraintMatcher.Match( matchingEntry.Constraints, newRouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { continue; } _logger.LogInformation( "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.", matchingEntry.RouteName, matchingEntry.RouteTemplate); try { context.RouteData = newRouteData; await matchingEntry.Target.RouteAsync(context); } finally { // Restore the original values to prevent polluting the route data. if (!context.IsHandled) { context.RouteData = oldRouteData; } } if (context.IsHandled) { break; } } }
public async virtual Task RouteAsync([NotNull] RouteContext context) { EnsureLoggers(context.HttpContext); using (_logger.BeginScope("TemplateRoute.RouteAsync")) { var requestPath = context.HttpContext.Request.Path.Value; if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') { requestPath = requestPath.Substring(1); } var values = _matcher.Match(requestPath, Defaults); if (values == null) { if (_logger.IsEnabled(TraceType.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, values, matchedValues: false, matchedConstraints: false, handled: context.IsHandled)); } // If we got back a null value set, that means the URI did not match return; } else { // Not currently doing anything to clean this up if it's not a match. Consider hardening this. context.RouteData.Values = values; if (RouteConstraintMatcher.Match(Constraints, values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { context.RouteData.DataTokens = _dataTokens; await _target.RouteAsync(context); if (_logger.IsEnabled(TraceType.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, values, matchedValues: true, matchedConstraints: true, handled: context.IsHandled)); } } else { if (_logger.IsEnabled(TraceType.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, values, matchedValues: true, matchedConstraints: false, handled: context.IsHandled)); } } } } }
public async virtual Task RouteAsync(RouteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } EnsureLoggers(context.HttpContext); var requestPath = context.HttpContext.Request.Path; var values = _matcher.Match(requestPath); if (values == null) { // If we got back a null value set, that means the URI did not match return; } var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); // Perf: Avoid accessing data tokens if you don't need to write to it, these dictionaries are all // created lazily. if (_dataTokens.Count > 0) { MergeValues(newRouteData.DataTokens, _dataTokens); } newRouteData.Routers.Add(_target); MergeValues(newRouteData.Values, values); if (!RouteConstraintMatcher.Match( Constraints, newRouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { return; } _logger.LogVerbose( "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.", Name, RouteTemplate); try { context.RouteData = newRouteData; await _target.RouteAsync(context); } finally { // Restore the original values to prevent polluting the route data. if (!context.IsHandled) { context.RouteData = oldRouteData; } } }
public async virtual Task RouteAsync([NotNull] RouteContext context) { EnsureLoggers(context.HttpContext); using (_logger.BeginScope("TemplateRoute.RouteAsync")) { var requestPath = context.HttpContext.Request.Path.Value; if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') { requestPath = requestPath.Substring(1); } var values = _matcher.Match(requestPath); if (values == null) { if (_logger.IsEnabled(LogLevel.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, context.RouteData.Values, matchedValues: false, matchedConstraints: false, handled: context.IsHandled)); } // If we got back a null value set, that means the URI did not match return; } var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); MergeValues(newRouteData.DataTokens, _dataTokens); newRouteData.Routers.Add(_target); MergeValues(newRouteData.Values, values); if (!RouteConstraintMatcher.Match( Constraints, newRouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { if (_logger.IsEnabled(LogLevel.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, newRouteData.Values, matchedValues: true, matchedConstraints: false, handled: context.IsHandled)); } return; } try { context.RouteData = newRouteData; await _target.RouteAsync(context); if (_logger.IsEnabled(LogLevel.Verbose)) { _logger.WriteValues(CreateRouteAsyncValues( requestPath, newRouteData.Values, matchedValues: true, matchedConstraints: true, handled: context.IsHandled)); } } finally { // Restore the original values to prevent polluting the route data. if (!context.IsHandled) { context.RouteData = oldRouteData; } } } }