Делаем сайт на SharePoint 2010. Оптимизация
Сайт на SharePoint 2010. Брендинг
Сайт на SharePoint 2010. Оптимизация
Сайт на SharePoint 2010. Брендинг Wiki-страниц
Сайт на SharePoint 2010. Построение иерархии страниц
Сегодня я расскажу про оптимизацию интернет-сайтов на базе MS SharePoint 2010 для достижения максимальной производительности. Этот пост будет продолжением предыдущего, посвященного брендингу SharePoint, т.е. оптимизация по большей части будет касаться интерфейса посетителей, а не редакторов/модераторов/администраторов.
Веб-части (WebParts)
Начну с простого, а именно с оптимизации отображения веб-частей на странице. В SharePoint у каждой веб-части есть название, меню и граница. Ничего из этого для посетителю сайта не понадобится. Скрыть эту "обертку" можно, указав свойство веб-части ChromeType
равным PartChromeType.None
. Т.к. мы должны оставить нетронутым интерфейс редакторов, придется реализовывать данный функционал в нашем классе FluentWebPartPage
. Сначала продублируем приватное свойство SPWebPartManager
в нашем классе:
- private SPWebPartManager SPWebPartManager
- {
- get
- {
- return (WebPartManager.GetCurrentWebPartManager(this) as SPWebPartManager);
- }
- }
Теперь на событии OnLoad мы зададим тип хрома для всех веб-частей на странице:
- protected override void OnLoad(EventArgs e)
- {
- base.OnLoad(e);
- // Если пользователь не анонимный, то выходим
- if (!IsAnonymous) return;
- // Перебираем все веб-части и задаем им тип хрома
- foreach(var webPart in SPWebPartManager.WebParts.Cast<WebPart>())
- {
- webPart.ChromeType = PartChromeType.None;
- }
- }
Теперь каждая веб-часть "обрамлена" незначительным HTML-кодом:
- <table class="s4-wpTopTable" border="0" cellpadding="0" cellspacing="0" width="100%">
- <tbody>
- <tr>
- <td valign="top">
- <div webpartid="[WebPartGuid]" haspers="false" id="WebPartWPQ3"
- width="100%" class="ms-WPBody noindex" allowdelete="false" style="">
- <!-- Содержимое веб-части -->
- </div>
- </td>
- </tr>
- </tbody>
- </table>
Если развивать это направление дальше, то можно переопределить рендеринг веб-частей и зон веб-частей, чтобы, например, уйти от таблиц.
Скрипты
Следующий шаг будет касаться клиентского кода, а именно его минимизации. Здесь я опишу два способа.
ScriptLink.IsMinimalMode
Первый способ касается свойства IsMinimalMode
объекта ScriptLink
:
- internal static bool IsMinimalMode
- {
- get
- {
- return ((HttpContext.Current.Items["sp-MinimalScriptKey"]
- as string) == "MinimalScriptMode");
- }
- set
- {
- string str = value ? "MinimalScriptMode" : string.Empty;
- HttpContext.Current.Items["sp-MinimalScriptKey"] = str;
- }
- }
-
Таким образом, чтобы задействовать этот режим, нам достаточно в метод FluentWebPartPage.SetAnonymousView
следующую строчку кода:
- HttpContext.Current.Items["sp-MinimalScriptKey"] = "MinimalScriptMode";
В этом случае мы оставляем за SharePoint право решать какие срипты загружать а какие нет. Мы просто декларируем свое желание минимизировать клиентский код.
ScriptLink.GetScriptLinks
Второй способ более радикален. Здесь уже мы будем решать какие скрипты загружать, а какие нет. SharePoint получает список скриптов, подлежащих регистрации на странице, вызывая метод ScriptLink.GetScriptLinks()
. Вот его код:
- private static List<ScriptLinkInfo> GetScriptLinks()
- {
- List<ScriptLinkInfo> list =
- HttpContext.Current.Items["sp-scriptlinks"]
- as List<ScriptLinkInfo>;
- if (list == null)
- {
- list = new List<ScriptLinkInfo>();
- HttpContext.Current.Items["sp-scriptlinks"] = list;
- }
- return list;
- }
Сам класс ScriptLinkInfo помечен как internal, что ограничивает нас в его применении, поэтому придется использовать рефлексию. Регистрация скриптов на странице в SharePoint происходит на событии OnPreRender, т.е. возможность повлиять на итоговый набор скриптов у нас есть. Класс ScriptLinkInfo содержит следующие свойства:
- public bool Defer { get; set; }
- public string Filename { get; }
- public string Language { get; }
- public bool LoadAfterUI { get; set; }
- public bool Localizable { get; }
- public bool OnDemand { get; set; }
- public string OnDemandKey { get; set; }
- public IEnumerable<string> Sections { get; }
Я покажу пример который будет искать скрипт по его имени (Filename) и, если он не помечен как загружаемый по требованию (поле OnDemand), то удалять его из коллекции скриптов. Также можно задействовать локализацию и другие свойства для более сложной логики.
- /// <summary>
- /// Удаление скрипта из списка на загрузку
- /// </summary>
- /// <param name="scriptName">Название скрипта</param>
- /// <param name="restrictIsDemand">Удалять даже если загрузка по требованию</param>
- private static void RestrictClientScript(string scriptName, bool restrictIsDemand)
- {
- // Берем скрипты, подлежащие регистрации на странице
- var scripts = HttpContext.Current.Items[SPScriptLinks] as IList;
- // Выходим, если scripts не существует
- if (scripts == null) return;
- var fileIndex = -1;
- // Беребираем все объекты в коллекции
- for (var i = 0; i < scripts.Count; i++)
- {
- // Текущий объект ScriptLinkInfo
- var file = scripts[i];
- var type = file.GetType();
- // Ищем в типе свойства Filename и OnDemand
- var prop = type.GetProperty("Filename");
- if (prop == null) continue;
- var propDemand = type.GetProperty("OnDemand");
- if (propDemand == null) continue;
- // Получаем значение свойства Filename
- var jsName = prop.GetValue(file, null).ToString();
- // Получаем значение свойства OnDemand
- // По умолчанию считаем OnDemand = false
- var jsOnDemandFlag = propDemand.GetValue(file, null) is bool
- ? (bool) propDemand.GetValue(file, null)
- : false;
- // Если скрипт загружется по требованию, то пропускаем его
- // если restrictIsDemand = false
- if (jsOnDemandFlag && !restrictIsDemand) continue;
- // Если имя скрипта не совпадает со значение параметра scriptName,
- // то передаем управление следующе итерации
- if (string.Compare(scriptName, jsName, true) != 0) continue;
- // Запоминаем позицию скрипта в коллекции и выходим из цикла
- fileIndex = i;
- break;
- }
- // Если в коллеции был найден скрипт, то удалем его
- if (fileIndex != -1)
- scripts.RemoveAt(fileIndex);
- // Указываем новую коллекцию скриптов
- HttpContext.Current.Items[SPScriptLinks] = scripts;
- }
Теперь можно использовать этот метод на событии OnPreRender:
- protected override void OnPreRender(EventArgs e)
- {
- base.OnPreRender(e);
- RestrictClientScript("core.js", true);
- RestrictClientScript("init.js", true);
- }
Стили
На набор стилей, которые будут зарегистрированы на странице повлиять не получится, поэтому мы просто уберем из master-страницы для посетителей контрол CssLink
и заменим его на стандартный HTML-элемент link
.
Ссылки на css в master-странице для редакторов сайта:
- <SharePoint:CssLink runat="server" Version="4"/>
Ссылки на css в master-странице для посетителей сайта:
- <link rel="stylesheet" type="text/css" media="Screen"
- href="/_layouts/ZhukBlog.InternetSite.Branding/Styles/Screen.css" />
- <link rel="stylesheet" type="text/css" media="Print"
- href="/_layouts/ZhukBlog.InternetSite.Branding/Styles/Print.css" />
Так как стандартный интерфейс SharePoint не используется для интернет пользователей, то можно и, я считаю, вполне разумно создать свой css-файл с нуля.
Результаты
Результат оптимизации по сравнению со стандартным интерфейсом SharePoint 2010 более чем показателен:
Незначительное уменьшение размера css-файлов связано стем, что файл Screen.css является копией файла CoreV4.css за исключением того, что из него удалены комментарии.
Что касается клиентского кода, то его меньше не стало, но теперь он будет подгружаться по требованию.
Здесь под временем загрузки страницы я имею ввиду время её генерации. Эти значения я получал с помощью Developer Dashboard.
Кэширование
Также для повышения производительности можно задействовать кэширование. Подробно кэширование в этом посте я описывать не буду, т.к. это уже административное воздействие на производительность SharePoint. Просто приведу ссылки на статьи в MSDN для ознакомления:
Кэширование вывода и профили кэша - стандартный механизм ASP.NET. Если в двух словах, то это "обмен" места ОЗУ на время ЦП. Если Front-End сервером два и более то есть вероятность возникновения несогласованности.
Дисковое кэширование для больших двоичных объектов (BLOB) - механизм, при задействовании которого SharePoint сохраняет на диск файлы, запрашиваемые из списков/библиотек, чтобы в дальнейшем снять нагрузку с SQL Server.
Кэширование объектов - сохранения элементов объектной модели SharePoint в памяти. Также касается и Linq to SharePoint.