[SharePoint 2013] Adding Links to the Suite Bar (Newsfeed, SkyDrive, Sites) By Overriding the SuiteLinksDelegate Delegate Control

SharePoint 2013 features a new set of links called the Suite Bar Links that are displayed in the top right corner of every SharePoint page. By default these links include “Newsfeed”, “SkyDrive”, and “Sites”.

When first seeing this links my first thought was “how do I change them?”. In exploring the master page, we can see that the links are added to the page with this delegate control:

This presents two options for changing the links:

1. Remove the delegate control (or hide it) and simply hard code the links in the master page. This is only a viable option if you using a single language site and you are only making the changes for a single site collection. If you were using a multilingual site, you would lose the automatically translated links that SharePoint provides. And you would need to make this change in the master pages of every site collection.

2. Override the delegate control with a custom control. This presents a unique challenge in that all of the default links are hard-coded in non-public methods of the SharePoint assemblies.

Creating a Custom Delegate Control to Override SuiteLinksDelegate

The goal with this custom control is to maintain the same functionality as the built-in SuiteLinksDelegate control while adding our own links. To do this, we are going to use similar code to what the built-in control uses, some .NET reflection, and some techniques of my own.

To get started, ensure that you have SharePoint 2013 installed and configured along with Visual Studio 2012 installed with the SharePoint 2013 developer tools.

  1. Launch Visual Studio 2012 and create a new project: Templates > Visual C# > Office/SharePoint > SharePoint Solutions > SharePoint 2013 Project. For this example, I named the project “SharePointDelegates”. When prompted, select Farm Solution as the solution type.

  2. Add the following references to your project (you will need to browse to C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15ISAPI): Microsoft.Office.Server.dll, Microsoft.Office.Server.Search.dll, Microsoft.Office.Server.UserProfiles.dll, and Microsoft.SharePoint.Portal.dll.

  3. Add a “UrlUtility” class (right-click the solution name > Add > Class.

  4. The UrlUtility class is used by the default SuiteLinksDelegate control and rather than reinvent the wheel, we are just copying the class from the SharePoint assemblies:

  5. Add a “SuiteLinksHelper” class.

  6. The SuiteLinksHelper class contains methods that mimic the internal SharePoint methods used by the SuiteLinksDelegate control. In places that accessed other internal SharePoint methods, .NET reflection is used instead to access the same methods.

  7. Add a new Empty Element to create the Delegate Control override reference. (Right-click solution name > Add > New Item > Empty Element (in the Office/SharePoint group). For this example, I used “SuiteLinksControl” as the name of the element.

  8. In the Elements.xml file of the Empty Element, we are using some XML to specify the Id of the Delegate Control to override and the source of our custom user control (which we haven’t created yet):

  9. In the Solution Explorer, highligh the Empty Element (SuiteLinksControl). In the Properties window, select the […] button for the Safe Controls property to open the Safe Control Entries window.

  10. Add a new Safe Control entry for our custom user control (which we haven’t created yet):

    • Name: SharePointDelegatesControlTemplates
    • Assembly: $SharePoint.Project.AssemblyFullName$
    • Namespace: SharePointDelegates.CONTROLTEMPLATES.SharePointDelegates
    • Safe: True
    • Safe Against Script: True
    • Type Name: *
  11. When we add the Empty Element, a new Feature was automatically added. In the Solution Explorer rename it to something more meaningful (like SuiteLinksFeature).

  12. Open the SuiteLinksFeature and set the scope to Farm. In addition, you should change the Title to something more meaningful (like “Custom Suite Links”). The Title is what gets displayed on the Features page when activating it.

  13. Add the CONTROLTEMPLATES SharePoint mapped folder (right-click Solution Name > Add > Add SharePoint Mapped Folder > Expand TEMPLATE and Select CONTROLTEMPLATES.

  14. To ensure no conflicts occur, add a new folder under the CONTROLTEMPLATES folder with the same name as the solution (like “SharePointDelegates”).

  15. Add a new user control to the SharePointDelegates folder called “SuiteLinksControl.ascx”. (Right-click the SharePointDelegates folder > Add > New Item > User Control (Farm Solution only)

  16. Open the SuiteLinksControl.ascx.cs file and add the following code:

  17. Now that we have our custom control ready to go we can deploy the solution and test. (Right-click the solution name > Deploy)

  18. You should now see the new links at the top right corner of the page when you open your SharePoint site.

That’s it. There is a lot of opportunity to extend this idea (such as with an administration page to manage custom links). It is just unfortunate that Microsoft hard-coded these links rather than making them configurable.

20 Comments

  1. This worked great on my development site, but when I tried to do this on my 2 tier production site, nothing happened. I got the project to deploy, but the menus did not change.

    Reply
    • The same thing happennded with me deployed to farm ok appeared in the features of th central admin when active the suite link menue disapears nothing added

      Reply
  2. Tried the above. New to VS so I’m sure I just miss a simple thing. after hitting “deploy” I get several errors saying: “Statement cannot appear outside of a method body/multiline lambda.” This is happening in my “SuitelinksHelper.vb” and “UrlUtility.vb” class. Any idea of what to try?

    Reply
  3. Great post. Works like a charm. Assume you hide the original “Newsfeed”, “SkyDrive”, and “Sites” links and add your own custom page. How do you handle the creation of the MySite for a user who has never accessed their site before?

    Reply
    • All of the My Site Host pages can be casted to an IPersonalPage. That has the current user’s profile and from there you can check to see if they have a personal site. If not, provision it. In order to ensure this doesn’t get called multiple times before it completes, you might create a cookie (or something to that effect) that indicates the process has started and not to start it again. You can use the sample code here in an AdditionalPageHead control, a user control you put on your master page, a web part on the My Site host, etc.

      var personalPage = this.Page as IPersonalPage;
      if (personalPage != null
      && personalPage.Profile != null
      && personalPage.Profile.PersonalSite == null)
      personalPage.Profile.CreatePersonalSite();

      Reply
  4. Worked perfectly for me, but I noticed that the little indicator at the bottom for which link you’re on (Sites, SkyDrive, Newsfeed) goes away. Any way to keep that functionality?

    Reply
    • This was changed for RTM (this was posted pre-RTM). Here are somethings you can modify to get that back:

      private static void RenderSuiteLink(HtmlTextWriter writer, string url, string name, string linkId, bool isActiveLink)
      {
      // Attempt to AAM the URL
      var aamUrl = string.Empty;
      try
      {
      SPUrlZone zone = SPContext.Current.Site.Zone;
      aamUrl = SPFarm.Local.AlternateUrlCollections.RebaseUriWithAlternateUri(new Uri(url), zone).AbsoluteUri;
      if (string.IsNullOrEmpty(aamUrl))
      aamUrl = url;
      }
      catch
      {
      aamUrl = url;
      }
      writer.AddAttribute(HtmlTextWriterAttribute.Class, “ms-core-suiteLink”);
      writer.RenderBeginTag(HtmlTextWriterTag.Li);
      writer.AddAttribute(HtmlTextWriterAttribute.Class, “ms-core-suiteLink-a”);
      writer.AddAttribute(HtmlTextWriterAttribute.Href, url);
      writer.AddAttribute(HtmlTextWriterAttribute.Id, linkId);
      writer.RenderBeginTag(HtmlTextWriterTag.A);
      writer.RenderBeginTag(HtmlTextWriterTag.Span);
      writer.Write(name);
      if (isActiveLink)
      {
      writer.AddAttribute(HtmlTextWriterAttribute.Id, “Suite_ActiveLinkIndicator_Clip”);
      writer.AddAttribute(HtmlTextWriterAttribute.Class, “ms-suitenav-caratBox”);
      writer.RenderBeginTag(HtmlTextWriterTag.Span);
      writer.AddAttribute(HtmlTextWriterAttribute.Id, “Suite_ActiveLinkIndicator”);
      writer.AddAttribute(HtmlTextWriterAttribute.Class, “ms-suitenav-caratIcon”);
      writer.AddAttribute(HtmlTextWriterAttribute.Src, SPUtility.GetThemedImageUrl(SPUrlUtility.CombineUrl(SPUtility.ContextImagesRoot, “spcommon.png”), “spcommon”));
      writer.RenderBeginTag(HtmlTextWriterTag.Img);
      writer.RenderEndTag();
      writer.RenderEndTag();
      }

      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();
      }

      internal enum SuiteLinkType
      {
      Documents = 0,
      Sites = 1,
      Newsfeed = 2
      }

      private SuiteLinkType GetActiveLink(int webTemplateId, int listTemplateId)
      {
      switch (webTemplateId)
      {
      case 0x15:
      {
      if (listTemplateId == 700)
      return SuiteLinkType.Documents;
      string absolutePath = SPAlternateUrl.ContextUri.AbsolutePath;
      if (absolutePath.EndsWith(“/Social/FollowedContent.aspx”, StringComparison.OrdinalIgnoreCase))
      return SuiteLinkType.Documents;
      if (absolutePath.EndsWith(“/Social/Sites.aspx”, StringComparison.OrdinalIgnoreCase))
      return SuiteLinkType.Sites;
      return SuiteLinkType.Newsfeed;
      }
      case 0x36:
      return SuiteLinkType.Newsfeed;
      }
      return SuiteLinkType.Sites;
      }

      And then with your call to render the links, just add a bool at the end to indicate if it is active. In this instance, I have gotten the active link as “activeLink”:

      RenderSuiteLink(writer, url, SocialControlHelper.GetString(LocStringId.MySuiteLinks_Newsfeed), this.NewsfeedLinkControlId, activeLink == SuiteLinkType.Newsfeed);

      Reply
      • Everything works great, on numerous environments with little effort… except I had trouble with this addition:

        RenderSuiteLink(writer, http://Server1, “Server1″, GetSuiteLinkControlId(“ShellServerOne”), activeLink == SuiteLinkType.Sites);

        The name ‘activeLink’ does not exist in the current context. Can’t figure out this activeLink” part.

        I was also unsuccessful with using icons in the links using the following:
        RenderSuiteLink(writer, UrlUtility.GetImageUrl(“PD01.png”) + “http://Server1/Pages/PD.aspx”, “PD”, GetSuiteLinkControlId(“ShellPD”), true);

        Reply
        • Hi Jason

          Just in case you didn’t find an answer, here’s something that worked for me.

          string absolutePath = SPAlternateUrl.ContextUri.AbsolutePath;
          bool MyProfileIsActive = true;
          bool SkyDriveIsActive = false;

          if (absolutePath.EndsWith(“/person.aspx”, StringComparison.OrdinalIgnoreCase))
          {
          MyProfileIsActive = true;
          SkyDriveIsActive = false;
          }
          else if (absolutePath.EndsWith(“/Documents/Forms/All.aspx”, StringComparison.OrdinalIgnoreCase))
          {
          MyProfileIsActive = false;
          SkyDriveIsActive = true;
          }

          RenderSuiteLink(writer, url, name1, this.MyProfileControlId, MyProfileIsActive);
          RenderSuiteLink(writer, str2, name2, this.SkyDriveProControlId, SkyDriveIsActive);
          writer.RenderEndTag();

          Good luck!

          Reply
  5. This is exactly what I was looking for, but the code about the activeLink does not compile because the activeLink variable does not exist, exactly this line: activeLink == SuiteLinkType.Newsfeed. Right now I hardcoded true and false to see the carat icon be rendered and it works perfect. However I need that it renders the caratIcon, if the current web is the active web, how can I do that?

    Reply
  6. I am using resharper to clean my code, and the constants at the top of the SuiteLinksDelegateCtrl are never used.

    Reply
  7. I am trying to build the solution, however, I am getting errors about:
    ‘Microsoft.SharePoint.Portal.WebControls.LocStringId’ does not contain a definition for ‘MySuiteLinks_Newsfeed’
    What am I doing wrong?

    Reply
    • There were changes to this control for Service Pack 1. I’ll get an updated post eventually. In the meantime, that reference should still work, but the SkyDrive one was simply replaced with “OneDrive” hard-coded (instead of the resource string). If the MySuiteLinks_Newsfeed constant still doesn’t work, replace it with 0x1bb1 (which is what that constant represents).

      Reply
  8. Hello. Thank you very match, it’s working!
    But I had some problems – when I have done “SuiteLinksHelper” I had error with “HttpContext” (I’m working on clean VS 2013). It was becouse in code we are using System.Web, but do not Add this reference. Please, correct your page. So we need to Add Reference “System.Web” (I have added System.Web.http too) for right working.

    Reply
  9. Hello John, your article is great and I implemented it many months ago, however I faced the following problem:

    1,. WHen I upgrade to SP1, the Sites Link renders as “Your trial version has expired”,

    http://screencast.com/t/UDukskykh

    this happens in this line:

    RenderSuiteLink(writer, str3, SuiteLinksHelper.GetString(LocStringId.MySuiteLinks_Sites),

    AllSitesLinkControlId, false, false);

    I guess they changed the resource files between RTM and SP1, but any idea how to fix this?

    Reply
    • The SkyDrive link string is now hard coded to “OneDrive”. As for the other two, I have started using my own resource strings in my own code so future updates don’t cause this again.

      Reply
      • Would you be kind to provide more info on how you updated your solution?
        Thank you!

        Reply
  10. is it possible to implement this solution in office 365?

    Reply
    • At present, you could attempt to do this in a sandboxed solution. However, MS is phasing out code-based sandboxed solutions from Office 365. So, it would only work for a little while. Your only real solution for Office 365 is to edit the master page. There, hide the existing control and manually add the HTML markup for the links.

      Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Pin It on Pinterest

Share This