Replace Content

Created: 28 Sep 2015, last update: 24 Jun 2017

Sitecore replace content pipeline process

In .NET / IIS you can create a HttpModule for replacing / modifying the HTML output for example a Minify HTML HttpModule this is working for WebForms and MVC. In Sitecore you can also use this, but better to use the httpRequestProcessed pipeline, this is configured inside the Sitecore tag and so on you can patch it from the include folder.

Cached content in the Sitecore HTML cache
Like a http module, this also applies to a httpRequestProcessed pipeline module the process runs after the page is rendered and all cached sublayout are included, so a replace is not cached, but the replace is done on the entire page, including Sitecore HTML cached (sub) layouts.

Performance
The process runs each request therefore it must be fast. Avoid locks, if you need a lock hold it short. Do preferably not do a database queries, Sitecore queries, if you have to do anyway, perhaps you can cache the query for x minutes so you do not have to run the heavy part with every request. Under normal circumstances you module process time should below one millisecond. Typically the time to render a page, less than 100 ms is fast, more than one seconds slowly. Thus, there is a certain degree of performance impact but which is incalculable.

Applications to replace content.
Http Modules are used for minifying html, CDN solutions to modify URLs. or to replace dynamic content think about stock data or interest rates.

Include file
Place this file below the App_Config\Include folder with a .config extension.

<!--
  Purpose: This include file adds a content replacer to the pipiline.
-->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
  	<pipelines>
  		<httpRequestProcessed>
  			<processor type="Mirabeau.Sitecore.Pipelines.TestReplaceProcessor, Mirabeau.Sitecore" />
  		</httpRequestProcessed>
  	</pipelines>
  </sitecore>
</configuration>

 

Example code
Almost equal to an http module.

#region Using
using System;
using System.IO;
using Sitecore.Pipelines.HttpRequest;

#endregion

namespace Mirabeau.Sitecore.Pipelines
{

    public class TestReplaceProcessor : HttpRequestProcessor
    {

        public override void Process(HttpRequestArgs args)
        {
            //only Sitecore content items, and not in page editor. (skip media, e.t.c)
            if (global::Sitecore.Context.Item != null && !global::Sitecore.Context.PageMode.IsPageEditor)
            {
                //also skip the /news site section... example.
                if (!args.Context.Request.RawUrl.ToLower().StartsWith("/news"))
                {
                    args.Context.Response.Filter = new ReplaceXXXFilter(args.Context.Response.Filter);
                }
            }
        }

        #region Stream filter

        public class ReplaceXXXFilter : Stream
        {

            public ReplaceXXXFilter(Stream sink)
            {
                _sink = sink;
            }

            private Stream _sink;

            #region Properites

            public override bool CanRead
            {
                get { return true; }
            }

            public override bool CanSeek
            {
                get { return true; }
            }

            public override bool CanWrite
            {
                get { return true; }
            }

            public override void Flush()
            {
                _sink.Flush();
            }

            public override long Length
            {
                get { return 0; }
            }

            private long _position;

            public override long Position
            {
                get { return _position; }
                set { _position = value; }
            }

            #endregion

            #region Methods

            public override int Read(byte[] buffer, int offset, int count)
            {
                return _sink.Read(buffer, offset, count);
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                return _sink.Seek(offset, origin);
            }

            public override void SetLength(long value)
            {
                _sink.SetLength(value);
            }

            public override void Close()
            {
                _sink.Close();
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                byte[] data = new byte[count];
                Buffer.BlockCopy(buffer, offset, data, 0, count);
                string html = System.Text.Encoding.Default.GetString(buffer);

                //The Replace Example
                html = html.Replace("XXX", "YYY");

                byte[] outdata = System.Text.Encoding.Default.GetBytes(html);
                _sink.Write(outdata, 0, outdata.GetLength(0));
            }

            #endregion

        }

        #endregion

    }
}

The Code
The pipeline is also hit by other requests like "/sitecore" cms urls or media requests, often you do not want to replace in page edit mode. Depend on your wishes you need to filter.

With the check Sitecore.Context.Item != null you can check of it is a valid website request for you, and filter the media / sitecore urls

Using the httpRequestBegin instead of the httpRequestProcessed pipeline
It is also possible to use the httpRequestBegin pipeline as a alternative for the httpRequestProcessed pipeline, with the same code. There are some differences for example the Sitecore media urls are not in the httpRequestBegin pipeline, and you should place your replace content pipeline at the end.

<httpRequestBegin>
<processor type="YourNameSpace.ContentReplaceProcess, YourDLL" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel']"/>