Building a favicon module for Orchard
I built a little module for Orchard tonight and it involves a couple of interesting magic tricks so I thought I'd share.
The module is replacing the default Orchard favicon with whatever you configure in site settings:
I haven't created any specific storage for the icons and instead decided to use a simple convention for the already existing Media module. What you can see in the site settings in the picture above is a list of suggestions that is just a list of the icons that are found in the favicon subfolder of the Media module. In order to do that, I added a reference to Orchard.Media in my module's csproj and the corresponding dependency in the module's manifest.
That done, I can get a dependency on IMediaService injected into my site settings part driver and get the files' URLs from the favicon subfolder:
faviconSuggestions = new List<string>( _mediaService.GetMediaFiles("favicon") .Select(f =>
_mediaService.GetPublicUrl("favicon/" + f.Name)));
The really tricky part though is to replace the existing favicon with the one that the user configures. The default favicon is introduced by the Document.cshtml template that can be found in the fallback SafeMode theme:
RegisterLink(new LinkEntry { Type = "image/x-icon", Rel = "shortcut icon", Href = Url.Content(
"~/modules/orchard.themes/Content/orchard.ico")});
The registered link then gets rendered a little farther in the same template:
<head> <meta charset="utf-8" /> <title>@Html.Title()</title> @Display(Model.Head) </head>
This code defines a "Head" zone in the current shape, which happens to be Layout, for which Document is just a wrapper. Don't worry if you're a little lost, that's not really the important part, I'm only explaining how I tracked down where I needed to act.
This "Head" zone is populated by the ResourceFilter as follows:
head.Add(_shapeFactory.Metas()); head.Add(_shapeFactory.HeadLinks()); head.Add(_shapeFactory.StylesheetLinks()); head.Add(_shapeFactory.HeadScripts());
It should become clear from this that the right shape to modify here is HeadLinks. We just need an event that is triggered right before that shape gets rendered, the "Displaying" event. We can do that from a shape table provider:
public class FaviconShapes : IShapeTableProvider { public void Discover(ShapeTableBuilder builder) { builder.Describe("HeadLinks") .OnDisplaying(shapeDisplayingContext => { // Do stuff here. }); } }
Now all we need to do is get the current list of links and get any favicon from it:
var resourceManager = workContext.Resolve<IResourceManager>(); var links = resourceManager.GetRegisteredLinks(); var currentFavicon = links .Where(l => l.Rel == "shortcut icon" &&
l.Type == "image/x-icon") .FirstOrDefault();
And then if we found one we can just replace its Href property, or if there isn't one already we create it:
// Modify if found if (currentFavicon != default(LinkEntry)) { currentFavicon.Href = faviconUrl; } else { // Add the new one resourceManager.RegisterLink(new LinkEntry { Type = "image/x-icon", Rel = "shortcut icon", Href = faviconUrl }); }
And that's it. Share and enjoy!
The module (with full source code) can be downloaded from the Orchard Gallery:
http://orchardproject.net/gallery/Packages/Modules/Details/Vandelay-Favicon-1-0