Всемогущий, Google, найди мне чего-нибудь!

вторник, 3 января 2012 г.

Перенаправление на главную страницу (Master page, SharePoint 2010)

- Задание -

При использовании действия "Войти под другим пользователем" новый пользователь должен быть перенаправлен на главную странцу (рис. 1), а не на ту, с которой было выполнено действие.

Рис. 1. Необходимая кнопка для изменения.

- Анализ -

Кнопка, которую мы анализируем находится в пользовательском контроле Welcome.ascx, который в свою очередь находится в папке CONTROLTEMPLATES. Рассмотрим его код разметки (часть кода была опущена):

<SharePoint:PersonalActions 
       AccessKey="<%$Resources:wss,personalactions_menu_ak%>"
       ToolTip="<%$Resources:wss,open_menu%>" 
       runat="server" 
       ID="ExplicitLogout" 
       Visible="false">
    <CustomTemplate>
        <SharePoint:FeatureMenuTemplate 
               runat="server" 
               FeatureScope="Site" 
               Location="Microsoft.SharePoint.StandardMenu"
               GroupId="PersonalActions" 
               ID="ID_PersonalActionMenu" 
               UseShortId="true">
            <%-- Тут пункты меню --%>
            <SharePoint:MenuItemTemplate 
                   runat="server" 
                   id="ID_LoginAsDifferentUser"
                   Text="<%$Resources:wss,personalactions_loginasdifferentuser%>"
                   Description="<%$Resources:wss,personalactions_loginasdifferentuserdescription%>"
                   MenuGroupId="200"
                   Sequence="100"
                   UseShortId="true" />
            <%-- И тут пункты меню --%>
        </SharePoint:FeatureMenuTemplate>
    </CustomTemplate>
</SharePoint:PersonalActions>

Итак, мы нашли необходимый нам пункт меню с уникальным идентификатором ID_LoginAsDifferentUser, но почему-то отсутствует инициализация свойства ClientOnClickScript. Сразу промелькнула мысль, что инициализация осуществляется в code behind. Смотрим туда, но интересного ничего не находим. Смотрим методы дочерних элементов и что мы видим (ILSpy 1.0.0.1000):

public sealed class PersonalActions : ToolBarMenuButton, IPreRenderOverride
{
 [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
 protected override void CreateChildControls()
 {
  /* Тут ничего интересного. */
  this.SetLoginAsDifferentUserMenuItemGoToPageUrl();
  this.SetSignOutMenuItemGoToPageUrl();
  this.HandleRequestForAccessMenuItem();
 }

 private void SetLoginAsDifferentUserMenuItemGoToPageUrl()
 {
  MenuItemTemplate menuItem = base.GetMenuItem("ID_LoginAsDifferentUser");
  if (menuItem != null)
  {
   if (SPSecurity.AuthenticationMode == AuthenticationMode.None)
   {
    menuItem.Visible = false;
    return;
   }

   string serverRelativeUrlFromUrl = 
      this.Web.GetServerRelativeUrlFromUrl("_layouts/closeConnection.aspx?loginasanotheruser=true");
   menuItem.ClientOnClickScript = "javascript:LoginAsAnother('" + 
      SPHttpUtility.EcmaScriptStringLiteralEncode(serverRelativeUrlFromUrl) + "', 0)";
   }
  }
}

Вуаля, то что нам нужно! Однако зоркий глаз программиста увидит, что унаследоваться от данного класса нельзя, соответственно необходимо искать другие подходы для переопределения данной функциональности. Можно, конечно, поправить JS-функцию LoginAsAnother() в init.js, но я бы не советовал трогать встроенную функциональность, лучше пойти в обход и "склонировать" (об этом далее).

- Действия -

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

<wssuc:Welcome id="IdWelcome" runat="server" EnableViewState="false" />

Далее создаём свой пользовательский контрол в mapped-папке CONTROLTEMPLATES, например, с названием Welcome.ascx (рис. 2).

Рис. 2. Создание пользовательского контрола Welcome.ascx

Разметку копируем с Welcome.ascx. Далее заходим в Welcome.ascx.cs и добавляем следущее:

/* Тут атрибуты, если необходимо */
public partial class WelcomeControl : Welcome
{
 private const string LoginAsAnotherUserId = "W_ID_LoginAsDifferentUser";

 /// 
 /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
 /// 
 protected override void CreateChildControls()
 {
  base.CreateChildControls();
  this.SetLoginAsDifferentUserMenuItemGoToPageUrl();
 }

 /// 
 /// Builds functionality for the control that performs "Sign in as different user" action.
 /// 
 protected virtual void SetLoginAsDifferentUserMenuItemGoToPageUrl()
 {
  MenuItemTemplate menuItem = this.ExplicitLogout.GetMenuItem(LoginAsAnotherUserId);
  if (menuItem != null)
  {
   if (SPSecurity.AuthenticationMode == AuthenticationMode.None)
   {
    menuItem.Visible = false;
    return;
   }

   SPWeb contextWeb = this.ExplicitLogout.Web;
   string serverRelativeUrlFromUrl = SPUrlUtility.CombineUrl(
       contextWeb.Url,
       "/_layouts/closeConnection.aspx?loginasanotheruser=true");
   menuItem.ClientOnClickScript = string.Format(
       "javascript:WLoginAsAnother('{0}', '{1}', 0);",
       SPHttpUtility.EcmaScriptStringLiteralEncode(serverRelativeUrlFromUrl),
       contextWeb.Site.Url);
   }
  }
 }
}

Значение поля LoginAsAnotherUserId - новый идентификатор действия, которое уже мы будем переопределять. Старый идентификатор просто заменяем на новый (в разметке). В методе PersonalActions.SetLoginAsDifferentUserMenuItemGoToPageUrl() стоит проверка на null если старого пункта меню не найдено, соответственно исключений нам не грозит. Далее, необходимо создать аналог JS-функции LoginAsAnother(), я назвал её WLoginAsAnother и принимает она уже не 2 параметра, а 3 (об этом далее). Создаём JS-файл (рис. 3), которые в дальнейшем будет зарегистрирован на мастер-странице через SctipLink (или можно зарегистрировать его в ascx-файле, не имеет значения).

Рис. 3. Добавление JS-файла с переопределённой функцией

Вот его код:

function ULSWInit() { 
 var o = new Object; 
 o.ULSTeamName = "Project name"; //// Укажите своё имя проекта.
 o.ULSFileName = "mkb.init.js"; 
 return o; 
}

function WLoginAsAnother(url, sourceUrl, bUseSource) {
    ULSWInit: ;
    document.cookie = "loginAsDifferentAttemptCount=0";
    if (bUseSource == "1") {
        GoToPage(url);
    }
    else {
        var ch = url.indexOf("?") >= 0 ? "&" : "?";
        url += ch + "Source=" + escapeProperly(sourceUrl);
        STSNavigate(url);
    }
}

Как мы видим, с какой бы страницы пользователь не вышел, он всегда будет перенаправлен на страницу, которую мы указали (contextWeb.Site.Url). Теперь топаем на мастер-страницу, регистрируем вместо стандартного Welcome.ascx только что созданный, регистрируем JS-файл и радуемся. В данном случае мы в выигрыше по двум причинам:

  1. не затронули стандартной функциональности и при необходимости можем безболезненно вернуть всё обратно.
  2. имеем доступ ко всем встроенным пунктам меню и можем добавлять новые прямо в разметку (без CustomAction).

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

Комментариев нет: