Earlier today someone started a thread in one of the SDN forums asking how to go about adding the ability to have a different favicon for each website managed in the same instance of Sitecore.
I had implemented this in the past for a few clients, and thought I should write a post on how I had done this.
In most of those solutions, the site’s start item would contain a “server file” field — yes I know it’s deprecated but it works well for this (if you can suggested a better field type to use, please leave a comment below) — that would point to a favicon on the file system:
Content authors/editors can then choose the appropriate favicon for each site managed in their Sitecore instance — just like this:
Not long after the SDN thread was started, John West — Chief Technology Officer at Sitecore USA — wrote a quick code snippet, followed by a blog post on how one might go about doing this.
John’s solution is a different than the one I had used in the past — each site’s favicon is defined on its site node in the Web.config.
After seeing John’s solution, I decided I would create a hybrid solution — the favicon set on the start item would have precedence over the one defined on the site node in the Web.config. In other words, the favicon defined on the site node would be a fallback.
For this hybrid solution, I decided to create a custom pipeline to retrieve the favicon for the context site, and created the following pipeline arguments class for it:
using System.Web.UI; using Sitecore.Pipelines; namespace Sitecore.Sandbox.Pipelines.GetFavicon { public class FaviconTryGetterArgs : PipelineArgs { public string FaviconUrl { get; set; } public Control FaviconControl{ get; set; } } }
The idea is to have pipeline processors set the URL of the favicon if possible, and have another processor create an ASP.NET control for the favicon when the URL is supplied.
The following class embodies this high-level idea:
using System; using System.Web.UI; using System.Web.UI.HtmlControls; using Sitecore; using Sitecore.Configuration; using Sitecore.Data.Fields; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Sandbox.Utilities.Extensions; namespace Sitecore.Sandbox.Pipelines.GetFavicon { public class FaviconTryGetter { private string FaviconFieldName { get; set; } public void TryGetFromStartItem(FaviconTryGetterArgs args) { Assert.ArgumentNotNull(args, "args"); bool canProcess = !string.IsNullOrWhiteSpace(FaviconFieldName) && Context.Site != null && !string.IsNullOrWhiteSpace(Context.Site.StartPath); if (!canProcess) { return; } Item startItem = Context.Database.GetItem(Context.Site.StartPath); args.FaviconUrl = startItem[FaviconFieldName]; } public void TryGetFromSite(FaviconTryGetterArgs args) { Assert.ArgumentNotNull(args, "args"); bool canProcess = Context.Site != null && string.IsNullOrWhiteSpace(args.FaviconUrl); if (!canProcess) { return; } /* GetFavicon is an extension method borrowed from John West. You can find it at http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2013/08/Use-Different-Shortcut-Icons-for-Different-Managed-Sites-with-the-Sitecore-ASPNET-CMS.aspx */ args.FaviconUrl = Context.Site.GetFavicon(); } public void TryGetFaviconControl(FaviconTryGetterArgs args) { Assert.ArgumentNotNull(args, "args"); if(string.IsNullOrWhiteSpace(args.FaviconUrl)) { return; } args.FaviconControl = CreateNewFaviconControl(args.FaviconUrl); } private static Control CreateNewFaviconControl(string faviconUrl) { Assert.ArgumentNotNullOrEmpty(faviconUrl, "faviconUrl"); HtmlLink link = new HtmlLink(); link.Attributes.Add("type", "image/x-icon"); link.Attributes.Add("rel", "icon"); link.Href = faviconUrl; return link; } } }
The TryGetFromStartItem method tries to get the favicon set on the favicon field on the start item — the name of the field is supplied via one of the processors defined in the configuration include file below — and sets it on the FaviconUrl property of the FaviconTryGetterArgs instance supplied by the caller.
If the field name for the field containing the favicon is not supplied, or there is something wrong with either the context site or the start item’s path, then the method does not finish executing.
The TryGetFromSite method is similar to what John had done in his post. It uses the same exact extension method John had used for getting the favicon off of a “favicon” attribute set on the context site’s node in the Web.config — I have omitted this extension method and its class since you can check it out in John’s post.
If a URL is set by either of the two methods discussed above, the TryGetFaviconControl method creates an instance of an HtmlLink System.Web.UI.HtmlControls.HtmlControl, sets the appropriate attributes for an html favicon link tag, and sets it in the FaviconControl property of the FaviconTryGetterArgs instance.
I assembled the methods above into a new getFavicon pipeline in the following configuration include file, and also set a fallback favicon for my local sandbox site’s configuration element:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <getFavicon> <processor type="Sitecore.Sandbox.Pipelines.GetFavicon.FaviconTryGetter, Sitecore.Sandbox" method="TryGetFromStartItem"> <FaviconFieldName>Favicon</FaviconFieldName> </processor> <processor type="Sitecore.Sandbox.Pipelines.GetFavicon.FaviconTryGetter, Sitecore.Sandbox" method="TryGetFromSite" /> <processor type="Sitecore.Sandbox.Pipelines.GetFavicon.FaviconTryGetter, Sitecore.Sandbox" method="TryGetFaviconControl" /> </getFavicon> </pipelines> <sites> <site name="website"> <patch:attribute name="favicon">/sitecore.ico</patch:attribute> </site> </sites> </sitecore> </configuration>
Just as John West had done in his post, I created a custom WebControl for rendering the favicon, albeit the following class invokes our new pipeline above to get the favicon ASP.NET control:
using System.Web.UI; using Sitecore.Pipelines; using Sitecore.Sandbox.Pipelines.GetFavicon; using Sitecore.Web.UI; namespace Sitecore.Sandbox.WebControls { public class Favicon : WebControl { protected override void DoRender(HtmlTextWriter output) { FaviconTryGetterArgs args = new FaviconTryGetterArgs(); CorePipeline.Run("getFavicon", args); if (args.FaviconControl != null) { args.FaviconControl.RenderControl(output); } } } }
If a favicon Control is supplied by our new getFavicon pipeline, the WebControl then delegates rendering responsibility to it.
I then defined an instance of the WebControl above in my default layout:
<%@ Register TagPrefix="sj" Namespace="Sitecore.Sharedsource.Web.UI.WebControls" Assembly="Sitecore.Sharedsource" %> ... <html> <head> ... <sj:Favicon runat="server" /> ...
For testing, I found a favicon generator website out on the internet — I won’t share this since it’s appeared to be a little suspect — and created a smiley face favicon. I set this on my start item, and published:
After clearing it out on my start item, and publishing, the fallback Sitecore favicon appears:
When you remove all favicons, none appear.
If you have any thoughts, suggestions, or comments on this, please share below.