public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues) { if (original == null) { throw new ArgumentNullException(nameof(original)); } return(SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues))); }
private bool MatchesConstraints(RoutePattern pattern, RoutePatternParameterPart parameter, string key, RouteValueDictionary requiredValues) { if (pattern.ParameterPolicies.TryGetValue(key, out var policies)) { for (var i = 0; i < policies.Count; i++) { var policy = _policyFactory.Create(parameter, policies[i]); if (policy is IRouteConstraint constraint) { if (!constraint.Match(httpContext: null, NullRouter.Instance, key, requiredValues, RouteDirection.IncomingRequest)) { return(false); } } } } return(true); }
private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues) { // Process each required value in sequence. Bail if we find any rejection criteria. The goal // of rejection is to avoid creating RoutePattern instances that can't *ever* match. // // If we succeed, then we need to create a new RoutePattern with the provided required values. // // Substitution can merge with existing RequiredValues already on the RoutePattern as long // as all of the success criteria are still met at the end. foreach (var kvp in requiredValues) { // There are three possible cases here: // 1. Required value is null-ish // 2. Required value is *any* // 3. Required value corresponds to a parameter // 4. Required value corresponds to a matching default value // // If none of these are true then we can reject this substitution. RoutePatternParameterPart parameter; if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty)) { // 1. Required value is null-ish - check to make sure that this route doesn't have a // parameter or filter-like default. if (original.GetParameter(kvp.Key) != null) { // Fail: we can't 'require' that a parameter be null. In theory this would be possible // for an optional parameter, but that's not really in line with the usage of this feature // so we don't handle it. // // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" } return(null); } else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) && !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue)) { // Fail: this route has a non-parameter default that doesn't match. // // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" } return(null); } // Success: (for this parameter at least) // // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... } continue; } else if (RoutePattern.IsRequiredValueAny(kvp.Value)) { // 2. Required value is *any* - this is allowed for a parameter with a default, but not // a non-parameter default. if (original.GetParameter(kvp.Key) == null && original.Defaults.TryGetValue(kvp.Key, out var defaultValue) && !RouteValueEqualityComparer.Default.Equals(string.Empty, defaultValue)) { // Fail: this route as a non-parameter default that is stricter than *any*. // // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = *any* } return(null); } // Success: (for this parameter at least) // // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = *any*, ... } continue; } else if ((parameter = original.GetParameter(kvp.Key)) != null) { // 3. Required value corresponds to a parameter - check to make sure that this value matches // any IRouteConstraint implementations. if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues)) { // Fail: this route has a constraint that failed. // // Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" } return(null); } // Success: (for this parameter at least) // // Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... } continue; } else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) && RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue)) { // 4. Required value corresponds to a matching default value - check to make sure that this value matches // any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't // hurt for us to check. if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues)) { // Fail: this route has a constraint that failed. // // Ex: // Admin/Home/{action=Index}/{id?} // defaults: { area = "Admin" } // constraints: { area = "Blog" } // with required values: { area = "Admin" } return(null); } // Success: (for this parameter at least) // // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... } continue; } else { // Fail: this is a required value for a key that doesn't appear in the templates, or the route // pattern has a different default value for a non-parameter. // // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... } // OR (less likely) // Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... } return(null); } } List <RoutePatternParameterPart> updatedParameters = null; List <RoutePatternPathSegment> updatedSegments = null; RouteValueDictionary updatedDefaults = null; // So if we get here, we're ready to update the route pattern. We need to update two things: // 1. Remove any default values that conflict with the required values. // 2. Merge any existing required values foreach (var kvp in requiredValues) { var parameter = original.GetParameter(kvp.Key); // We only need to handle the case where the required value maps to a parameter. That's the only // case where we allow a default and a required value to disagree, and we already validated the // other cases. // // If the required value is *any* then don't remove the default. if (parameter != null && !RoutePattern.IsRequiredValueAny(kvp.Value) && original.Defaults.TryGetValue(kvp.Key, out var defaultValue) && !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue)) { if (updatedDefaults == null && updatedSegments == null && updatedParameters == null) { updatedDefaults = new RouteValueDictionary(original.Defaults); updatedSegments = new List <RoutePatternPathSegment>(original.PathSegments); updatedParameters = new List <RoutePatternParameterPart>(original.Parameters); } updatedDefaults.Remove(kvp.Key); RemoveParameterDefault(updatedSegments, updatedParameters, parameter); } } foreach (var kvp in original.RequiredValues) { requiredValues.TryAdd(kvp.Key, kvp.Value); } return(new RoutePattern( original.RawText, updatedDefaults ?? original.Defaults, original.ParameterPolicies, requiredValues, updatedParameters ?? original.Parameters, updatedSegments ?? original.PathSegments)); }