Поделиться через


Параметры навигации для SharePoint

В этой статье описаны варианты навигации сайтов с включенной публикацией SharePoint в SharePoint. Выбор и конфигурация навигации значительно влияют на производительность и масштабируемость сайтов в SharePoint. Шаблон сайта публикации SharePoint следует использовать только при необходимости для централизованного портала, а функция публикации должна быть включена только на определенных сайтах и только при необходимости, так как она может повлиять на производительность при неправильном использовании.

Примечание.

Если вы используете современные параметры навигации SharePoint, такие как мегаменю, каскадная навигация или навигация в центре, эта статья не применяется к вашему сайту. Современные архитектуры сайтов SharePoint используют более плоскую иерархию сайтов и звездообразную модель. Это позволяет реализовать множество сценариев, для которых не требуется использовать функцию публикации SharePoint.

Обзор параметров навигации

Конфигурация поставщика навигации может значительно повлиять на производительность всего сайта, и необходимо тщательно рассмотреть вопрос о выборе поставщика навигации и конфигурации, которая эффективно масштабируется в соответствии с требованиями сайта SharePoint. Существует два готовых поставщика навигации, а также пользовательские реализации навигации.

Первый вариант, Структурная навигация, является рекомендуемой навигацией в SharePoint для классических сайтов SharePoint, если вы включаете структурное кэширование навигации для сайта. Этот поставщик навигации отображает элементы навигации под текущим сайтом и при необходимости текущий сайт и его одноуровневые элементы. Он предоставляет дополнительные возможности, такие как обрезка безопасности и перечисление структуры сайта. Если кэширование отключено, это негативно повлияет на производительность и масштабируемость и может быть подвержено регулированию.

Второй вариант, управляемая (метаданные) навигация, представляет элементы навигации с помощью набора терминов управляемых метаданных. Мы рекомендуем отключить обрезку безопасности, если это не требуется. Обрезка безопасности включена как параметр безопасности по умолчанию для этого поставщика навигации; однако многие сайты не требуют дополнительных затрат на обрезку безопасности, так как элементы навигации часто согласованы для всех пользователей сайта. В рекомендуемой конфигурации для отключения обрезки безопасности этот поставщик навигации не требует перечисления структуры сайта и является высокомасштабируемым с приемлемым влиянием на производительность.

Помимо встроенных поставщиков навигации, многие клиенты успешно реализовали альтернативные пользовательские реализации навигации. В этой статье см. Поиск клиентские скрипты.

Плюсы и недостатки параметров навигации SharePoint

В следующей таблице перечислены плюсы и недостатки каждого параметра.

Структурная навигация Управляемая навигация навигация на основе Поиск Поставщик пользовательской навигации
Плюсы:

Простота обслуживания
Безопасность обрезана
Автоматическое обновление в течение 24 часов при изменении содержимого
Плюсы:

Простота обслуживания
Плюсы:

Безопасность обрезана
Автоматически обновляется при добавлении сайтов
Быстрая загрузка и локально кэшированные структуры навигации
Плюсы:

Более широкий выбор доступных вариантов
Быстрая загрузка при правильном использовании кэширования
Многие параметры хорошо работают с адаптивным дизайном страницы
Минусы:

Влияет на производительность, если кэширование отключено
Подлежит регулированию
Минусы:

Не обновляется автоматически в соответствии со структурой сайта
Влияет на производительность при включенной обрезке безопасности или при сложной структуре навигации
Минусы:

Нет возможности легко заказать сайты
Требуется настройка страницы master (требуются технические навыки)
Минусы:

Требуется пользовательская разработка
Требуется хранить внешний источник данных или кэш, например Azure

Наиболее подходящий вариант для вашего сайта зависит от требований к сайту и от ваших технических возможностей. Если вам нужен простой в настройке поставщик навигации, который автоматически обновляется при изменении содержимого, то хорошо подходит структурная навигация с включенным кэшированием .

Примечание.

Применение того же принципа, что и для современных сайтов SharePoint, благодаря упрощению общей структуры сайта к плоской, неиерархичной структуре повышает производительность и упрощает переход на современные сайты SharePoint. Это означает, что вместо того, чтобы иметь одно семейство веб-сайтов с сотнями сайтов (вложенных веб-сайтов), лучший подход заключается в том, чтобы иметь много семейств веб-сайтов с очень небольшим количеством дочерних сайтов (вложенных веб-сайтов).

Анализ производительности навигации в SharePoint

Средство "Диагностика страницы" для SharePoint — это расширение браузера для браузеров Microsoft Edge и Chrome, которое анализирует страницы современного портала SharePoint и классических сайтов публикации. Это средство работает только для SharePoint и не может использоваться на системной странице SharePoint.

Средство создает отчет для каждой анализируемой страницы, показывающий, как страница работает с предварительно определенным набором правил, и отображает подробные сведения, когда результаты теста выходят за рамки базового значения. Администраторы и дизайнеры SharePoint могут использовать средство для устранения проблем с производительностью, чтобы обеспечить оптимизацию новых страниц перед публикацией.

В частности, SPRequestDuration — это время, необходимое SharePoint для обработки страницы. Тяжелая навигация (например, включение страниц в навигацию), сложные иерархии сайтов и другие параметры конфигурации и топологии могут значительно способствовать увеличению длительности.

Использование структурной навигации в SharePoint

Это стандартная навигация, используемая по умолчанию, и это самое простое решение. Он не требует настройки, и нетехнический пользователь также может легко добавлять элементы, скрывать элементы и управлять навигацией на странице параметров. Рекомендуется включить кэширование, в противном случае существует дорогостоящий компромисс производительности.

Реализация кэширования структурной навигации

В разделе Внешнийвид и интерфейс>навигации>параметров сайтаможно проверить, выбрана ли структурная навигация для глобальной или текущей навигации. Выбор параметра Показать страницы негативно скажется на производительности.

Структурная навигация с выбранным параметром Показать дочерние сайты.

Кэширование можно включить или отключить на уровне семейства веб-сайтов и на уровне сайта, и по умолчанию включено для обоих. Чтобы включить на уровне семейства веб-сайтов, в разделе Параметры> сайтаАдминистрирование> семействавеб-сайтов Навигация семейства веб-сайтов проверка поле Включить кэширование.

Включите кэширование на уровне семейства веб-сайтов.

Чтобы включить на уровне сайта, в разделеНавигацияпараметров> сайта проверка поле Включить кэширование.

Включите кэширование на уровне сайта.

Использование управляемой навигации и метаданных в SharePoint

Управляемая навигация — это еще один встроенный вариант, который можно использовать для воссоздания большей части той же функциональности, что и структурная навигация. Управляемые метаданные можно настроить для включения или отключения обрезки безопасности. При настройке с отключенной обрезкой безопасности управляемая навигация достаточно эффективна, так как загружает все ссылки навигации с постоянным числом вызовов сервера. Включение обрезки безопасности, однако, сводит на нет некоторые преимущества производительности управляемой навигации.

Если необходимо включить обрезку безопасности, рекомендуется:

  • Обновление всех понятных URL-ссылок на простые ссылки
  • Добавление необходимых узлов обрезки безопасности в качестве понятных URL-адресов
  • Ограничьте количество элементов навигации не более 100 и не более трех уровней глубины

Многие сайты не требуют обрезки безопасности, так как структура навигации часто согласована для всех пользователей сайта. Если обрезка системы безопасности отключена и в навигацию добавляется ссылка, доступ к которому имеются не у всех пользователей, ссылка по-прежнему будет отображаться, но приведет к сообщению об отказе в доступе. Нет риска случайного доступа к содержимому.

Реализация управляемой навигации и результатов

Сведения об управляемой навигации см. в статье Microsoft Learn. Например, см. статью Обзор управляемой навигации в SharePoint Server.

Чтобы реализовать управляемую навигацию, необходимо настроить термины с URL-адресами, соответствующими структуре навигации сайта. Во многих случаях управляемую навигацию можно даже курировать вручную, чтобы заменить структурную навигацию. Например:

Структура сайта SharePoint.)

Использование клиентских сценариев на Поиск

Один из общих классов пользовательских реализаций навигации включает шаблоны проектирования, отрисованные клиентом, которые хранят локальный кэш узлов навигации.

Эти поставщики навигации имеют несколько ключевых преимуществ:

  • Как правило, они хорошо работают с адаптивным макетом страниц.
  • Они очень масштабируемые и производительные, так как они могут отрисовыться без затрат на ресурсы (и обновляться в фоновом режиме после истечения времени ожидания).
  • Эти поставщики навигации могут получать данные навигации с помощью различных стратегий, начиная от простых статических конфигураций и заканчивая различными динамическими поставщиками данных.

Примером поставщика данных является использование навигации на основе Поиск, которая обеспечивает гибкость для перечисления узлов навигации и эффективной обработки обрезки безопасности.

Существуют и другие популярные варианты создания пользовательских поставщиков навигации. Дополнительные рекомендации по созданию пользовательского поставщика навигации см. в статье Решения навигации для порталов SharePoint .

С помощью поиска можно использовать индексы, созданные в фоновом режиме, с помощью непрерывного обхода контента. Результаты поиска извлекаются из индекса поиска, а результаты обрезаются по безопасности. Обычно это происходит быстрее, чем у поставщиков встроенных навигаций, когда требуется обрезка безопасности. Использование поиска структурной навигации, особенно если у вас сложная структура сайта, значительно ускорит загрузку страницы. Преимущество этого main по сравнению с управляемой навигацией заключается в том, что вы получаете преимущества от обрезки безопасности.

Этот подход включает в себя создание настраиваемой страницы master и замену встроенного кода навигации пользовательским HTML. Выполните эту процедуру, описанную в следующем примере, чтобы заменить код навигации в файле seattle.html. В этом примере вы откроете seattle.html файл и замените весь элемент id="DeltaTopNavigation" пользовательским HTML-кодом.

Пример. Замена встроенного кода навигации на странице master

  1. Перейдите на страницу Параметры сайта.
  2. Откройте коллекцию страниц master, щелкнув Главные страницы.
  3. Отсюда вы можете перейти по библиотеке и скачать файл seattle.master.
  4. Измените код с помощью текстового редактора и удалите блок кода на следующем снимке экрана.
    Удалите показанный блок кода.
  5. Удалите код между <SharePoint:AjaxDelta id="DeltaTopNavigation"> тегами и <\SharePoint:AjaxDelta> и замените его следующим фрагментом кода:
<div id="loading">
  <!--Replace with path to loading image.-->
  <div style="background-image: url(''); height: 22px; width: 22px; ">
  </div>
</div>
<!-- Main Content-->
<div id="navContainer" style="display:none">
    <div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">
        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
        </a>
        <ul id="menu" data-bind="foreach: $data.children" style="padding-left:20px">
            <li class="static dynamic-children level1">
                <a class="static dynamic-children menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

                 <!-- ko if: children.length > 0-->
                    <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                <!-- ko if: children.length == 0-->
                    <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                </a>

                <!-- ko if: children.length > 0-->
                <ul id="menu"  data-bind="foreach: children;" class="dynamic  level2" >
                    <li class="dynamic level2">
                        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline  ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

          <!-- ko if: children.length > 0-->
          <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
           <!-- /ko -->
          <!-- ko if: children.length == 0-->
          <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
          <!-- /ko -->
                        </a>
          <!-- ko if: children.length > 0-->
         <ul id="menu" data-bind="foreach: children;" class="dynamic level3" >
          <li class="dynamic level3">
           <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
           </a>
          </li>
         </ul>
           <!-- /ko -->
                    </li>
                </ul>
                <!-- /ko -->
            </li>
        </ul>
    </div>
</div>

6. Замените URL-адрес в теге привязки образа загрузки в начале на ссылку на загружающее изображение в семействе веб-сайтов. После внесения изменений переименуйте файл и отправьте его в коллекцию страниц master. При этом создается новый объект . master файл.
7. Этот HTML-код является базовой разметкой, которая будет заполняться результатами поиска, возвращенными из кода JavaScript. Вам потребуется изменить код, чтобы изменить значение для var root = "URL-адрес семейства веб-сайтов", как показано в следующем фрагменте кода:
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";

8. Результаты назначаются массиву self.nodes, а иерархия строится из объектов с помощью linq.js назначения выходных данных массиву self.hierarchy. Этот массив является объектом, привязанным к HTML. Это выполняется в функции toggleView() путем передачи объекта self в функцию ko.applyBinding().
Затем массив иерархии привязывается к следующему HTML-коду:
<div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">

Обработчики событий для mouseenter и mouseexit добавляются в навигацию верхнего уровня для обработки раскрывающихся меню дочернего addEventsToElements() сайта, что выполняется в функции.

В нашем примере сложной навигации новая загрузка страницы без локального кэширования показывает, что время, затраченное на сервере, сокращено из структурной навигации производительности, чтобы получить результат, аналогичный подходу управляемой навигации.

О файле JavaScript...

Примечание.

При использовании пользовательского Кода JavaScript убедитесь, что общедоступная сеть CDN включена и файл находится в расположении CDN.

Весь файл JavaScript выглядит следующим образом:

//Models and Namespaces
var SPOCustom = SPOCustom || {};
SPOCustom.Models = SPOCustom.Models || {}
SPOCustom.Models.NavigationNode = function () {

    this.Url = ko.observable("");
    this.Title = ko.observable("");
    this.Parent = ko.observable("");

};

var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";
var baseUrl = root + "/_api/search/query?querytext=";
var query = baseUrl + "'contentClass=\"STS_Web\"+path:" + root + "'&trimduplicates=false&rowlimit=300";

var baseRequest = {
    url: "",
    type: ""
};


//Parses a local object from JSON search result.
function getNavigationFromDto(dto) {
    var item = new SPOCustom.Models.NavigationNode();
    if (dto != undefined) {

        var webTemplate = getSearchResultsValue(dto.Cells.results, 'WebTemplate');

        if (webTemplate != "APP") {
            item.Title(getSearchResultsValue(dto.Cells.results, 'Title')); //Key = Title
            item.Url(getSearchResultsValue(dto.Cells.results, 'Path')); //Key = Path
            item.Parent(getSearchResultsValue(dto.Cells.results, 'ParentLink')); //Key = ParentLink
        }

    }
    return item;
}

function getSearchResultsValue(results, key) {

    for (i = 0; i < results.length; i++) {
        if (results[i].Key == key) {
            return results[i].Value;
        }
    }
    return null;
}

//Parse a local object from the serialized cache.
function getNavigationFromCache(dto) {
    var item = new SPOCustom.Models.NavigationNode();

    if (dto != undefined) {

        item.Title(dto.Title);
        item.Url(dto.Url);
        item.Parent(dto.Parent);
    }

    return item;
}

/* create a new OData request for JSON response */
function getRequest(endpoint) {
    var request = baseRequest;
    request.type = "GET";
    request.url = endpoint;
    request.headers = { ACCEPT: "application/json;odata=verbose" };
    return request;
};

/* Navigation Module*/
function NavigationViewModel() {
    "use strict";
    var self = this;
    self.nodes = ko.observableArray([]);
    self.hierarchy = ko.observableArray([]);;
    self.loadNavigatioNodes = function () {
        //Check local storage for cached navigation datasource.
        var fromStorage = localStorage["nodesCache"];
        if (false) {
            var cachedNodes = JSON.parse(localStorage["nodesCache"]);

            if (cachedNodes && timeStamp) {
                //Check for cache expiration. Currently set to 3 hrs.
                var now = new Date();
                var diff = now.getTime() - timeStamp;
                if (Math.round(diff / (1000 * 60 * 60)) < 3) {

                    //return from cache.
                    var cacheResults = [];
                    $.each(cachedNodes, function (i, item) {
                        var nodeitem = getNavigationFromCache(item, true);
                        cacheResults.push(nodeitem);
                    });

                    self.buildHierarchy(cacheResults);
                    self.toggleView();
                    addEventsToElements();
                    return;
                }
            }
        }
        //No cache hit, REST call required.
        self.queryRemoteInterface();
    };

    //Executes a REST call and builds the navigation hierarchy.
    self.queryRemoteInterface = function () {
        var oDataRequest = getRequest(query);
        $.ajax(oDataRequest).done(function (data) {
            var results = [];
            $.each(data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results, function (i, item) {

                if (i == 0) {
                    //Add root element.
                    var rootItem = new SPOCustom.Models.NavigationNode();
                    rootItem.Title("Root");
                    rootItem.Url(root);
                    rootItem.Parent(null);
                    results.push(rootItem);
                }
                var navItem = getNavigationFromDto(item);
                results.push(navItem);
            });
            //Add to local cache
            localStorage["nodesCache"] = ko.toJSON(results);

            localStorage["nodesCachedAt"] = new Date().getTime();
            self.nodes(results);
            if (self.nodes().length > 0) {
                var unsortedArray = self.nodes();
                var sortedArray = unsortedArray.sort(self.sortObjectsInArray);

                self.buildHierarchy(sortedArray);
                self.toggleView();
                addEventsToElements();
            }
        }).fail(function () {
            //Handle error here!!
            $("#loading").hide();
            $("#error").show();
        });
    };
    self.toggleView = function () {
        var navContainer = document.getElementById("navContainer");
        ko.applyBindings(self, navContainer);
        $("#loading").hide();
        $("#navContainer").show();

    };
    //Uses linq.js to build the navigation tree.
    self.buildHierarchy = function (enumerable) {
        self.hierarchy(Enumerable.From(enumerable).ByHierarchy(function (d) {
            return d.Parent() == null;
        }, function (parent, child) {
            if (parent.Url() == null || child.Parent() == null)
                return false;
            return parent.Url().toUpperCase() == child.Parent().toUpperCase();
        }).ToArray());

        self.sortChildren(self.hierarchy()[0]);
    };


    self.sortChildren = function (parent) {

        // sjip processing if no children
        if (!parent || !parent.children || parent.children.length === 0) {
            return;
        }

        parent.children = parent.children.sort(self.sortObjectsInArray2);

        for (var i = 0; i < parent.children.length; i++) {
            var elem = parent.children[i];

            if (elem.children && elem.children.length > 0) {
                self.sortChildren(elem);
            }
        }
    };

    // ByHierarchy method breaks the sorting in chrome and firefox
    // we need to resort  as ascending
    self.sortObjectsInArray2 = function (a, b) {
        if (a.item.Title() > b.item.Title())
            return 1;
        if (a.item.Title() < b.item.Title())
            return -1;
        return 0;
    };


    self.sortObjectsInArray = function (a, b) {
        if (a.Title() > b.Title())
            return -1;
        if (a.Title() < b.Title())
            return 1;
        return 0;
    }
}

//Loads the navigation on load and binds the event handlers for mouse interaction.
function InitCustomNav() {
    var viewModel = new NavigationViewModel();
    viewModel.loadNavigatioNodes();
}

function addEventsToElements() {
    //events.
      $("li.level1").mouseover(function () {
          var position = $(this).position();
          $(this).find("ul.level2").css({ width: 100, left: position.left + 10, top: 50 });
      })
   .mouseout(function () {
     $(this).find("ul.level2").css({  left: -99999, top: 0 });
   
    });
   
     $("li.level2").mouseover(function () {
          var position = $(this).position();
          console.log(JSON.stringify(position));
          $(this).find("ul.level3").css({ width: 100, left: position.left + 95, top:  position.top});
      })
   .mouseout(function () {
     $(this).find("ul.level3").css({  left: -99999, top: 0 });
    });
} _spBodyOnLoadFunctionNames.push("InitCustomNav");

Чтобы суммировать код, показанный выше в jQuery $(document).ready функции, viewModel object создается, а затем loadNavigationNodes() вызывается функция для этого объекта. Эта функция загружает ранее созданную иерархию навигации, хранящуюся в локальном хранилище HTML5 клиентского браузера, или вызывает функцию queryRemoteInterface().

QueryRemoteInterface() создает запрос с помощью getRequest() функции с параметром запроса, определенным ранее в скрипте, а затем возвращает данные с сервера. По сути, эти данные представляют собой массив всех сайтов в семействе веб-сайтов, представленных в виде объектов передачи данных с различными свойствами.

Затем эти данные анализируются в ранее определенные SPO.Models.NavigationNode объекты, которые используются Knockout.js для создания наблюдаемых свойств для использования путем привязки значений к HTML-коду, который мы определили ранее.

Затем объекты помещаются в массив результатов. Этот массив анализируется в JSON с помощью Knockout и сохраняется в локальном хранилище браузера для повышения производительности при будущих загрузках страниц.

Преимущества этого подхода

Одним из основных преимуществ этого подхода является то, что при использовании локального хранилища HTML5 навигация сохраняется локально для пользователя при следующей загрузке страницы. Мы получаем значительные улучшения производительности за счет использования API поиска для структурной навигации; однако для выполнения и настройки этой функции требуются некоторые технические возможности.

В примере реализации сайты упорядочены так же, как и готовые структуры навигации; алфавитный порядок. Если вы хотите отклониться от этого порядка, было бы сложнее разработать и поддерживать. Кроме того, этот подход требует отклониться от поддерживаемых master страниц. Если пользовательская страница master не поддерживается, на вашем сайте будут пропущены обновления и улучшения, которые корпорация Майкрософт вносит на master страницы.

Приведенный выше код имеет следующие зависимости:

Текущая версия LinqJS не содержит метод ByHierarchy, используемый в приведенном выше коде, и нарушает код навигации. Чтобы устранить эту проблему, добавьте следующий метод в файл Linq.js перед строкой Flatten: function ().

ByHierarchy: function(firstLevel, connectBy, orderBy, ascending, parent) {
     ascending = ascending == undefined ? true : ascending;
     var orderMethod = ascending == true ? 'OrderBy' : 'OrderByDescending';
     var source = this;
     firstLevel = Utils.CreateLambda(firstLevel);
     connectBy = Utils.CreateLambda(connectBy);
     orderBy = Utils.CreateLambda(orderBy);

     //Initiate or increase level
     var level = parent === undefined ? 1 : parent.level + 1;

    return new Enumerable(function() {
         var enumerator;
         var index = 0;

        var createLevel = function() {
                 var obj = {
                     item: enumerator.Current(),
                     level : level
                 };
                 obj.children = Enumerable.From(source).ByHierarchy(firstLevel, connectBy, orderBy, ascending, obj);
                 if (orderBy !== undefined) {
                     obj.children = obj.children[orderMethod](function(d) {
                         return orderBy(d.item); //unwrap the actual item for sort to work
                     });
                 }
                 obj.children = obj.children.ToArray();
                 Enumerable.From(obj.children).ForEach(function(child) {
                     child.getParent = function() {
                         return obj;
                     };
                 });
                 return obj;
             };

        return new IEnumerator(

        function() {
             enumerator = source.GetEnumerator();
         }, function() {
             while (enumerator.MoveNext()) {
                 var returnArr;
                 if (!parent) {
                     if (firstLevel(enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }

                } else {
                     if (connectBy(parent.item, enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }
                 }
             }
             return false;
         }, function() {
             Utils.Dispose(enumerator);
         })
     });
 },

Обзор управляемой навигации в SharePoint Server

Кэширование и производительность структурной навигации