7

I am working with a multi-lingual multi site solution and I come across a case where I need to set a different link manager provider settings for only one of the sites in my solution.

Is there a way to set the custom link provider for only one site, ignoring the default provider ?

<linkManager defaultProvider="sitecore">
   <providers>
      ...
      <add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="asNeeded" languageLocation="filePath" lowercaseUrls="false" shortenUrls="true" useDisplayName="false" />
      <add name="siteSpecificLinkManager" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="always" languageLocation="filePath" lowercaseUrls="false" shortenUrls="true" useDisplayName="false" />
      ...
   </providers>
</linkManager>`

I want to set "siteSpecificLinkManager" as a link manager provider for only one site.

1 Answer 1

10

There is no out of the box way to make custom link manager configurations scope to a particular site, however you could always include that logic in the custom link manager itself. You can make your link manager resolve the site that a link is for. Then, if the resolved site is Site A then use the custom link manager logic; otherwise, if the resolved site is Site B then use the native link manager logic; and so on. This is not an original idea, but rather one that I took from JammyKam's SwitchingLinkProvider implementation (credit to @MarkCassidy for finding the link before I did).

SwitchingLinkProvider

Core Concepts

The following are the core concepts on which the SwitchingLinkProvider implementation is based:

  • Sitecore supports a LinkProviderCollection allowing more than one LinkProvider to be specified
  • One LinkProvider must be the default
  • LinkProvider can resolve the context site
  • Custom config attributes can be added to <site> definitions

Implementation at a High-Level

Implementing the SwitchingLinkProvider, the first thing that you need to do is create a default LinkProvider that does nothing except choose which site-specific LinkProvider's logic will be used. This LinkProvider is the switcher. This provider should be patched into the list of providers and set as the defaultProvider.

The next thing that you need to do is create and patch in your site-specific LinkProvider types into the list, (conventionally) giving them a name that will help you recognize which site(s) they belong to.

Lastly, you will need to add custom linkProvider attributes to each of your <site> definitions for which you wish to use a custom, site-specific LinkProvider. The linkProvider attribute value will be set to the name of the LinkProvider configuration that you wish to use. In your SwitchingLinkProvider class, you will resolve the context site, retrieve the configuration from its <site> definition, and then read the value of the linkProvider attribute to decide which LinkProvider configuration you will use.

Code and Patches

The SwitchingLinkProvider

The following is the class for the SwitchingLinkProvider, as copied from JammyKam's blog:

public class SwitchingLinkProvider : Sitecore.Links.LinkProvider
{
    private const String FALLBACK_ATTRIBUTE_KEY = "fallback";
    private const String LINKPROVIDER_ATTRIBUTE_KEY = "linkProvider";
    private string Fallback { get; set; }

    public override void Initialize(string name, NameValueCollection config)
    {
        base.Initialize(name, config);
        this.Fallback = config[FALLBACK_ATTRIBUTE_KEY];
        Assert.IsNotNullOrEmpty(this.Fallback, "fallback");
    }

    private Sitecore.Links.LinkProvider SiteLinkProvider
    {
        get
        {
            var siteLinkProvider = (Sitecore.Context.Site != null)
                                    ? Sitecore.Context.Site.Properties[LINKPROVIDER_ATTRIBUTE_KEY] : String.Empty;

            if (String.IsNullOrEmpty(siteLinkProvider))
                siteLinkProvider = this.Fallback;

            return LinkManager.Providers[siteLinkProvider] 
                   ?? LinkManager.Providers[this.Fallback];
        }
    }
    ...    
}

Note that JammyKam includes support for LinkProvider fallback, which I highly recommend that you use, but will not go into in this post as it is extra, optional functionality.

Note also that the site-resolving logic that you are looking for occurs in the SiteLinkProvider property's getter method. This is where the context site is resolved and the LinkProvider to be used is resolved.

The following is the patch-config for the SwitchingLinkProvider, which JammyKam aptly named "switcher":

<linkManager>
  <patch:attribute name="defaultProvider" value="switcher" />
  <providers>
    <add name="switcher" fallback="sitecore" type="Sitecore.Custom.Links.SwitchingLinkProvider, Sitecore.Custom" />
  </providers>
</linkManager>

Note that the defaultProvider is set to switcher so that the SwitchingLinkProvider will be used first, giving it the ability to choose which LinkProvider's logic should actually be used.

Site-Specific Providers

The following is the patch configuration for two sample site-specific providers that JammyKam included in his post. He uses the naming convention "provider-[sitename]" which is totally valid and descriptive, but you can change it, if you like. Remember that the name is not really functional, but more for description and reference purposes:

<linkManager>
  <providers>
    <add name="provider-site1" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" languageEmbedding="never" useDisplayName="true" ... />
    <add name="provider-site2" type="Sitecore.Custom.Links.SpecificLinkProvider, Sitecore.Custom" alwaysIncludeServerUrl="true" languageEmbedding="always" lowercaseUrls="true" ... />    
  </providers>
</linkManager>

Lastly, the following is the patch-config for the custom linkProvider attributes added to the <site> definitions, in order to tie each site-specific LinkProvider to its respective site(s):

<sites>
  <site name="site1">
    <patch:attribute name="linkProvider" value="provider-site1" />
  </site>
  <site name="site2"> 
    <patch:attribute name="linkProvider" value="provider-site2" />
  </site>
  <site name="site3">
    ...
  </site>
  ...
</sites>

Adapting to Your Use-Case

In order to use this solution, you will need to implement the SwitchingLinkProvider, add in the necessary patch configuration for it, and patch the <site> definition for the site that you wish to use the "siteSpecificLinkManager" to include a linkProvider="siteSpecificLinkManager" attribute.

The patches should end up looking something like the following:

<linkManager>
  <patch:attribute name="defaultProvider" value="switcher" />
  <providers>
    <add name="switcher" fallback="sitecore" type="Sitecore.Custom.Links.SwitchingLinkProvider, Sitecore.Custom" />
    <add name="siteSpecificLinkManager" fallback="sitecore" type="..." />
  </providers>
</linkManager>
<sites>
  <site name="mySiteWithCustomLinkProvider">
    <patch:attribute name="linkProvider" value="siteSpecificLinkManager" />
  </site>
</sites>

Important Note for Those Using Sitecore 8.x...

In the comments, @jwsadler points out that the LinkManager.Providers property has been marked obsolete in 8.x, which means that this solution will result in compiler warnings. The property has been marked with the ObsoleteAttribute and given the comment "Use methods fromLinkManagerinstead." The property looks as follows:

[Obsolete("Use methods from LinkManager instead.")]
public static LinkProviderCollection Providers
{
  get
  {
    return LinkManager.ProviderHelper.Value.Providers;
  }
}

Unfortunately, Sitecore has not provided any methods for getting the providers on the LinkManager, so the technique above is really the only way of doing. However, if your goal is to simply get rid of the compiler warnings (for code analysis, etc.) then what you can do is instantiate your own ProviderHelper and get the values from there, using the same logic as the LinkManager.Providers property. Note that the logic would be exactly the same as the obsolete logic, but the warnings would not show up.

Please note that I do NOT recommend the below implementation, as it circumvents good compiler warnings for a property that may be removed and replaced in an upcoming release. Instead, I recommend the same implementation as was described earlier in this post, using the LinkManager.Providers property. However, if you are still set on eliminating the compiler warnings then you can change your SwitchingLinkProvider to be as follows:

public class SwitchingLinkProvider : Sitecore.Links.LinkProvider
{
    private const String FALLBACK_ATTRIBUTE_KEY = "fallback";
    private const String LINKPROVIDER_ATTRIBUTE_KEY = "linkProvider";

    private string Fallback { get; set; }

    private static readonly LazyResetable<Sitecore.Configuration.ProviderHelper<LinkProvider, LinkProviderCollection>> ProviderHelper = ServiceLocator.GetRequiredResetableService<Sitecore.Configuration.ProviderHelper<LinkProvider, LinkProviderCollection>>();

    public override void Initialize(string name, NameValueCollection config)
    {
        base.Initialize(name, config);
        this.Fallback = config[FALLBACK_ATTRIBUTE_KEY];
        Assert.IsNotNullOrEmpty(this.Fallback, "fallback");
    }

    private Sitecore.Links.LinkProvider SiteLinkProvider
    {
        get
        {
            var siteLinkProvider = (Sitecore.Context.Site != null)
                                    ? Sitecore.Context.Site.Properties[LINKPROVIDER_ATTRIBUTE_KEY] : String.Empty;

            if (String.IsNullOrEmpty(siteLinkProvider))
                siteLinkProvider = this.Fallback;

            var providers = ProviderHelper.Value.Providers;

            return providers[siteLinkProvider] 
                   ?? providers[this.Fallback];
        }
    }
    ...    
}

To summarize, if you're using Sitecore 8.x, my recommendation would be to use the same technique as described for earlier versions, including the use of the LinkManager.Providers property, and make an exception for the resulting compiler warnings in your CI/CD processes, code analysis, etc. If and when Sitecore adds a new technique for getting the providers, this answer will be updated. Until then, the LinkManager.Providers property still works and is the way to go.

5
  • Site-Specific Providers is exactly what I am looking for. Thanks a ton !! Jun 14, 2017 at 18:25
  • This approach now gives compiler warnings when trying to use LinkManager.Providers, is there a recommended approach for Sitecore 8.x and above?
    – jwsadler
    Sep 13, 2017 at 16:40
  • @jwsadler, see my updated answer (the last section, which includes a note for those using Sitecore 8.x). To summarize, there is no other technique for doing this currently available in Sitecore. There is a way (that I do not recommend) that you can circumvent the property to avoid the compiler warnings. I cannot stress enough that I do not recommend this. The compiler warnings are a tool and they are your friend. When that property is replaced in the future, they will likely show you what you should change the code to. Don't let code analysis tools dissuade you from good coding practices. ;) Sep 13, 2017 at 17:23
  • @ZacharyKniebel thanks! Appreciate the quick response.
    – jwsadler
    Sep 13, 2017 at 18:00
  • It looks like there is no way to set a provider in Sitecore 9 :-(
    – Paul Urban
    Oct 30, 2019 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.