Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy


Add to Technorati Favorites Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

Building a favicon module for Orchard

(c) Bertrand Le Roy 2011I 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:

The default favicon The new favicon
The site settings for favicon

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

Comments

Attila said:

Wow :-) It's a very great example of how modular Orchard is!

(And from this very simple example I get how hard to document such extensibility points :-))

# January 18, 2011 4:12 AM

Raymond de Jong said:

A nice feature i'll be sure to use some day. I'm glad to see it in the galery instead of a NuGet package which i always open with my favorite zip tool.

# January 18, 2011 7:24 AM

Wictor said:

Nice module!

How about giving a detailed instruction on how to "bundle" this module into a Orchard Azure package? Can't just get it to work :)

# January 18, 2011 8:37 AM

Bertrand Le Roy said:

@Wictor: did you read this? orchardproject.net/.../Deploying-Orchard-to-Windows-Azure.ashx

What exactly doesn't work? Feel free to e-mail me at bleroy at you know where.

# January 18, 2011 3:18 PM

jason_palmer said:

What about seo ?

Wordpress sucks on SEO so to kill wordpress with orchard it needs ace seo.

# January 22, 2011 9:32 AM

Bertrand Le Roy said:

@Jason: what about it? People put many different sets of expectations under SEO. What's yours?

I published a meta description and keywords module there: orchardproject.net/.../SEO

Nowadays, SEO is above all about offering relevant contents in clean markup, under friendly URLs. And have other well referenced sites link to you. There is only so much the platform can do for you.

# January 23, 2011 12:40 AM

Russ Cam said:

Hey Bertrand: nice module, but unfortunately I can't enable it :( It installed fine from the gallery but when I enable in Configuration|Features, I get the following server error:

Cannot insert the value NULL into column 'Id', table '[database name]__Settings_ShellFeatureRecord'; column does not allow nulls. INSERT fails.

The statement has been terminated.

Any ideas?

# January 25, 2011 4:06 AM

Bertrand Le Roy said:

@Russ: none at all. Can you install other modules fine?

# January 25, 2011 2:46 PM

Russ Cam said:

@Bertrand: D'oh! it appears it's an issue to install any modules. I copied the database via an SSIS package to a provider, so I'll try again by installing from a backup.

Many Thanks

# January 26, 2011 5:42 PM

Jeff said:

That seems like a lot of work to add a favicon...I mean all of that work to add something like this to a page:

<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />

It has the smell of an over-engineered abstraction.

# February 10, 2011 4:31 PM

Bertrand Le Roy said:

@Jeff: you can go ahead and do that in the document.cshtml of your theme and you'd be done. This is in response to several users who wanted more than that: admin UI to change the icon and a solution that works across all themes without requiring the change of a template file.

# February 10, 2011 4:39 PM

Jeff said:

@Bertrand - I gotcha programmer free maintenance. I stand corrected :)

# February 11, 2011 12:24 PM

Bertrand Le Roy said:

@Jeff: oh, and multi-tenancy is another reason to use this: you want each tenant to be able to choose their icon.

# February 11, 2011 2:10 PM

Mathias said:

Hello Bertrand,

I'm trying to use your FavIcon module in a multi tenant orchard site. It works in the primary tenant, but not in the other tenant. I have enabled it for the sub tenant as well, but it does not show up in the settings page of the tenant. Do you have any ideas why it fails?

# March 25, 2011 9:09 AM

Mathias said:

It seems that the table that stores the favicon settings was only created in the database of the main tenant, but not in the db of the sub tenant. When I manually create the table, the module works for the sub tenant as well.

# March 25, 2011 9:18 AM

Bertrand Le Roy said:

@Mathias: weird. I've filed a bug to track this: orchard.codeplex.com/.../17558

# March 25, 2011 8:21 PM

nellbryant said:

This one worked for me. Thanks Bertrand!

# April 20, 2011 3:32 AM

Sean said:

Hi Bertrand,

I have tried adding code like this to a theme - I added a class like your FaviconShapes class but the Discover method never gets called - what lets the Core know to call my IShapeTableProvider?

Thanks!

Sean

# November 23, 2011 2:39 AM

Bertrand Le Roy said:

@Sean: That's because you need a project in your theme for code to get picked up.

# November 23, 2011 8:17 PM

Sean said:

I do have my theme in a project. Is that all, if I just have the theme in a project and a class that implements IShapeTableProvider then it should just work?

# November 24, 2011 10:36 PM

Bertrand Le Roy said:

@Sean: to be clear, it's not that you need your theme in a project, it's that your theme must have a csproj file with the same name as its folder. Then if that project file references cs files, they will get compiled. So yes.

# December 4, 2011 9:32 PM

Sarkie said:

Would it be possible to have a route for

/favicon.ico

For old browsers that assume it'll be in the route? Getting a few 404's for them looking for that file. I'm currently adding a url rewrite.

# January 17, 2012 7:54 AM

Bertrand Le Roy said:

@Sarkie: please file a bug for that on vandelay.codeplex.com/.../Create

# January 17, 2012 3:07 PM