Extending Sitecore Rich Text Preview CSS

The other Sitecore Kam wrote an excellent post titled User Specific or Multi Site Specific CSS styles in Sitecore Rich Text Editor a few months ago. I noticed one gap in what it did, however: changing the RTE configuration does not change the preview of the rich text shown in the Sitecore content editor interface.

This means that you’d see the site specific styles when in the RTE proper, but not when browsing the item in the content editor.

So here’s how to accomplish just that. The RTE preview is rendered by the sitecore/shell/Controls/Rich Text Editor/Preview.aspx page. Being a normal Web Form page, we can hack its Inherits attribute to something else in order to inject our custom CSS:

<%@ Page ... Inherits="MyLibrary.MultisiteRichTextEditorPreview, MyLibraryAssembly" %>

Then we have to create a class which inherits the default codebehind class, Sitecore.Shell.Controls.RADEditor.Preview:

using System;
using System.Web.UI;
using Sitecore.Shell.Controls.RADEditor;

namespace MyLibrary
{
    public class MultisiteRichTextEditorPreview : Preview
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            // replace the method call here with whatever you're using to resolve a custom RTE CSS path(s)
            // note that the query string parameter 'id' is the context item ID here just like in the EditorConfiguration Kamruz blogged about.
            var stylesheetPath = MultisiteRichTextEditorUtility.GetRichTextEditorCssPath();

            if (stylesheetPath != null) Stylesheets.Controls.Add(new LiteralControl(string.Format("<link href=\"{0}\" rel=\"stylesheet\">", stylesheetPath)));
        }
    }
}

Finally, we need to ‘improve’ our RTE stylesheet (this is optional, but excellent!):

* {
    font-family: "Comic Sans MS" !important;
    color: pink !important;
}

And then we have our newly enhanced rich text preview:

A Multiple Root Treelist Field

Recently I came across a need to allow selecting items for a TreeList field from several locations - specifically, we needed to be able to choose images OR videos to assign, and those were in different places in the content tree.

This has been done before, but there was altogether too much copied private method in that solution for my taste - and that it appears to not work with Sitecore 7.5 sealed the deal. To the hackmobile!

Turned out to not be too hard to reduce it to one file: Sitecore itself provides a MultiRootTreeList control that is used for rendering the insert data source item dialog (which supports multiple roots). With a little creative manipulation of the control tree, here we go:

And, the code (also as a Gist) - tested on Sitecore 7.2 Update-3 and Sitecore 8 Update-2:

using System;
using System.Linq;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Text;
using Sitecore.Web;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.WebControls;

namespace Kamsar.FieldTypes
{
    /// <summary>
    /// This field type is like a tree list, but you can specify more than one root item to select from (for example, videos or photos)
    /// The data source roots are specified using pipe delimiting just like regular Sitecore Query language
    /// </summary>
    public class MultiRootTreeList : TreeList
    {
        protected override void OnLoad(EventArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            base.OnLoad(args);

            if (!Sitecore.Context.ClientPage.IsEvent)
            {
                // find the existing TreeviewEx that the base OnLoad added, get a ref to its parent, and remove it from controls
                var existingTreeView = (TreeviewEx)WebUtil.FindControlOfType(this, typeof(TreeviewEx));
                var treeviewParent = existingTreeView.Parent;

                existingTreeView.Parent.Controls.Clear(); // remove stock treeviewex, we replace with multiroot

                // find the existing DataContext that the base OnLoad added, get a ref to its parent, and remove it from controls
                var dataContext = (DataContext)WebUtil.FindControlOfType(this, typeof(DataContext));
                var dataContextParent = dataContext.Parent;

                dataContextParent.Controls.Remove(dataContext); // remove stock datacontext, we parse our own

                // create our MultiRootTreeview to replace the TreeviewEx
                var impostor = new Sitecore.Web.UI.WebControls.MultiRootTreeview();
                impostor.ID = existingTreeView.ID;
                impostor.DblClick = existingTreeView.DblClick;
                impostor.Enabled = existingTreeView.Enabled;
                impostor.DisplayFieldName = existingTreeView.DisplayFieldName;

                // parse the data source and create appropriate data contexts out of it
                var dataContexts = ParseDataContexts(dataContext);

                impostor.DataContext = string.Join("|", dataContexts.Select(x => x.ID));
                foreach(var context in dataContexts) dataContextParent.Controls.Add(context);

                // inject our replaced control where the TreeviewEx originally was
                treeviewParent.Controls.Add(impostor);
            }
        }

        /// <summary>
        /// Parses multiple source roots into discrete data context controls (e.g. 'dataSource=/sitecore/content|/sitecore/media library')
        /// </summary>
        /// <param name="originalDataContext">The original data context the base control generated. We reuse some of its property values.</param>
        /// <returns></returns>
        protected virtual DataContext[] ParseDataContexts(DataContext originalDataContext)
        {
            return new ListString(DataSource).Select(x => CreateDataContext(originalDataContext, x)).ToArray();
        }

        /// <summary>
        /// Creates a DataContext control for a given Sitecore path data source
        /// </summary>
        protected virtual DataContext CreateDataContext(DataContext baseDataContext, string dataSource)
        {
            DataContext dataContext = new DataContext();
            dataContext.ID = GetUniqueID("D");
            dataContext.Filter = baseDataContext.Filter;
            dataContext.DataViewName = "Master";
            if (!string.IsNullOrEmpty(DatabaseName))
            {
                dataContext.Parameters = "databasename=" + DatabaseName;
            }
            dataContext.Root = dataSource;
            dataContext.Language = Language.Parse(ItemLanguage);

            return dataContext;
        }
    }
}

Have fun!

Blogging with Hexo - a Node.js detour

For the last couple years, I’ve run my blog from GitHub Pages using a static site generator, specifically Octopress. Static Site Generators are more or less a basic CMS for technical folks - you define your layout template(s) and then write content that inherit one of those layouts - say, a blog post. When you’ve authored your post you “bake” the site using the SSG tool, which takes the page layout and combines it with your content items and then spits out static HTML pages and assets. You then can upload the 100% static HTML site to a hosting service - such as GitHub Pages, Heroku, or Azure Websites.

Using a static site generator might seem quite limiting at first - after all, highly dynamic CMS like Sitecore would never work with HTML files and no database. So yes - you give up personalization. You give up any sort of server-side logic. You have to use JS-based commenting (e.g. Disqus, FB comments).

But you gain a lot as well by using such a tool. Ever hacked a static HTML site? Thought not. When was the last major WordPress security hole that demanded you to upgrade now? Yeah - last week. With a staticly deployed site you can also use the full branch and merge power of Git to keep a permanent history of your posts - and even accept pull requests. You’ll also never have to worry about backend performance - there is no backend. Authoring static sites can be done in powerful meta-languages, like writing posts in Markdown, CSS with SASS, and JS with Browserify. And finally, there are completely free hosting services for static sites - such as GitHub Pages - so it can cost nothing to host a static blog.

Enter Hexo

My previous Octopress blog suffered from an annoying problem: it was a huge pain to set up the generation environment on Windows, because it depended on Ruby, Python, Ruby DevTools, and a host of other very specifically versioned libraries. Not fun - in fact, I resorted to authoring all my posts on OS X. I started to look for something better.

It turns out there are just a few static site generators out there, in about every programming language known to humanity. I evaluated a number of them and settled on Hexo, which is written in Node. “But you’re a C# developer,” you say. In this case the C# options (like Pretzel) are significantly less mature - and I like that I can run Hexo on any platform because it’s JavaScript.

Setting up Hexo was very simple and took about 5 minutes. No really, the whole set of commands you need are on the front page of the Hexo site (well, except “install Node.js first”). I was able to import all my Octopress posts nearly unmodified, and tweak the base URL settings to keep my faux-wordpress URL scheme intact.

Then I wrote this post by simply entering hexo new post "Blogging with Hexo - a Node.js detour" at a command prompt, opening the file in Sublime Text, and writing my Markdown. I previewed it while writing using hexo server, which starts a HTTP server you can see the rendered page on. When I deploy it to the blog, all I’ll have to do is hexo deploy at a command prompt and all the git magic will be done by Hexo.

Neat, huh? And you can even read the source.

Synthesis 8.1 Released

Synthesis 8.1 has been released to NuGet. This is a minor feature release that also resolves an issue with the model provider caching in Synthesis 8.0.

DPI-aware image support

The new `@Html.DpiAwareImageFor()MVC helper method emits asrcset` along with the image that enables “retina images” served from the media library. To use it, you basically upload the image 2x (or 3x for the highest DPI devices) larger than it would normally display. Devices that support the higher resolution will automatically acquire the higher resolution version, and lower resolution devices will use the regular version.

Note that this is only for DPI-aware images. The more advanced forms of srcset and picture that enable viewport-based features (‘responsive images’) are not supported at this time. Last I checked not well supported by browsers either.

WebActivator removed as a dependency

Synthesis 8.0 depended on WebActivator to register its startup tasks. In 8.1, these have moved to the more Sitecorian initialize pipeline.

Improved config schema

The Synthesis.config file has been broken into three files: Synthesis.config (global model configuration), Synthesis.ControlPanel.config (enables the control panel page), and Synthesis.Startup.config (enables on-startup sync checks and regeneration). This makes it much easier to deploy Synthesis to a Content Delivery server, as you can simply delete all but Synthesis.config and be secure.

Upgrade Instructions

Synthesis 8.1 is a NuGet upgrade away. However to cover the new configuration changes, you do need to make a couple tweaks to an existing Synthesis.config.

  • remove <registerDefaultConfiguration value="true" /> (Synthesis 8.0 only)
  • remove the whole <synchronizationSettings> node in Synthesis.config (its values now live in Synthesis.Startup.config)
  • remove pipelines/httpRequestBegin in Synthesis.config (values now live in Synthesis.ControlPanel.config)
  • add default configuration registration to Synthesis.config <pipelines>:

    <initialize>
        <!-- REGISTER DEFAULT CONFIGURATION
            If this processor is registered, the configuration values below for providers are loaded into a Default Configuration.
            This enables Synthesis to work out of the box if you do not need multiple configurations.
    
            For multiple configurations you may leave this active if you want to keep the default configuration and add your own on top of it,
            or remove it to register only your own configurations. Configurations are registered by calling ProviderResolver.RegisterConfiguration()
            - e.g. in more initialize pipeline processors.
    
            NOTE: Do not register multiple configurations that contain overlapping templates.
        -->
        <processor type="Synthesis.Pipelines.Initialize.RegisterDefaultConfiguration, Synthesis"/>
    </initialize>
    
  • if using the optimizeCompilations setting to speed up Sitecore 8 startup time, turn it off temporarily after the upgrade or clear your temporary asp.net files folder, because the helper structure changed slightly during the upgrade and the cached razor files will be invalid.

Announcing Synthesis 8.0

I am happy to announce the availability of Synthesis 8.0. Synthesis is an object mapping framework for Sitecore that takes advantage of integrating the code generation with the mapping framework. This enables very high speed object mapping, LINQ-to-indexes support, model sync checking, and deep interface support for template inheritance and unit testability.

What’s new in Synthesis 8.0

Sitecore MVC Support

There are relatively few reasons to stick with Web Forms any longer for Sitecore development. Synthesis 8 introduces full-scale Sitecore MVC support, including:

  • A high-speed model provider for view renderings: you can use @model IMySynthesisTypeItem transparently on view renderings
  • HTML helpers designed to make it easy to work with Synthesis models and enable Experience Editor (e.g. `@Html.ImageFor(x=>x.MyImageField)`)
  • Invalid model types on view renderings are trapped and contained to only the rendering involved, preventing the whole page from exploding due to one rendering failing. Messaging is shown to preview and editing users to show the failed rendering.
  • Diagnostic HTML comments are injected around renderings when dynamic debug compilation is enabled, showing the start and end of each rendering, its name, render time, and output cache diagnostics.

The functionality of the Synthesis.Mvc package is largely similar to Blade combined with Synthesis.Blade except that Blade is for Web Forms and Synthesis.Mvc is for Sitecore MVC. The Synthesis.Mvc HTML helpers are fully compatible with Synthesis.Blade’s.

Multiple configurations

Synthesis now supports more than one model configuration. This enables, for example, generating a separate model per-site for a multi-site installation of Sitecore. The additional configurations are registered similarly to MVC areas, using either WebActivator, the <initialize> pipeline, or Global.asax. There is some basic documentation available.

Configurations may also be friends, which enables sharing template classes across configurations that are aware of each other. For example, if you have a ‘shared’ set of templates that multiple sites’ templates derive from, making the shared model a ‘friend’ of your site model will enable your site model to derive from the shared model’s interfaces.

New control panel

Synthesis 8 uses a control panel that is hooked from the httpRequestBegin pipeline instead of using a HTTP handler and HTTP module. This enables Synthesis to require no web.config changes at all to install or remove. The control panel is multiple-configuration aware.

This control panel uses a configurable activation URL to allow you to put it where you want it. The default is /synthesis.aspx.

Installation and Upgrade

Synthesis 8 is available from NuGet right now. Initial installation is as simple as installing the Synthesis NuGet package (and, if you want MVC support the Synthesis.Mvc package). There is a README that opens after installation that helps you get started and configure it.

Upgrading does require merging the current Synthesis.config with your existing one. A few settings have been added or modified in this version to support the control panel and multiple configurations. You will also need to remove the Synthesis HTTP handler and HTTP module from web.config if you have them registered. Otherwise upgrade is as simple as upgrading the NuGet package and, if appropriate, installing the MVC package.

Synthesis 8.0 is designed for Sitecore 7.2 and later due to content search API versioning, and was primarily developed and tested on Sitecore 8.0 Update-1.

Under the hood changes

  • Modular generator: The code generator has been refactored and improved into a two-stage design where metadata (e.g. template hierarchy) is generated first, and passed to a code generator. This enables plugging in custom code generators if desired (if you’re feeling brave, it should be completely possible to say emit a Glass model from Synthesis’ generator). Metadata generation also enables multiple configurations to refer to each other.
  • Sitecore-isolated generation classes: All providers involved in generation no longer have any direct hooks to the Sitecore API. This hypothetically enables non-Sitecore database datasources for generation - for example, .item files on disk.
  • The default TypeListProvider allows for wildcards when specifying assemblies to scan for model types. Useful for cases where you have an assembly per site and don’t want to register them all individually.
  • The NuGet package now includes a README file that provides some basic directions for getting started with Synthesis, as this has been a previously lacking area of the documentation
  • Generated Synthesis models are now less prone to causing SCM merge conflicts. The Synthesis version stamp has been reduced to x.x instead of x.x.x.x on attributes, and the ‘generated by .net framework version x’ is removed from the header, which could cause problems if one developer had say .18072 and the other .17345; producing constant pointless model changes in the repository.
  • IStandardTemplateItem now has a Children property, which is an alias to .Axes.GetChildren(), to make it feel more at home for those used to the Item API.

Bug fixes

  • There was a problem with the way the template inheritance cycle rejection was implemented in 7.3.3 that caused multilevel inheritance on templates who had fields named the same as their template would generate invalid models. This has been fixed.
  • Setting the value of a HyperlinkField.Href with an external URL value (e.g. http://google.com) would cause it to save the link as an internal link type and Sitecore to flag it as a broken link. These are now correctly set as link type external.
  • When using LINQ-to-indexes and calling the GetResults() extension method, an exception will no longer be thrown. The exception is due to some misbehaving private reflection deep in Sitecore, and the workaround is even more private reflection. The authorities have been notified ;)

Sitecore 8 Experience Editor Performance Optimization

Sitecore 8 is pretty nice, but not without its share of rough edges. One persistently annoying issue I came across is that after you compile your solution in Visual Studio, it is glacially slow to start up in Experience Editor (formerly Page Editor) mode. Given that most sites built in modern Sitecore fashion are using component based architecture that is Experience Editor centric, this is a big drag on development time.

In this post I’ll outline the story so far, and some tweaks you can perform to improve the performance of Sitecore 8 after a compilation.

As a baseline, on my i7-3770k, 32GB RAM, and all-SSD system EE 8.0 takes 1:30 to startup after a compile. Yeah, a minute and a half. Sitecore 6.5 will start in the same situation in 0:21.

Option 1: Precompilation

John West and Kevin Obee noted that I should try disabling the SPEAK precompilation features. These are in the <initialize> pipeline and registered in Sitecore.Speak.config and ContentTesting\Sitecore.ContentTesting.config. If you comment these out, you trade having the SPEAK interfaces come up slower the first time (because they are not precompiled) for faster initial startup time (because you skip precompiling).

With those two precompilers commented out, my startup time dropped massively to 0:41. Still twice as slow as Sitecore 7.2, but oh so worth it when you’re compiling all day.

Option 2: Optimize Compilation

Still unsatisfied with spending 20 seconds compiling unchanged .cshtml SPEAK views every time I compile my application, I started looking around into the actual compilation process. An interesting facet of this issue is that it’s not triggered by simply restarting the app pool. You must touch an assembly in the bin folder to trigger the slow startup.

A clue came in the form of this StackOverflow answer from @pbering. He mentioned enabling the optimizeCompilations setting in the web.config. What does this do? Well, off to MSDN. If you read this document, it makes it clear that when you change a top-level file (which includes anything in bin) it invalidates all dynamically compiled assets because a dependency of their compilation has changed. Razor files, such as what SPEAK uses, are dynamically compiled - .NET parses them, compiles them to a C# class, and stores them off in the Temporary ASP.NET Files folder.

So essentially what happens is that Sitecore 8 depends on a WHOLE LOT of dynamically compiled SPEAK renderings. When you start it up the first time, ASP.NET compiles them and stores them in its compilation cache. As long as you don’t change any dependencies - e.g. dlls, global.asax - .NET can update the cache selectively when the cshtml file is changed, and it’s fast. But as soon as a dependency is changed - e.g. your site dll - .NET throws out the whole site’s cache and compiles it all again, just to be safe. With Sitecore 8, this takes 20-60 seconds at least.

You can instruct .NET to ignore dependency changes and only recompile dynamically compiled files when the file itself is modified. This has some potential dangers if you modify a dependency without changing the dynamically compiled file and make a breaking change; see the MSDN article for details of the things to be aware of if you enable optimization.

The good news is, if you turn on <compilation optimizeCompilations="true"> in your web.config, there is no longer any SPEAK recompiling on startup! Even better, it starts up in 0:13, beating 7.2 by 8 seconds!

That said, be aware that there are significant issues to be aware of when enabling optimizeCompilations. Read the MSDN article. I have not deeply tested using this setting, other than it removes the startup speed issue. If you run into problems, stopping IIS and clearing the Temporary ASP.NET files folder should clear it up. Rebuild may not help.

Option 3: Disable the SPEAK Experience Editor

A separate ticket on Sitecore support (relating to EE in Sitecore MVC) rustled up this other possible solution: disable the SPEAK-based Experience Editor, and go back to the Sheer one from previous Sitecore versions. This removes all cshtml compilation issues from the equation, and feels slightly faster in the browser to me. It has a less-polished UI appearance however, likely due to it not being a priority to reskin :)

To disable the SPEAK EE, open App_Config/Include/Sitecore.MvcExperienceEditor.config. Locate the comment around like 33 regarding ‘uncomment the page extenders below to switch to sheer’ and follow the directions in the comment.

Hope that helps!

Synthesis 7.3 released

Synthesis 7.3 has been released to NuGet and GitHub.

The big new feature in 7.3 is the Synthesis.Mvc package, which adds a Synthesis-compatible Sitecore MVC model resolution mechanism. This enables you to create View Renderings that transparently can use a Synthesis type as a model without any additional work:

@model IFooItem

<h1>@Html.TextFor(x => x.SomeSitecoreField)</h1>

The Mvc library also includes:

  • Helpers to make it simple to render Synthesis fields (like TextFor above). If you’ve ever used Blade these helpers are essentially identical.
  • Rendering diagnostics, also similar to Blade, which adds HTML comments around renderings with useful data - like whether it was output cached - whenever the site is run with debug compilation enabled. A great tool if you work with frontend devs who never know what rendering markup came from.
  • Altered handling of invalid rendering model types. By default, an exception that takes the whole page down is thrown if a view declares an @model and receives something other than that type. This can cause issues if your datasource item is ever an invalid item ID or something similar. Synthesis.Mvc changes this behavior by simply hiding renderings with invalid datasource types; if in preview or edit mode a message is shown in place of the rendering explaining why it’s hidden.

    Installing Synthesis.Mvc is easy: Just add the NuGet package to your web project. It consists of a single assembly in /bin and the Synthesis.Mvc.config file that registers pipelines in /App_Config/Include.

Bug fixes

Several bugs were also fixed in Synthesis 7.3:

  • The mechanism Synthesis uses to generate media URLs was simplified as Sitecore 6.5 is no longer supported. This makes the media URLs on image and file fields valid in all cases, even when forcing media to have the server in the URL. Thanks to Jeremy Clifton and Robert Pate for the PR.
  • The Synthesis-wrapped version of Axes.GetPreviousSibling() incorrectly called GetNextSibling() internally. Thanks to @ullmark for the PR
  • If two templates formed an inheritance cycle, generating Synthesis would cause a stack overflow. This is no longer the case, however it will still emit code that won’t compile (as the interfaces will create a cycle in C#). Thanks to @GreyGhostStudio for the issue report.
  • The constructor of TestNumericField incorrectly took an int? when it should have been decimal?. Thanks to Robert Hardy for the PR.

Upgrading to 7.3

The configuration format for 7.3 has no changes, so upgrading is as simple as upgrading your NuGet packages. The Mvc support is a new separate NuGet package that would need to be installed.

Quick note: Sitecore MVC partials

Here’s a bit of an obtuse trick that lets you use `@Html.Partial()` effectively on Sitecore MVC View renderings without specifying the full path to the partial you want to render. You might think that regular MVC syntax would work here:

@Html.Partial("_MyPartial")

But you’d be wrong. Instead, you’ll get an error that the partial could not be found, and the perplexing message that it tried to search [path-to-views]/Sitecore/_MyPartial.cshtml. Where’d that “/sitecore” come from? To understand this, a bit of background on how ASP.NET MVC resolves partials is in order.

The view resolution convention is based on the controller name that executes the view, not the view’s physical location. This makes sense in a pure ASP.NET MVC environment where every view is the result of a controller. However in a View Rendering, there’s just the cshtml. Except that in the background, Sitecore executes the View Rendering using a shim controller - a shim that goes by SitecoreController. This is where the /Views/Sitecore search path comes from.

So what’s the trick? Well, as long as all your views are in a MVC-like hierarchy, you can refer to the partial using the parent path:

@Html.Partial("../ViewParentFolder/_MyPartial")

This jumps out of the controller rendering folder (always Sitecore) and lets you specify the parent folder directly instead, without resorting to a full absolute path which - especially if you use areas or moved views around later - could break.

In case you’re wondering why I’m using native partials instead of `@Html.Sitecore().Rendering()or@Html.Sitecore().ViewRendering()` and such, these are static components of a MVC layout that are shared between multiple layouts - so it doesn’t make much sense to also register them as dynamic renderings and take the performance hit of that lookup.

Synthesis 7.2.1 released

Synthesis 7.2.1 has been released to NuGet.

This release includes:

  • a bug fix from @Gobiner for internal links with query strings,
  • a bug fix for content search index issues when the master database has been removed, reported by @GreyGhostStudio
  • the Add<T>() method of Synthesis objects now supports interfaces instead of just concrete types. A suggestion from @ullmark

In addition, Synthesis 7.2.1 has been tested on the Sitecore 8 technical preview and operates without issue.

Have fun!

Automatic Sitecore image optimization with Dianoga

Have you ever run Google PageSpeed or WebPagetest against a Sitecore site and been disappointed with the unoptimized images coming from your Sitecore media library? When I did, I was seeing claims that I could reduce the size of many of my images by 10-30%, simply by removing unnecessary image data and optimizing the image encoding. The tools to accomplish image optimization, such as jpegtran or PNGOptimizer, operate in a lossless fashion - in other words, your images lose zero fidelity but become smaller. On a big website, any reduction in data transfer is a big cost savings on bandwidth as well as a UX improvement due to improved load times.

With this in mind I set out to make a tool to automatically optimize the output of the Sitecore media library, and Dianoga was born. Dianoga sits in the trash compactor getMediaStream pipeline, which Sitecore uses to transform media items before it writes them to its disk cache. When an image gets processed, we complete the processing by transparently optimizing it before it goes in the cache. No changes are ever made to the actual media item in the database, and you never have to explicitly request optimization.

There are some other image optimization modules out there for Sitecore, so how is this one different? First, this one is automatic (no clicking a button in the content editor, or forgetting to). Second, this one optimizes as the media is served to the client, which means that if you’re using Sitecore’s image resizing functionality (which is perfect for modern responsive techniques like srcset and Adaptive Images without tons of work) each size of the image is individually optimized - which is important because the act of resizing the image deoptimizes it.

Sound like fun? Free bandwidth savings for no work other than a really difficult installation process? Just kidding - installation is as simple as a zero-configuration NuGet package that doesn’t even touch your Sitecore databases. You might want to delete App_Data/MediaCache after install so that media is recached with optimizations, but that’s all there is to it.

Find Dianoga on NuGet (Sitecore 7+), or you can get the source from GitHub (source works for Sitecore 6.x). There is also a readme on GitHub with additional details about logging, troubleshooting, and such.