Example #1
0
 public void Accept(string key, object value)
 {
     if (!_acceptedValues.ContainsKey(key))
     {
         _acceptedValues.Add(key, value);
     }
 }
        public void ListStorage_RemoveAt_RearrangesInnerArray()
        {
            // Arrange
            var dict = new DispatcherValueCollection();

            dict.Add("key", "value");
            dict.Add("key2", "value2");
            dict.Add("key3", "value3");

            // Assert 1
            var storage = Assert.IsType <DispatcherValueCollection.ListStorage>(dict._storage);

            Assert.Equal(3, storage.Count);

            // Act
            dict.Remove("key2");

            // Assert 2
            Assert.Equal(2, storage.Count);
            Assert.Equal("key", storage[0].Key);
            Assert.Equal("value", storage[0].Value);
            Assert.Equal("key3", storage[1].Key);
            Assert.Equal("value3", storage[1].Value);

            Assert.Throws <ArgumentOutOfRangeException>(() => storage[2]);
        }
Example #3
0
        public async Task MatchAsync_MatchesConstrainedEndpointsWithDefaults(string url, object[] values)
        {
            // Arrange
            var dataSource = new DefaultDispatcherDataSource()
            {
                Endpoints =
                {
                    new RoutePatternEndpoint("{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}",
                                             new { parameter1 = 1,                                                                          parameter2= 2, parameter3 = 3, parameter4 = 4 }, Test_Delegate, "Test"),
                },
            };

            var valueKeys      = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
            var expectedValues = new DispatcherValueCollection();

            for (int i = 0; i < valueKeys.Length; i++)
            {
                expectedValues.Add(valueKeys[i], values[i]);
            }

            var context = CreateMatcherContext(url);
            var factory = new TreeMatcherFactory();
            var matcher = factory.CreateMatcher(dataSource, new List <EndpointSelector>());

            // Act
            await matcher.MatchAsync(context);

            // Assert
            foreach (var entry in expectedValues)
            {
                var data = Assert.Single(context.Values, v => v.Key == entry.Key);
                Assert.Equal(entry.Value, data.Value);
            }
        }
        public void GetUrlVerifyEncoding()
        {
            var values = new DispatcherValueCollection();

            values.Add("controller", "#;?:@&=+$,");
            values.Add("action", "showcategory");
            values.Add("id", 123);
            values.Add("so?rt", "de?sc");
            values.Add("maxPrice", 100);

            RunTest(
                "{controller}.mvc/{action}/{id}",
                new DispatcherValueCollection(new { controller = "Home" }),
                new DispatcherValueCollection(new { controller = "home", action = "Index", id = (string)null }),
                values,
                "/%23;%3F%3A@%26%3D%2B$,.mvc/showcategory/123?so%3Frt=de%3Fsc&maxPrice=100",
                UrlEncoder.Default);
        }
        public void Add_EmptyStorage()
        {
            // Arrange
            var dict = new DispatcherValueCollection();

            // Act
            dict.Add("key", "value");

            // Assert
            Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
            Assert.IsType <DispatcherValueCollection.ListStorage>(dict._storage);
        }
        public void GetUrlWithEmptyStringForMiddleParameterShouldUseDefaultValue()
        {
            var ambientValues = new DispatcherValueCollection();

            ambientValues.Add("Controller", "Test");
            ambientValues.Add("Action", "Fallback");
            ambientValues.Add("param1", "fallback1");
            ambientValues.Add("param2", "fallback2");
            ambientValues.Add("param3", "fallback3");

            var values = new DispatcherValueCollection();

            values.Add("controller", "subtest");
            values.Add("param1", "b");

            RunTest(
                "{controller}.mvc/{action}/{param1}",
                new DispatcherValueCollection(new { action = "Default" }),
                ambientValues,
                values,
                "/UrlEncode[[subtest]]UrlEncode[[.mvc]]/UrlEncode[[Default]]/UrlEncode[[b]]");
        }
        public void ListStorage_DynamicallyAdjustsCapacity()
        {
            // Arrange
            var dict = new DispatcherValueCollection();

            // Act 1
            dict.Add("key", "value");

            // Assert 1
            var storage = Assert.IsType <DispatcherValueCollection.ListStorage>(dict._storage);

            Assert.Equal(4, storage.Capacity);

            // Act 2
            dict.Add("key2", "value2");
            dict.Add("key3", "value3");
            dict.Add("key4", "value4");
            dict.Add("key5", "value5");

            // Assert 2
            Assert.Equal(8, storage.Capacity);
        }
        public void GetUrlGeneratesQueryStringForNewValuesAndEscapesQueryString()
        {
            var values = new DispatcherValueCollection(new { controller = "products", action = "showcategory", id = 123, maxPrice = 100 });

            values.Add("so?rt", "de?sc");

            RunTest(
                "{controller}.mvc/{action}/{id}",
                new DispatcherValueCollection(new { controller = "Home" }),
                new DispatcherValueCollection(new { controller = "home", action = "Index", id = (string)null }),
                values,
                "/UrlEncode[[products]]UrlEncode[[.mvc]]/UrlEncode[[showcategory]]/UrlEncode[[123]]" +
                "?UrlEncode[[so?rt]]=UrlEncode[[de?sc]]&UrlEncode[[maxPrice]]=UrlEncode[[100]]");
        }
        public void GetUrlWithEmptyStringForMiddleParameterIgnoresRemainingParameters()
        {
            var ambientValues = new DispatcherValueCollection();

            ambientValues.Add("controller", "UrlRouting");
            ambientValues.Add("action", "Play");
            ambientValues.Add("category", "Photos");
            ambientValues.Add("year", "2008");
            ambientValues.Add("occasion", "Easter");
            ambientValues.Add("SafeParam", "SafeParamValue");

            var values = new DispatcherValueCollection();

            values.Add("year", String.Empty);
            values.Add("occasion", "Hola");

            RunTest(
                "UrlGeneration1/{controller}.mvc/{action}/{category}/{year}/{occasion}/{SafeParam}",
                new DispatcherValueCollection(new { year = 1995, occasion = "Christmas", action = "Play", SafeParam = "SafeParamValue" }),
                ambientValues,
                values,
                "/UrlEncode[[UrlGeneration1]]/UrlEncode[[UrlRouting]]UrlEncode[[.mvc]]/"
                + "UrlEncode[[Play]]/UrlEncode[[Photos]]/UrlEncode[[1995]]/UrlEncode[[Hola]]");
        }
        public void Add_PropertyStorage()
        {
            // Arrange
            var dict = new DispatcherValueCollection(new { age = 30 });

            // Act
            dict.Add("key", "value");

            // Assert
            Assert.Collection(
                dict.OrderBy(kvp => kvp.Key),
                kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
                kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
            Assert.IsType <DispatcherValueCollection.ListStorage>(dict._storage);
        }
        public void Add_DuplicateKey_CaseInsensitive()
        {
            // Arrange
            var dict = new DispatcherValueCollection()
            {
                { "key", "value" },
            };

            var message = $"An element with the key 'kEy' already exists in the {nameof(DispatcherValueCollection)}";

            // Act & Assert
            ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message);

            // Assert
            Assert.Collection(
                dict.OrderBy(kvp => kvp.Key),
                kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
            Assert.IsType <DispatcherValueCollection.ListStorage>(dict._storage);
        }
Example #12
0
        public bool TryMatch(PathString path, DispatcherValueCollection values)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            var i             = 0;
            var pathTokenizer = new PathTokenizer(path);

            // Perf: We do a traversal of the request-segments + route-segments twice.
            //
            // For most segment-types, we only really need to any work on one of the two passes.
            //
            // On the first pass, we're just looking to see if there's anything that would disqualify us from matching.
            // The most common case would be a literal segment that doesn't match.
            //
            // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
            // and start capturing strings.
            foreach (var stringSegment in pathTokenizer)
            {
                if (stringSegment.Length == 0)
                {
                    return(false);
                }

                var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i];
                if (pathSegment == null && stringSegment.Length > 0)
                {
                    // If pathSegment is null, then we're out of route segments. All we can match is the empty
                    // string.
                    return(false);
                }
                else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameter parameter && parameter.IsCatchAll)
                {
                    // Nothing to validate for a catch-all - it can match any string, including the empty string.
                    //
                    // Also, a catch-all has to be the last part, so we're done.
                    break;
                }
                if (!TryMatchLiterals(i++, stringSegment, pathSegment))
                {
                    return(false);
                }
            }

            for (; i < RoutePattern.PathSegments.Count; i++)
            {
                // We've matched the request path so far, but still have remaining route segments. These need
                // to be all single-part parameter segments with default values or else they won't match.
                var pathSegment = RoutePattern.PathSegments[i];
                Debug.Assert(pathSegment != null);

                if (!pathSegment.IsSimple)
                {
                    // If the segment is a complex segment, it MUST contain literals, and we've parsed the full
                    // path so far, so it can't match.
                    return(false);
                }

                var part = pathSegment.Parts[0];
                if (part.IsLiteral || part.IsSeparator)
                {
                    // If the segment is a simple literal - which need the URL to provide a value, so we don't match.
                    return(false);
                }

                var parameter = (RoutePatternParameter)part;
                if (parameter.IsCatchAll)
                {
                    // Nothing to validate for a catch-all - it can match any string, including the empty string.
                    //
                    // Also, a catch-all has to be the last part, so we're done.
                    break;
                }

                // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
                // defaults to have a value.
                if (!_hasDefaultValue[i] && !parameter.IsOptional)
                {
                    // There's no default for this (non-optional) parameter so it can't match.
                    return(false);
                }
            }

            // At this point we've very likely got a match, so start capturing values for real.
            i = 0;
            foreach (var requestSegment in pathTokenizer)
            {
                var pathSegment = RoutePattern.PathSegments[i++];
                if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment))
                {
                    break;
                }
                if (!pathSegment.IsSimple)
                {
                    if (!MatchComplexSegment(pathSegment, requestSegment.ToString(), Defaults, values))
                    {
                        return(false);
                    }
                }
            }

            for (; i < RoutePattern.PathSegments.Count; i++)
            {
                // We've matched the request path so far, but still have remaining route segments. We already know these
                // are simple parameters that either have a default, or don't need to produce a value.
                var pathSegment = RoutePattern.PathSegments[i];
                Debug.Assert(pathSegment != null);
                Debug.Assert(pathSegment.IsSimple);

                var part = pathSegment.Parts[0];
                Debug.Assert(part.IsParameter);

                // It's ok for a catch-all to produce a null value
                if (part is RoutePatternParameter parameter && (parameter.IsCatchAll || _hasDefaultValue[i]))
                {
                    // Don't replace an existing value with a null.
                    var defaultValue = _defaultValues[i];
                    if (defaultValue != null || !values.ContainsKey(parameter.Name))
                    {
                        values[parameter.Name] = defaultValue;
                    }
                }
            }

            // Copy all remaining default values to the route data
            foreach (var kvp in Defaults)
            {
                if (!values.ContainsKey(kvp.Key))
                {
                    values.Add(kvp.Key, kvp.Value);
                }
            }

            return(true);
        }
Example #13
0
        // Step 1: Get the list of values we're going to try to use to match and generate this URI
        public (DispatcherValueCollection acceptedValues, DispatcherValueCollection combinedValues) GetValues(DispatcherValueCollection ambientValues, DispatcherValueCollection values)
        {
            var context = new TemplateBindingContext(_defaults);

            // Find out which entries in the URI are valid for the URI we want to generate.
            // If the URI had ordered parameters a="1", b="2", c="3" and the new values
            // specified that b="9", then we need to invalidate everything after it. The new
            // values should then be a="1", b="9", c=<no value>.
            //
            // We also handle the case where a parameter is optional but has no value - we shouldn't
            // accept additional parameters that appear *after* that parameter.
            for (var i = 0; i < _pattern.Parameters.Count; i++)
            {
                var parameter = _pattern.Parameters[i];

                // If it's a parameter subsegment, examine the current value to see if it matches the new value
                var parameterName = parameter.Name;

                var hasNewParameterValue = values.TryGetValue(parameterName, out var newParameterValue);

                object currentParameterValue    = null;
                var    hasCurrentParameterValue = ambientValues != null &&
                                                  ambientValues.TryGetValue(parameterName, out currentParameterValue);

                if (hasNewParameterValue && hasCurrentParameterValue)
                {
                    if (!RoutePartsEqual(currentParameterValue, newParameterValue))
                    {
                        // Stop copying current values when we find one that doesn't match
                        break;
                    }
                }

                if (!hasNewParameterValue &&
                    !hasCurrentParameterValue &&
                    _defaults?.ContainsKey(parameter.Name) != true)
                {
                    // This is an unsatisfied parameter value and there are no defaults. We might still
                    // be able to generate a URL but we should stop 'accepting' ambient values.
                    //
                    // This might be a case like:
                    //  template: a/{b?}/{c?}
                    //  ambient: { c = 17 }
                    //  values: { }
                    //
                    // We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
                    // we can't use it.
                    //
                    // In the example above we should fall into this block for 'b'.
                    break;
                }

                // If the parameter is a match, add it to the list of values we will use for URI generation
                if (hasNewParameterValue)
                {
                    if (IsRoutePartNonEmpty(newParameterValue))
                    {
                        context.Accept(parameterName, newParameterValue);
                    }
                }
                else
                {
                    if (hasCurrentParameterValue)
                    {
                        context.Accept(parameterName, currentParameterValue);
                    }
                }
            }

            // Add all remaining new values to the list of values we will use for URI generation
            foreach (var kvp in values)
            {
                if (IsRoutePartNonEmpty(kvp.Value))
                {
                    context.Accept(kvp.Key, kvp.Value);
                }
            }

            // Accept all remaining default values if they match a required parameter
            for (var i = 0; i < _pattern.Parameters.Count; i++)
            {
                var parameter = _pattern.Parameters[i];
                if (parameter.IsOptional || parameter.IsCatchAll)
                {
                    continue;
                }

                if (context.NeedsValue(parameter.Name))
                {
                    // Add the default value only if there isn't already a new value for it and
                    // only if it actually has a default value, which we determine based on whether
                    // the parameter value is required.
                    context.AcceptDefault(parameter.Name);
                }
            }

            // Validate that all required parameters have a value.
            for (var i = 0; i < _pattern.Parameters.Count; i++)
            {
                var parameter = _pattern.Parameters[i];
                if (parameter.IsOptional || parameter.IsCatchAll)
                {
                    continue;
                }

                if (!context.AcceptedValues.ContainsKey(parameter.Name))
                {
                    // We don't have a value for this parameter, so we can't generate a url.
                    return(null, null);
                }
            }

            // Any default values that don't appear as parameters are treated like filters. Any new values
            // provided must match these defaults.
            foreach (var filter in _filters)
            {
                var parameter = _pattern.GetParameter(filter.Key);
                if (parameter != null)
                {
                    continue;
                }

                if (values.TryGetValue(filter.Key, out var value))
                {
                    if (!RoutePartsEqual(value, filter.Value))
                    {
                        // If there is a non-parameterized value in the route and there is a
                        // new value for it and it doesn't match, this route won't match.
                        return(null, null);
                    }
                }
            }

            // Add any ambient values that don't match parameters - they need to be visible to constraints
            // but they will ignored by link generation.
            var combinedValues = new DispatcherValueCollection(context.AcceptedValues);

            if (ambientValues != null)
            {
                foreach (var kvp in ambientValues)
                {
                    if (IsRoutePartNonEmpty(kvp.Value))
                    {
                        var parameter = _pattern.GetParameter(kvp.Key);
                        if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
                        {
                            combinedValues.Add(kvp.Key, kvp.Value);
                        }
                    }
                }
            }

            return(context.AcceptedValues, combinedValues);
        }