Easily construct site-resolving URLs in Sitecore using extension methods

New: Dynamically evaluate C# expressions and execute C# scripts with a single statement, from anywhere in a Sitecore or .NET application. Click here for more info.

The LinkManager class is familiar to anyone who has had to construct URLs in Sitecore. It’s useful, especially with the site resolution feature, designed to help one construct cross-domain links. However, it’s important to remember to set the targetHostName attribute in the site configuration for best results.

As one would expect, given the high degree of extensibility of the Sitecore platform, one can extend and change the default link provider mechanism used to generate links. However, in a large number of cases, writing a custom class and configuring it properly, or even constructing a UrlOptions object explicitly and using it in calls to LinkManager, can be avoided by using a few extension methods. Just as the LinkManager.GetItemUrl() method was designed to make link generation easier, a single-shot method for constructing URLs, right from any item, would make things easier in many programming scenarios.

Below is sample code for such an approach. Features include the ability to programmatically set URL options as the default for a site or container, and a method to call from an Item instance to generate a URL, with the option to override the URL option defaults. Site resolution is performed for any content item in a site configured in Web.config with the targetHostName property, even if in a different site from the context item. If desired, one can tweak this approach further, such as by constructing the default URL options used here from configuration settings dealing with links and site resolution.

There’s nothing wrong with the LinkManager implementation, but a bit of sugar such as this would make it easier to use the framework. It wouldn’t prevent switching to a different link provider implementation, either, since the static extension methods simply front-end the built-in API.

public static class SitecoreDataItemExtensions
{
   static SitecoreDataItemExtensions()
   {
      defaultUrlOptions = new Sitecore.Links.UrlOptions();
      defaultUrlOptions.AddAspxExtension = false;
      defaultUrlOptions.AlwaysIncludeServerUrl = true;
      defaultUrlOptions.LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Never;
      defaultUrlOptions.LowercaseUrls = true;
      defaultUrlOptions.ShortenUrls = true;
      defaultUrlOptions.SiteResolving = true;
      defaultUrlOptions.UseDisplayName = false;   
   }

   private static System.Collections.Concurrent.ConcurrentDictionary<string, Sitecore.Links.UrlOptions> siteUrlOptionsMap = new System.Collections.Concurrent.ConcurrentDictionary<string,Sitecore.Links.UrlOptions>();
   private static Sitecore.Links.UrlOptions defaultUrlOptions;

   /// <summary>
   /// Constructs and returns a clone of the default URL options for the specified site
   /// </summary>
   /// <param name="siteContext">The site for which to get the default URL options</param>
   /// <returns>A clone of the URL options, using the container-wide default as a fallback, with the specified site context applied</returns>
   private static Sitecore.Links.UrlOptions GetDefaultUrlOptions(Sitecore.Sites.SiteContext siteContext)
   {
      Sitecore.Links.UrlOptions urlOptions = defaultUrlOptions;
      try { siteUrlOptionsMap.TryGetValue(siteContext.Name, out urlOptions); } catch {}
      if (urlOptions == null)
         urlOptions = defaultUrlOptions;

      urlOptions = (Sitecore.Links.UrlOptions)urlOptions.Clone();
      urlOptions.Site = siteContext;
      return urlOptions;
   }

   /// <summary>
   /// Sets a clone of these UrlOptions as the default to use for all sites in this container
   /// </summary>
   public static void SetAsDefault(this Sitecore.Links.UrlOptions urlOptions)
   {
      if (urlOptions == null) return;

      Sitecore.Links.UrlOptions clone = (Sitecore.Links.UrlOptions)urlOptions.Clone();
      clone.Site = null;
      defaultUrlOptions = clone;
   }

   /// <summary>
   /// Sets a clone of this set of URL options as the default for the specified site
   /// </summary>
   /// <param name="siteContext">The optional site for which to set these UrlOptions as the default, overriding any which are set on the UrlOptions directly</param>
   public static void SetAsSiteDefault(this Sitecore.Links.UrlOptions urlOptions, Sitecore.Sites.SiteContext siteContext = null)
   {
      if (urlOptions == null) return;
      else if (siteContext == null)
      {
         urlOptions.SetAsDefault();
         return;
      }

      urlOptions = (Sitecore.Links.UrlOptions)urlOptions.Clone();
      urlOptions.Site = siteContext;
      urlOptions.SiteResolving = true;
      
      siteUrlOptionsMap.AddOrUpdate(siteContext.Name, urlOptions, (k, v) => urlOptions);
   }

   /// <summary>
   /// Sets a clone of this set of URL options as the default for the specified site
   /// </summary>
   /// <param name="siteName">The optional name of the site for which to set these UrlOptions as the default, overriding any which are set on the UrlOptions directly</param>
   public static void SetAsSiteDefault(this Sitecore.Links.UrlOptions urlOptions, string siteName = null)
   {
      if (urlOptions == null) return;

      Sitecore.Sites.SiteContext siteContext = null;
      if (!string.IsNullOrWhiteSpace(siteName))
      {
         try
         {
            siteContext = Sitecore.Sites.SiteContextFactory.GetSiteContext(siteName);
         } 
         catch (Exception e) {
            throw new InvalidOperationException("Could not find site '" + siteName + "' (Check Web.config <sites>); " + e.Message);
         }
         if (siteContext == null) 
            throw new InvalidOperationException("Could not find site '" + siteName + "' (Check Web.config <sites>)");
      }
      
      if (siteContext == null)
         siteContext = urlOptions.Site;

      urlOptions.SetAsSiteDefault(siteContext);
   }

   private static List<Sitecore.Sites.SiteContext> siteContexts = null;

   /// <summary>
   /// Gets a list of site contexts for this container
   /// </summary>
   private static List<Sitecore.Sites.SiteContext> SiteContexts
   {
      get 
      {
         if (siteContexts == null)
         {
            List<Sitecore.Sites.SiteContext> siteContextList = new List<SC.Sites.SiteContext>();

            Sitecore.Sites.SiteContext sc;
            foreach (string siteName in Sitecore.Sites.SiteContextFactory.GetSiteNames())
            {
               sc = Sitecore.Sites.SiteContextFactory.GetSiteContext(siteName);
               if (sc != null) siteContextList.Add(sc);
            }

            siteContexts = siteContextList;
         }

         return siteContexts;
      }
   }

   /// <summary>
   /// Gets the site context, if any, for this item
   /// </summary>
   public static Sitecore.Sites.SiteContext GetSiteContext(this Sitecore.Data.Items.Item item)
   {
      if (item == null) return null;
      string itemPath = item.Paths.FullPath.ToLower();

      foreach (Sitecore.Sites.SiteContext siteContext in SiteContexts)
         if (!string.IsNullOrWhiteSpace(siteContext.RootPath) &&
            siteContext.RootPath.StartsWith("/sitecore/content/") &&
            (siteContext.RootPath.Length > 18) &&
            itemPath.StartsWith(siteContext.RootPath.ToLower()))
            return siteContext;

      return null;
   }

   /// <summary>
   /// Gets a URL for this item; if no optional URL options are supplied, uses site-resolving default URL options
   /// </summary>
   public static string GetUrl(this Sitecore.Data.Items.Item item, Sitecore.Links.UrlOptions urlOptions = null)
   {
      if (item == null) throw new ArgumentNullException();
      Sitecore.Sites.SiteContext siteContext = item.GetSiteContext();

      if (urlOptions == null)
      {
         if (siteContext != null)
            urlOptions = GetDefaultUrlOptions(siteContext);
      }
      else if (siteContext != null)
      {
         urlOptions.Site = siteContext;
      }

      if (urlOptions == null)
         return Sitecore.Links.LinkManager.GetItemUrl(item);
      else
         return Sitecore.Links.LinkManager.GetItemUrl(item, urlOptions);
   }

}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s