Exemplo n.º 1
0
        public Enumerator(PathTokenizer tokenizer)
        {
            _path = tokenizer._path;

            _index  = -1;
            _length = -1;
        }
Exemplo n.º 2
0
        /// <inheritdoc />
        public async Task RouteAsync(RouteContext context)
        {
            foreach (var tree in _trees)
            {
                var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
                var root      = tree.Root;

                var treeEnumerator = new TreeEnumerator(root, tokenizer);

                // Create a snapshot before processing the route. We'll restore this snapshot before running each
                // to restore the state. This is likely an "empty" snapshot, which doesn't allocate.
                var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null);

                while (treeEnumerator.MoveNext())
                {
                    var node = treeEnumerator.Current;
                    foreach (var item in node.Matches)
                    {
                        var entry   = item.Entry;
                        var matcher = item.TemplateMatcher;
                        if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values))
                        {
                            continue;
                        }

                        try
                        {
                            if (!RouteConstraintMatcher.Match(
                                    entry.Constraints,
                                    context.RouteData.Values,
                                    context.HttpContext,
                                    this,
                                    RouteDirection.IncomingRequest,
                                    _constraintLogger))
                            {
                                continue;
                            }

                            _logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText);
                            context.RouteData.Routers.Add(entry.Handler);

                            await entry.Handler.RouteAsync(context);

                            if (context.Handler != null)
                            {
                                return;
                            }
                        }
                        finally
                        {
                            if (context.Handler == null)
                            {
                                // Restore the original values to prevent polluting the route data.
                                snapshot.Restore();
                            }
                        }
                    }
                }
            }
        }
            public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
            {
                _stack     = new Stack <UrlMatchingNode>();
                _tokenizer = tokenizer;
                Current    = null;

                _stack.Push(root);
            }
Exemplo n.º 4
0
    public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments)
    {
        // Arrange
        var tokenizer = new PathTokenizer(new PathString(path));

        // Act & Assert
        Assert.Equal <StringSegment>(expectedSegments, tokenizer);
    }
Exemplo n.º 5
0
        /// <inheritdoc />
        public async Task RouteAsync(RouteContext context)
        {
            foreach (var tree in _trees)
            {
                var tokenizer  = new PathTokenizer(context.HttpContext.Request.Path);
                var enumerator = tokenizer.GetEnumerator();
                var root       = tree.Root;

                var treeEnumerator = new TreeEnumerator(root, tokenizer);

                while (treeEnumerator.MoveNext())
                {
                    var node = treeEnumerator.Current;
                    foreach (var item in node.Matches)
                    {
                        var values = item.TemplateMatcher.Match(context.HttpContext.Request.Path);
                        if (values == null)
                        {
                            continue;
                        }

                        var match    = new TemplateMatch(item, values);
                        var snapshot = context.RouteData.PushState(match.Entry.Target, match.Values, dataTokens: null);

                        try
                        {
                            if (!RouteConstraintMatcher.Match(
                                    match.Entry.Constraints,
                                    context.RouteData.Values,
                                    context.HttpContext,
                                    this,
                                    RouteDirection.IncomingRequest,
                                    _constraintLogger))
                            {
                                continue;
                            }

                            _logger.MatchedRoute(match.Entry.RouteName, match.Entry.RouteTemplate.TemplateText);

                            await match.Entry.Target.RouteAsync(context);

                            if (context.Handler != null)
                            {
                                return;
                            }
                        }
                        finally
                        {
                            if (context.Handler == null)
                            {
                                // Restore the original values to prevent polluting the route data.
                                snapshot.Restore();
                            }
                        }
                    }
                }
            }
        }
Exemplo n.º 6
0
    public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments)
    {
        // Arrange
        var tokenizer = new PathTokenizer(new PathString(path));

        // Act & Assert
        for (var i = 0; i < expectedSegments.Length; i++)
        {
            Assert.Equal(expectedSegments[i], tokenizer[i]);
        }
    }
Exemplo n.º 7
0
    public void PathTokenizer_Count(string path, StringSegment[] expectedSegments)
    {
        // Arrange
        var tokenizer = new PathTokenizer(new PathString(path));

        // Act
        var count = tokenizer.Count;

        // Assert
        Assert.Equal(expectedSegments.Length, count);
    }
Exemplo n.º 8
0
        public void PathTokenizer_Count(string path, StringSegment[] expectedSegments)
        {
            // Arrange
            var tokenizer = new PathTokenizer(new PathString(path));

            // Act
            var count = tokenizer.Count;

            // Assert
            Assert.Equal(expectedSegments.Length, count);
        }
Exemplo n.º 9
0
        public override async Task MatchAsync(MatcherContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            EnsureTreeMatcherServicesInitialized(context);

            var cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer);

            var values = new DispatcherValueCollection();

            context.Values = values;

            for (var i = 0; i < cache.Trees.Length; i++)
            {
                var tree      = cache.Trees[i];
                var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);

                var treenumerator = new TreeEnumerator(tree.Root, tokenizer);

                while (treenumerator.MoveNext())
                {
                    var node = treenumerator.Current;
                    foreach (var item in node.Matches)
                    {
                        var entry   = item.Entry;
                        var matcher = item.RoutePatternMatcher;

                        values.Clear();
                        if (!matcher.TryMatch(context.HttpContext.Request.Path, values))
                        {
                            continue;
                        }

                        Logger.MatchedRoute(entry.RoutePattern.RawText);

                        if (!MatchConstraints(context.HttpContext, values, entry.Constraints))
                        {
                            continue;
                        }

                        await SelectEndpointAsync(context, (entry.Endpoints));

                        if (context.ShortCircuit != null)
                        {
                            Logger.RequestShortCircuited(context);
                            return;
                        }

                        if (context.Endpoint != null)
                        {
                            if (context.Endpoint is IRoutePatternEndpoint templateEndpoint)
                            {
                                foreach (var kvp in templateEndpoint.Values)
                                {
                                    if (!context.Values.ContainsKey(kvp.Key))
                                    {
                                        context.Values[kvp.Key] = kvp.Value;
                                    }
                                }
                            }

                            return;
                        }
                    }
                }
            }
        }
Exemplo n.º 10
0
 public ExpressionToken(PathTokenizer value, CharacterLocation location)
 {
     TokenType = ExpressionTokenType.Path;
     Value     = value;
     Location  = location;
 }
Exemplo n.º 11
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);
        }
Exemplo n.º 12
0
        public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments)
        {
            // Arrange
            var tokenizer = new PathTokenizer(new PathString(path));

            // Act & Assert
            for (var i = 0; i < expectedSegments.Length; i++)
            {
                Assert.Equal(expectedSegments[i], tokenizer[i]);
            }
        }
Exemplo n.º 13
0
        public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments)
        {
            // Arrange
            var tokenizer = new PathTokenizer(new PathString(path));

            // Act & Assert
            Assert.Equal<StringSegment>(expectedSegments, tokenizer);
        }
Exemplo n.º 14
0
        public RouteValueDictionary Match(PathString path)
        {
            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 requestSegment in pathTokenizer)
            {
                var routeSegment = Template.GetSegment(i++);
                if (routeSegment == null && requestSegment.Length > 0)
                {
                    // If pathSegment is null, then we're out of route segments. All we can match is the empty
                    // string.
                    return null;
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsLiteral)
                {
                    // This is a literal segment, so we need to match the text, or the route isn't a match.
                    var part = routeSegment.Parts[0];
                    if (!requestSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase))
                    {
                        return null;
                    }
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].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;
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
                {
                    // For a parameter, validate that it's a has some length, or we have a default, or it's optional.
                    var part = routeSegment.Parts[0];
                    if (requestSegment.Length == 0 &&
                        !_hasDefaultValue[i] &&
                        !part.IsOptional)
                    {
                        // There's no value for this parameter, the route can't match.
                        return null;
                    }
                }
                else
                {
                    Debug.Assert(!routeSegment.IsSimple);

                    // Don't attempt to validate a complex segment at this point other than being non-emtpy,
                    // do it in the second pass.
                }
            }

            for (; i < Template.Segments.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 routeSegment = Template.GetSegment(i);
                Debug.Assert(routeSegment != null);

                if (!routeSegment.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 null;
                }

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

                if (part.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.
                Debug.Assert(routeSegment.IsSimple && part.IsParameter);
                if (!_hasDefaultValue[i] && !part.IsOptional)
                {
                    // There's no default for this (non-optional) parameter so it can't match.
                    return null;
                }
            }

            // At this point we've very likely got a match, so start capturing values for real.
            var values = new RouteValueDictionary();

            i = 0;
            foreach (var requestSegment in pathTokenizer)
            {
                var routeSegment = Template.GetSegment(i++);

                if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll)
                {
                    // A catch-all captures til the end of the string.
                    var part = routeSegment.Parts[0];
                    var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
                    if (captured.Length > 0)
                    {
                        values.Add(part.Name, captured);
                    }
                    else
                    {
                        // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
                        values.Add(part.Name, _defaultValues[i]);
                    }

                    // A catch-all has to be the last part, so we're done.
                    break;
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
                {
                    // A simple parameter captures the whole segment, or a default value if nothing was
                    // provided.
                    var part = routeSegment.Parts[0];
                    if (requestSegment.Length > 0)
                    {
                        values.Add(part.Name, requestSegment.ToString());
                    }
                    else
                    {
                        if (_hasDefaultValue[i])
                        {
                            values.Add(part.Name, _defaultValues[i]);
                        }
                    }
                }
                else if (!routeSegment.IsSimple)
                {
                    if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values))
                    {
                        return null;
                    }
                }
            }

            for (; i < Template.Segments.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 routeSegment = Template.GetSegment(i);
                Debug.Assert(routeSegment != null);
                Debug.Assert(routeSegment.IsSimple);

                var part = routeSegment.Parts[0];
                Debug.Assert(part.IsParameter);
   
                // It's ok for a catch-all to produce a null value
                if (_hasDefaultValue[i] || part.IsCatchAll)
                {
                    values.Add(part.Name, _defaultValues[i]);
                }
            }

            // 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 values;
        }
Exemplo n.º 15
0
        public bool TryMatch(PathString path, RouteValueDictionary 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 pathSegment in pathTokenizer)
            {
                if (pathSegment.Length == 0)
                {
                    return(false);
                }

                var routeSegment = Template.GetSegment(i++);
                if (routeSegment == null && pathSegment.Length > 0)
                {
                    // If routeSegment is null, then we're out of route segments. All we can match is the empty
                    // string.
                    return(false);
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsLiteral)
                {
                    // This is a literal segment, so we need to match the text, or the route isn't a match.
                    var part = routeSegment.Parts[0];
                    if (!pathSegment.Equals(part.Text, StringComparison.OrdinalIgnoreCase))
                    {
                        return(false);
                    }
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].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;
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
                {
                    // For a parameter, validate that it's a has some length, or we have a default, or it's optional.
                    var part = routeSegment.Parts[0];
                    if (pathSegment.Length == 0 &&
                        !_hasDefaultValue[i] &&
                        !part.IsOptional)
                    {
                        // There's no value for this parameter, the route can't match.
                        return(false);
                    }
                }
                else
                {
                    Debug.Assert(!routeSegment.IsSimple);

                    // Don't attempt to validate a complex segment at this point other than being non-emtpy,
                    // do it in the second pass.
                }
            }

            for (; i < Template.Segments.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 routeSegment = Template.GetSegment(i);
                Debug.Assert(routeSegment != null);

                if (!routeSegment.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 = routeSegment.Parts[0];
                if (part.IsLiteral)
                {
                    // If the segment is a simple literal - which need the URL to provide a value, so we don't match.
                    return(false);
                }

                if (part.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.
                Debug.Assert(routeSegment.IsSimple && part.IsParameter);
                if (!_hasDefaultValue[i] && !part.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 routeSegment = Template.GetSegment(i++);

                if (routeSegment.IsSimple && routeSegment.Parts[0].IsCatchAll)
                {
                    // A catch-all captures til the end of the string.
                    var part     = routeSegment.Parts[0];
                    var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
                    if (captured.Length > 0)
                    {
                        values[part.Name] = captured;
                    }
                    else
                    {
                        // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
                        values[part.Name] = _defaultValues[i];
                    }

                    // A catch-all has to be the last part, so we're done.
                    break;
                }
                else if (routeSegment.IsSimple && routeSegment.Parts[0].IsParameter)
                {
                    // A simple parameter captures the whole segment, or a default value if nothing was
                    // provided.
                    var part = routeSegment.Parts[0];
                    if (requestSegment.Length > 0)
                    {
                        values[part.Name] = requestSegment.ToString();
                    }
                    else
                    {
                        if (_hasDefaultValue[i])
                        {
                            values[part.Name] = _defaultValues[i];
                        }
                    }
                }
                else if (!routeSegment.IsSimple)
                {
                    if (!MatchComplexSegment(routeSegment, requestSegment.ToString(), Defaults, values))
                    {
                        return(false);
                    }
                }
            }

            for (; i < Template.Segments.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 routeSegment = Template.GetSegment(i);
                Debug.Assert(routeSegment != null);
                Debug.Assert(routeSegment.IsSimple);

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

                // It's ok for a catch-all to produce a null value
                if (_hasDefaultValue[i] || part.IsCatchAll)
                {
                    // Don't replace an existing value with a null.
                    var defaultValue = _defaultValues[i];
                    if (defaultValue != null || !values.ContainsKey(part.Name))
                    {
                        values[part.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);
        }
Exemplo n.º 16
0
        public override async Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            if (feature == null)
            {
                throw new ArgumentNullException(nameof(feature));
            }

            var values = new RouteValueDictionary();

            feature.Values = values;

            var cache = _cache.Value;

            for (var i = 0; i < cache.Length; i++)
            {
                var tree      = cache[i];
                var tokenizer = new PathTokenizer(httpContext.Request.Path);

                var treenumerator = new TreeEnumerator(tree.Root, tokenizer);

                while (treenumerator.MoveNext())
                {
                    var node = treenumerator.Current;
                    foreach (var item in node.Matches)
                    {
                        var entry   = item.Entry;
                        var matcher = item.TemplateMatcher;

                        values.Clear();
                        if (!matcher.TryMatch(httpContext.Request.Path, values))
                        {
                            continue;
                        }

                        Log.MatchedTemplate(_logger, httpContext, entry.RouteTemplate);

                        if (!MatchConstraints(httpContext, values, entry.Constraints))
                        {
                            continue;
                        }

                        await SelectEndpointAsync(httpContext, feature, (MatcherEndpoint[])entry.Tag);

                        if (feature.Endpoint != null)
                        {
                            if (feature.Endpoint is MatcherEndpoint endpoint)
                            {
                                foreach (var kvp in endpoint.Values)
                                {
                                    if (!feature.Values.ContainsKey(kvp.Key))
                                    {
                                        feature.Values[kvp.Key] = kvp.Value;
                                    }
                                }
                            }

                            return;
                        }
                    }
                }
            }
        }