示例#1
0
    public void SelectMultipleEntries_BothMatch_CriteriaSubset()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });

        entries.Add(entry1);

        var entry2 = CreateMatch(new { controller = "Store" });

        entry2.Entry.Order = 1;
        entries.Add(entry2);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(
            values: new { controller = "Store" },
            ambientValues: new { controller = "Store", action = "Buy" });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();

        // Assert
        Assert.Equal(entries, matches);
    }
示例#2
0
    public void GetMatches_PagesWithArea_AllValuesAmbient()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });

        entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
        entries.Add(entry1);

        var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });

        entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
        entries.Add(entry2);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(new { }, new { page = "/Store/Buy", area = "Admin", });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();

        // Assert
        Assert.Collection(
            matches,
            m => { Assert.Same(entry2, m); },
            m => { Assert.Same(entry1, m); });
    }
示例#3
0
    public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });

        entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
        entries.Add(entry1);

        var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });

        entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
        entries.Add(entry2);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(new { controller = "Store", action = "Buy" });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();

        // Assert
        Assert.Equal(entries, matches);
    }
        public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
        {
            // Arrange
            var entries = new List <TreeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });

            entry1.GenerationPrecedence = 1;
            entries.Add(entry1);

            var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });

            entry2.GenerationPrecedence = 0;
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context).Select(m => m.Entry).ToList();

            // Assert
            Assert.Equal(entries, matches);
        }
示例#5
0
    public void ToDebuggerDisplayString_GivesAFlattenedTree()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V1" }, "Store/Buy/V1"));
        entries.Add(CreateMatch(new { action = "Buy", controller = "Store", area = "Admin" }, "Admin/Store/Buy"));
        entries.Add(CreateMatch(new { action = "Buy", controller = "Products" }, "Products/Buy"));
        entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V2" }, "Store/Buy/V2"));
        entries.Add(CreateMatch(new { action = "Cart", controller = "Store" }, "Store/Cart"));
        entries.Add(CreateMatch(new { action = "Index", controller = "Home" }, "Home/Index/{id?}"));
        var tree     = new LinkGenerationDecisionTree(entries);
        var newLine  = Environment.NewLine;
        var expected =
            " => action: Buy => controller: Store => version: V1 (Matches: Store/Buy/V1)" + newLine +
            " => action: Buy => controller: Store => version: V2 (Matches: Store/Buy/V2)" + newLine +
            " => action: Buy => controller: Store => area: Admin (Matches: Admin/Store/Buy)" + newLine +
            " => action: Buy => controller: Products (Matches: Products/Buy)" + newLine +
            " => action: Cart => controller: Store (Matches: Store/Cart)" + newLine +
            " => action: Index => controller: Home (Matches: Home/Index/{id?})" + newLine;

        // Act
        var flattenedTree = tree.DebuggerDisplayString;

        // Assert
        Assert.Equal(expected, flattenedTree);
    }
示例#6
0
    public void GetMatches_LinkToPageFromController_WithActionValueAmbiguous()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });

        entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
        entries.Add(entry1);

        var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });

        entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
        entries.Add(entry2);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(new { page = "/Store/Buy", action = "Index", }, new { controller = "Home", action = "Index", page = "16", });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();

        // Assert
        Assert.Empty(matches);
    }
示例#7
0
    public void SelectMultipleEntries_OneDoesntMatch()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });

        entries.Add(entry1);

        var entry2 = CreateMatch(new { controller = "Store", action = "Cart" });

        entries.Add(entry2);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(
            values: new { controller = "Store" },
            ambientValues: new { controller = "Store", action = "Buy" });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues);

        // Assert
        Assert.Same(entry1, Assert.Single(matches).Match);
    }
示例#8
0
        /// <summary>
        /// Creates a new <see cref="InnerAttributeRoute"/>.
        /// </summary>
        /// <param name="next">The next router. Invoked when a route entry matches.</param>
        /// <param name="entries">The set of route entries.</param>
        public InnerAttributeRoute(
            [NotNull] IRouter next,
            [NotNull] IEnumerable <AttributeRouteMatchingEntry> matchingEntries,
            [NotNull] IEnumerable <AttributeRouteLinkGenerationEntry> linkGenerationEntries,
            [NotNull] ILogger logger,
            [NotNull] ILogger constraintLogger,
            int version)
        {
            _next             = next;
            _logger           = logger;
            _constraintLogger = constraintLogger;

            Version = version;

            // Order all the entries by order, then precedence, and then finally by template in order to provide
            // a stable routing and link generation order for templates with same order and precedence.
            // We use ordinal comparison for the templates because we only care about them being exactly equal and
            // we don't want to make any equivalence between templates based on the culture of the machine.

            _matchingRoutes = matchingEntries
                              .OrderBy(o => o.Order)
                              .ThenBy(e => e.Precedence)
                              .ThenBy(e => e.Route.RouteTemplate, StringComparer.Ordinal)
                              .Select(e => e.Route)
                              .ToArray();

            var namedEntries = new Dictionary <string, AttributeRouteLinkGenerationEntry>(
                StringComparer.OrdinalIgnoreCase);

            foreach (var entry in linkGenerationEntries)
            {
                // Skip unnamed entries
                if (entry.Name == null)
                {
                    continue;
                }

                // We only need to keep one AttributeRouteLinkGenerationEntry per route template
                // so in case two entries have the same name and the same template we only keep
                // the first entry.
                AttributeRouteLinkGenerationEntry namedEntry = null;
                if (namedEntries.TryGetValue(entry.Name, out namedEntry) &&
                    !namedEntry.TemplateText.Equals(entry.TemplateText, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException(
                              Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.Name),
                              "linkGenerationEntries");
                }
                else if (namedEntry == null)
                {
                    namedEntries.Add(entry.Name, entry);
                }
            }

            _namedEntries = namedEntries;

            // The decision tree will take care of ordering for these entries.
            _linkGenerationTree = new LinkGenerationDecisionTree(linkGenerationEntries.ToArray());
        }
 private void BuildOutboundMatches()
 {
     // Refresh the matches in the case where a datasource's endpoints changes. The following is OK to do
     // as refresh of new endpoints happens within a lock and also these fields are not publicly accessible.
     var(allMatches, namedMatchResults) = GetOutboundMatches();
     _namedMatchResults            = namedMatchResults;
     _allMatchesLinkGenerationTree = new LinkGenerationDecisionTree(allMatches);
 }
 public StateEntry(
     List <OutboundMatch> allMatches,
     LinkGenerationDecisionTree allMatchesLinkGenerationTree,
     Dictionary <string, List <OutboundMatchResult> > namedMatches)
 {
     AllMatches = allMatches;
     AllMatchesLinkGenerationTree = allMatchesLinkGenerationTree;
     NamedMatches = namedMatches;
 }
        public void SelectSingleEntry_NoCriteria()
        {
            // Arrange
            var entries = new List<TreeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { });
            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Same(entry, Assert.Single(matches).Entry);
        }
        public void SelectSingleEntry_MultipleCriteria()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Same(entry, Assert.Single(matches).Entry);
        }
示例#13
0
    public void GetMatches_AllowsNullAmbientValues()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry = CreateMatch(new { });

        entries.Add(entry);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(new { });

        // Act
        var matches = tree.GetMatches(context.Values, ambientValues: null);

        // Assert
        Assert.Same(entry, Assert.Single(matches).Match);
    }
        public void SelectSingleEntry_MultipleCriteria()
        {
            // Arrange
            var entries = new List <TreeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });

            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Same(entry, Assert.Single(matches).Entry);
        }
        public void SelectSingleEntry_NoCriteria()
        {
            // Arrange
            var entries = new List <OutboundMatch>();

            var entry = CreateMatch(new { });

            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Same(entry, Assert.Single(matches).Match);
        }
        public void SelectSingleEntry_MultipleCriteria_NoMatch()
        {
            // Arrange
            var entries = new List <OutboundMatch>();

            var entry = CreateMatch(new { controller = "Store", action = "Buy" });

            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "AddToCart" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Empty(matches);
        }
示例#17
0
    public void SelectSingleEntry_MultipleCriteria()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry = CreateMatch(new { controller = "Store", action = "Buy" });

        entries.Add(entry);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(new { controller = "Store", action = "Buy" });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues);

        // Assert
        Assert.Same(entry, Assert.Single(matches).Match);
    }
        public void SelectSingleEntry_MultipleCriteria_AmbientValues()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(values: null, ambientValues: new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            var match = Assert.Single(matches);
            Assert.Same(entry, match.Entry);
            Assert.False(match.IsFallbackMatch);
        }
        public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
        {
            // Arrange
            var entries = new List <TreeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });

            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(
                values: new { controller = "Store" },
                ambientValues: new { controller = "Store", action = "Cart" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Empty(matches);
        }
        public void SelectSingleEntry_MultipleCriteria_AmbientValues()
        {
            // Arrange
            var entries = new List <TreeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });

            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(values: null, ambientValues: new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            var match = Assert.Single(matches);

            Assert.Same(entry, match.Entry);
            Assert.False(match.IsFallbackMatch);
        }
示例#21
0
    public void SelectSingleEntry_MultipleCriteria_Replaced()
    {
        // Arrange
        var entries = new List <OutboundMatch>();

        var entry = CreateMatch(new { controller = "Store", action = "Buy" });

        entries.Add(entry);

        var tree = new LinkGenerationDecisionTree(entries);

        var context = CreateContext(
            values: new { action = "Buy" },
            ambientValues: new { controller = "Store", action = "Cart" });

        // Act
        var matches = tree.GetMatches(context.Values, context.AmbientValues);

        // Assert
        var match = Assert.Single(matches);

        Assert.Same(entry, match.Match);
        Assert.False(match.IsFallbackMatch);
    }
        public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
        {
            // Arrange
            var entries = new List <TreeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });

            entries.Add(entry1);

            var entry2 = CreateEntry(new { slug = "1234" });

            entry2.Order = 1;
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy", slug = "1234" });

            // Act
            var matches = tree.GetMatches(context).Select(m => m.Entry).ToList();

            // Assert
            Assert.Equal(entries, matches);
        }
示例#23
0
    internal TreeRouter(
        UrlMatchingTree[] trees,
        IEnumerable <OutboundRouteEntry> linkGenerationEntries,
        UrlEncoder urlEncoder,
        ObjectPool <UriBuildingContext> objectPool,
        ILogger routeLogger,
        ILogger constraintLogger,
        int version)
    {
        if (trees == null)
        {
            throw new ArgumentNullException(nameof(trees));
        }

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

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

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

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

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

        _trees            = trees;
        _logger           = routeLogger;
        _constraintLogger = constraintLogger;

        _namedEntries = new Dictionary <string, OutboundMatch>(StringComparer.OrdinalIgnoreCase);

        var outboundMatches = new List <OutboundMatch>();

        foreach (var entry in linkGenerationEntries)
        {
            var binder        = new TemplateBinder(urlEncoder, objectPool, entry.RouteTemplate, entry.Defaults);
            var outboundMatch = new OutboundMatch()
            {
                Entry = entry, TemplateBinder = binder
            };
            outboundMatches.Add(outboundMatch);

            // Skip unnamed entries
            if (entry.RouteName == null)
            {
                continue;
            }

            // We only need to keep one OutboundMatch per route template
            // so in case two entries have the same name and the same template we only keep
            // the first entry.
            if (_namedEntries.TryGetValue(entry.RouteName, out var namedMatch) &&
                !string.Equals(
                    namedMatch.Entry.RouteTemplate.TemplateText,
                    entry.RouteTemplate.TemplateText,
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(
                          Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.RouteName),
                          nameof(linkGenerationEntries));
            }
            else if (namedMatch == null)
            {
                _namedEntries.Add(entry.RouteName, outboundMatch);
            }
        }

        // The decision tree will take care of ordering for these entries.
        _linkGenerationTree = new LinkGenerationDecisionTree(outboundMatches.ToArray());

        Version = version;
    }
        public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
            entry1.TemplateText = "a";
            entries.Add(entry1);

            var entry2 = CreateEntry(new { controller = "Store", action = "Buy" });
            entry2.TemplateText = "b";
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context).Select(m => m.Entry).ToList();

            // Assert
            Assert.Equal(entries, matches);
        }
        public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry1);

            var entry2 = CreateEntry(new { slug = "1234" });
            entry2.Order = 1;
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(new { controller = "Store", action = "Buy", slug = "1234" });

            // Act
            var matches = tree.GetMatches(context).Select(m => m.Entry).ToList();

            // Assert
            Assert.Equal(entries, matches);
        }
        public void SelectMultipleEntries_OneDoesntMatch()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry1);

            var entry2 = CreateEntry(new { controller = "Store", action = "Cart" });
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(
                values: new { controller = "Store" },
                ambientValues: new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Same(entry1, Assert.Single(matches).Entry);
        }
        public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
        {
            // Arrange
            var entries = new List<AttributeRouteLinkGenerationEntry>();

            var entry = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(
                values: new { controller = "Store" },
                ambientValues: new { controller = "Store", action = "Cart" });

            // Act
            var matches = tree.GetMatches(context);

            // Assert
            Assert.Empty(matches);
        }
        public void SelectMultipleEntries_BothMatch_CriteriaSubset()
        {
            // Arrange
            var entries = new List<TreeRouteLinkGenerationEntry>();

            var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
            entries.Add(entry1);

            var entry2 = CreateEntry(new { controller = "Store" });
            entry2.Order = 1;
            entries.Add(entry2);

            var tree = new LinkGenerationDecisionTree(entries);

            var context = CreateContext(
                values: new { controller = "Store" },
                ambientValues: new { controller = "Store", action = "Buy" });

            // Act
            var matches = tree.GetMatches(context).Select(m => m.Entry).ToList();

            // Assert
            Assert.Equal(entries, matches);
        }
示例#29
0
 private void BuildOutboundMatches()
 {
     var(allOutboundMatches, namedOutboundMatches) = GetOutboundMatches();
     _namedMatches = GetNamedMatches(namedOutboundMatches);
     _allMatchesLinkGenerationTree = new LinkGenerationDecisionTree(allOutboundMatches.ToArray());
 }
        /// <summary>
        /// Creates a new <see cref="InnerAttributeRoute"/>.
        /// </summary>
        /// <param name="next">The next router. Invoked when a route entry matches.</param>
        /// <param name="entries">The set of route entries.</param>
        public InnerAttributeRoute(
            [NotNull] IRouter next,
            [NotNull] IEnumerable<AttributeRouteMatchingEntry> matchingEntries,
            [NotNull] IEnumerable<AttributeRouteLinkGenerationEntry> linkGenerationEntries,
            [NotNull] ILogger logger,
            [NotNull] ILogger constraintLogger,
            int version)
        {
            _next = next;
            _logger = logger;
            _constraintLogger = constraintLogger;

            Version = version;

            // Order all the entries by order, then precedence, and then finally by template in order to provide
            // a stable routing and link generation order for templates with same order and precedence.
            // We use ordinal comparison for the templates because we only care about them being exactly equal and
            // we don't want to make any equivalence between templates based on the culture of the machine.

            _matchingEntries = matchingEntries
                .OrderBy(o => o.Order)
                .ThenBy(e => e.Precedence)
                .ThenBy(e => e.RouteTemplate, StringComparer.Ordinal)
                .ToArray();

            var namedEntries = new Dictionary<string, AttributeRouteLinkGenerationEntry>(
                StringComparer.OrdinalIgnoreCase);

            foreach (var entry in linkGenerationEntries)
            {
                // Skip unnamed entries
                if (entry.Name == null)
                {
                    continue;
                }

                // We only need to keep one AttributeRouteLinkGenerationEntry per route template
                // so in case two entries have the same name and the same template we only keep
                // the first entry.
                AttributeRouteLinkGenerationEntry namedEntry = null;
                if (namedEntries.TryGetValue(entry.Name, out namedEntry) &&
                    !namedEntry.TemplateText.Equals(entry.TemplateText, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException(
                        Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.Name),
                        nameof(linkGenerationEntries));
                }
                else if (namedEntry == null)
                {
                    namedEntries.Add(entry.Name, entry);
                }
            }

            _namedEntries = namedEntries;

            // The decision tree will take care of ordering for these entries.
            _linkGenerationTree = new LinkGenerationDecisionTree(linkGenerationEntries.ToArray());
        }