What I have done is created a base class that can be re-used for different filters you might need.
namespace MyNamespace { using System.IO; using System.Text; public class MemoryStreamFilter : MemoryStream { private readonly Stream _responseStream; private readonly Encoding _encoding; private readonly MemoryStream _buffer; public MemoryStreamFilter(Stream stream, Encoding encoding) { _buffer = new MemoryStream(); _responseStream = stream; _encoding = encoding; } public virtual string ProcessHtml(string html) { // This function will be overloaded in your derived class to do the actual work return html; } public override void Flush() { var html = _encoding.GetString(_buffer.ToArray()); html = ProcessHtml(html); var outBuffer = _encoding.GetBytes(html); _responseStream.Write(outBuffer, 0, outBuffer.Length); base.Flush(); } public override void Write(byte[] buffer, int offset, int count) { _buffer.Write(buffer, offset, count); } } }
This class is based on the standard MemoryStream class however it overrides the Write method to append the data to an internal buffer. When that buffer is flushed it converts the internal buffer to a string and calls ProcessHtml (which will do the required updates, whatever those may be), then writes the updated html to the response stream. Almost all of the methods here are going to be calling indirectly by the underlying MVC\ASP.net framework. When we ultimately hand this class over to the MVC framework as a filter it will stream the html in chunks via the Write method and when all of the html has been streamed the framework will call the Flush method, and this is where we update the html and write it to the response.
That's our base class, so let's leverage it to do something simple like replace "Hello" with "Goodbye".
namespace MyNamespace { using System.IO; using System.Text; public class HelloGoodbuyFilter : MemoryStreamFilter { public HelloGoodbuyFilter(Stream stream, Encoding encoding) : base(stream, encoding) { } public override string ProcessHtml(string html) { StringBuilder sb = new StringBuilder(html); sb.Replace("Hello", "Goodbye"); return sb.ToString(); } } }
Next we need to configure our filter so it is used, and we do this via the httpRequestProcessed pipeline so let's create a new pipeline step for our filter.
namespace MyNamespace { using Sitecore.Pipelines.HttpRequest; public class MyCustomFilter : HttpRequestProcessor { public override void Process(HttpRequestArgs args) { // Here we're checking a few things are true before deciding to use our filter. // We only want to use it when in normal mode and for our front-end site if (Sitecore.Context.Item == null || !Sitecore.Context.PageMode.IsNormal || Sitecore.Context.Site.Name != "website") { return; } // Create our filter var filter = new HelloGoodbuyFilter(args.Context.Response.Filter, args.Context.Response.ContentEncoding); // Tell the context to use it args.Context.Response.Filter = filter; } } }
Now we create a config path file in the "app_config/include" folder to plug in our custom step.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <sitecore> <pipelines> <httpRequestProcessed> <processor type="MyNamespace.MyCustomFilter, MyAssembly"/> </httpRequestProcessed> </pipelines> </sitecore> </configuration>
Here's the output from one of my components that is driven by a rich text box as it appears in preview mode.
<div class="text-group"> <div class="text-block"> <p>Hello, John.</p> </div> </div>
But in normal mode;
<div class="text-group"> <div class="text-block"> <p>Goodbye, John.</p> </div> </div>
No comments:
Post a Comment