Using ClientDependency Filters to manipulate HTML
On a recent Umbraco project, I needed to be able to manipulate the HTML contents before it was sent to the browser.
Typically, on Umbraco projects you'd do whatever you need do within Razor templating, but in my case, I had to do after the entire page markup was built. (I won't go into details, as the requirement is specific to my client project.)
My initial thought for the solution was using a Request.Filter
. I'd done this previously in my ASP.NET WebForms days - even open-sourced a packaged called Safe Mail Link that utilised this approach, (it would encode/protect any email addresses found in the markup). The guts of the Request.Filter
came from Rick Strahl's ResponseFilterStream
blog post.
Given the original ResponseFilterStream
code was originally posted over 10 years ago, (wow!) I wasn't sure whether the approach would work with latest Umbraco (v8 is ASP.NET MVC 5). Turns out, it does!
As I started to implement this approach, I recalled that the ClientDependency library (which ships with Umbraco) uses a HttpModule
to manipulate the HTML output to insert references to its bundled CSS & JS assets. So, thought it best to look at the source-code.
...and as it happens, ClientDependencyModule
has a lovely undocumented feature in there... its very own IFilter
interface. This enables you to piggyback ClientDependency's HttpModule
and manipulate the HTML with your own code!
After a small bit of reverse-engineering, (I know, I know, it's all open-source ... so I mean "researching"), I had a working prototype! Here's a reduced example (for Umbraco v8) ...
using System.Configuration;
using System.Web;
using ClientDependency.Core.Config;
using ClientDependency.Core.Module;
using Umbraco.Core;
using Umbraco.Web.Composing;
namespace Umbraco.Community.Web
{
public class UpdateHtmlExampleComposer : IUserComposer
{
public void Compose(Composition composition)
{
ClientDependencySettings.Instance.ConfigSection.Filters
.Add(new ProviderSettings(nameof(UpdateHtmlExampleFilter), typeof(UpdateHtmlExampleFilter).GetFullNameWithAssembly()));
}
}
public class UpdateHtmlExampleFilter : IFilter
{
public HttpContextBase CurrentContext { get; private set; }
public bool CanExecute()
{
return Current.UmbracoContext?.IsFrontEndUmbracoRequest == true;
}
public void SetHttpContext(HttpContextBase ctx) => CurrentContext = ctx;
public string UpdateOutputHtml(string html)
{
// TODO: Do your HTML updates in here!
// ------------------------------------
// o o/ o o/ o o/
// //| /| //| /| //| /|
// / \ / \ / \ / \ / \ / \
// ------------------------------------
return html
.Replace("Headless", "Heartcore");
}
// NOTE: If ClientDependency's MvcFilter is valid, then I'm cool with that.
public bool ValidateCurrentHandler() => true;
}
}
Examples of things you could do with this, could be: minify the HTML markup; inject external scripts before the closing </body>
tag, (e.g. instant.page); protect/encode email address (a la Safe Mail Link); or a super-quick way to rename a product across an entire website?