예제 #1
0
        public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
        {
            if (original == null)
            {
                throw new ArgumentNullException(nameof(original));
            }

            return(SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues)));
        }
예제 #2
0
        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);
        }
예제 #3
0
        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));
        }