private static bool ValidateControllerConstraint(ActionDescriptor action, MvcEndpointInfo endpointInfo) { if (action is ControllerActionDescriptor controllerActionDescriptor) { return(endpointInfo.ControllerType.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo)); } return(false); }
private bool UseDefaultValuePlusRemainingSegmentsOptional( int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, List <RoutePatternPathSegment> pathSegments) { // Check whether the remaining segments are all optional and one or more of them is // for area/controller/action and has a default value var usedDefaultValue = false; for (var i = segmentIndex; i < pathSegments.Count; i++) { var segment = pathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart) { if (parameterPart.IsOptional || parameterPart.IsCatchAll) { continue; } if (action.RouteValues.ContainsKey(parameterPart.Name)) { if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue && action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) { usedDefaultValue = true; continue; } } } else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart && separatorPart.Content == ".") { // Check if this pattern ends in an optional extension, e.g. ".{ext?}" // Current literal must be "." and followed by a single optional parameter part var nextPartIndex = j + 1; if (nextPartIndex == segment.Parts.Count - 1 && segment.Parts[nextPartIndex].IsParameter && segment.Parts[nextPartIndex] is RoutePatternParameterPart extensionParameterPart && extensionParameterPart.IsOptional) { continue; } } // Stop because there is a non-optional/non-defaulted trailing value return(false); } } return(usedDefaultValue); }
private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey) { if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue)) { // Action does not have a value for this routeKey, most likely because action is not in an area // Check that the pattern does not have a parameter for the routeKey var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey); if (matchingParameter == null && (!endpointInfo.ParsedPattern.Defaults.TryGetValue(routeKey, out var value) || !string.IsNullOrEmpty(Convert.ToString(value)))) { return(true); } } else { if (endpointInfo.MergedDefaults != null && string.Equals(actionValue, endpointInfo.MergedDefaults[routeKey] as string, StringComparison.OrdinalIgnoreCase)) { return(true); } var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey); if (matchingParameter != null) { // Check that the value matches against constraints on that parameter // e.g. For {controller:regex((Home|Login))} the controller value must match the regex if (endpointInfo.ParameterPolicies.TryGetValue(routeKey, out var parameterPolicies)) { foreach (var policy in parameterPolicies) { if (policy is IRouteConstraint constraint && !constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return(false); } } } return(true); } } return(false); }
private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey) { if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue)) { // Action does not have a value for this routeKey, most likely because action is not in an area // Check that the template does not have a parameter for the routeKey var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); if (matchingParameter == null) { return(true); } } else { if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase)) { return(true); } var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); if (matchingParameter != null) { // Check that the value matches against constraints on that parameter // e.g. For {controller:regex((Home|Login))} the controller value must match the regex // // REVIEW: This is really ugly if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return(false); } return(true); } } return(false); }
private bool UseDefaultValuePlusRemainingSegementsOptional( int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, RouteTemplate template) { // Check whether the remaining segments are all optional and one or more of them is // for area/controller/action and has a default value var usedDefaultValue = false; for (var i = segmentIndex; i < template.Segments.Count; i++) { var segment = template.Segments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll) { continue; } if (part.IsParameter) { if (IsMvcParameter(part.Name)) { if (endpointInfo.MergedDefaults[part.Name] is string defaultValue && action.RouteValues.TryGetValue(part.Name, out var routeValue) && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) { usedDefaultValue = true; continue; } } } // Stop because there is a non-optional/non-defaulted trailing value return(false); } } return(usedDefaultValue); }
private void UpdatePathSegments( int i, ActionDescriptor action, IDictionary <string, string> resolvedRequiredValues, RoutePattern routePattern, List <RoutePatternPathSegment> newPathSegments, ref IDictionary <string, IList <IParameterPolicy> > allParameterPolicies) { List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part is RoutePatternParameterPart parameterPart) { if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } if (allParameterPolicies == null) { allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } // Route value could be null if it is a "known" route value. // Do not use the null value to de-normalize the route pattern, // instead leave the parameter unchanged. // e.g. // RouteValues will contain a null "page" value if there are Razor pages // Skip replacing the {page} parameter if (parameterRouteValue != null) { if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { // Check if the parameter has a transformer policy // Use the first transformer policy for (var k = 0; k < parameterPolicies.Count; k++) { if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) { parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); break; } } } segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); } } } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } }
// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values // Because of default values it is possible for a route pattern to resolve to multiple endpoints private int CreateEndpoints( List <Endpoint> endpoints, ref StringBuilder patternStringBuilder, ActionDescriptor action, int routeOrder, RoutePattern routePattern, IReadOnlyDictionary <string, object> allDefaults, IReadOnlyDictionary <string, object> nonInlineDefaults, string name, RouteValueDictionary dataTokens, IDictionary <string, IList <IParameterPolicy> > allParameterPolicies, bool suppressLinkGeneration, bool suppressPathMatching) { var newPathSegments = routePattern.PathSegments.ToList(); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // can resolve to the following endpoints: // - /Home/Index/{id?} // - /Home // - / if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments)) { var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, nonInlineDefaults, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(subEndpoint); } List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart && action.RouteValues.ContainsKey(parameterPart.Name)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } if (allParameterPolicies == null) { allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } var parameterRouteValue = action.RouteValues[parameterPart.Name]; // Replace parameter with literal value if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { // Check if the parameter has a transformer policy // Use the first transformer policy for (var k = 0; k < parameterPolicies.Count; k++) { if (parameterPolicies[k] is IParameterTransformer parameterTransformer) { parameterRouteValue = parameterTransformer.Transform(parameterRouteValue); break; } } } segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } } var endpoint = CreateEndpoint( action, name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, nonInlineDefaults, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(endpoint); return(routeOrder); string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }