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]); }
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); }
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); }
// 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); }