I'd like to recommend the following code. It works perfectly (I'm using it on several websites) and its simpler than @David's version:
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
public class WhitespaceStrip : ActionFilterAttribute {
public override void OnActionExecuting(
ActionExecutingContext Context) {
try {
Context.HttpContext.Response.Filter = new WhitespaceFilter();
} catch (Exception) {
// Ignore
};
}
}
public class WhitespaceFilter : MemoryStream {
private HttpResponse Response = HttpContext.Current.Response;
private Stream Filter = null;
private string Source = string.Empty;
private string[] ContentTypes = new string[1] {
"text/html"
};
public WhitespaceFilter() {
this.Filter = this.Response.Filter;
}
public override void Write(
byte[] Buffer,
int Offset,
int Count) {
this.Source = Encoding.UTF8.GetString(Buffer);
if (this.ContentTypes.Contains(this.Response.ContentType)) {
this.Response.ContentEncoding = Encoding.UTF8;
this.Source = new Regex("(<pre>[^<>]*(((?<Open><)[^<>]*)+((?<Close-Open>>)[^<>]*)+)*(?(Open)(?!))</pre>)|\\s\\s+|[\\t\\n\\r]", RegexOptions.Compiled | RegexOptions.Singleline).Replace(this.Source, "$1");
this.Source = new Regex("<!--.*?-->", RegexOptions.Compiled | RegexOptions.Singleline).Replace(this.Source, string.Empty);
this.Filter.Write(Encoding.UTF8.GetBytes(this.Source), Offset, Encoding.UTF8.GetByteCount(this.Source));
} else {
this.Filter.Write(Encoding.UTF8.GetBytes(this.Source), Offset, Encoding.UTF8.GetByteCount(this.Source));
};
}
}
UPDATE
@Omu, just because it irritated me when you said it was "6x" slower, I set out to see if you're right or not. I ended up re-writing the filter and cleaning it up a bit, and then I ran some tests where I looped a table 10,000 to generate some white space and see how the filters work. When all was said and done, I saw no difference between the two regular expressions at all.
Now, if you're implying that the way the expressions work differently and that mine is going to be slower, than maybe there's some truth to that, but for you to see any differences you'll have to push out more than 1 MB sized HTML pages... That I hope is not what you're doing.
Furthermore, my expression preserves white space within <pre>
elements...
All of that being said, here's my revised version:
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
internal class WhitespaceStripAttribute : ActionFilterAttribute {
public override void OnActionExecuted(
ActionExecutedContext ActionExecutedContext) {
ActionExecutedContext.HttpContext.Response.Filter = new WhitespaceStream(ActionExecutedContext.HttpContext);
}
}
internal class WhitespaceStream : MemoryStream {
private readonly HttpContextBase HttpContext = null;
private readonly Stream FilterStream = null;
private readonly string[] ContentTypes = new string[1] {
"text/html"
};
private static Regex WhitespaceRegex = new Regex("(<pre>[^<>]*(((?<Open><)[^<>]*)+((?<Close-Open>>)[^<>]*)+)*(?(Open)(?!))</pre>)|\\s\\s+|[\\t\\n\\r]", RegexOptions.Singleline | RegexOptions.Compiled);
private static Regex CommentsRegex = new Regex("<!--.*?-->", RegexOptions.Singleline | RegexOptions.Compiled);
public WhitespaceStream(
HttpContextBase HttpContext) {
this.HttpContext = HttpContext;
this.FilterStream = HttpContext.Response.Filter;
}
public override void Write(
byte[] Buffer,
int Offset,
int Count) {
string Source = Encoding.UTF8.GetString(Buffer);
if (this.ContentTypes.Any(
ct =>
(ct == this.HttpContext.Response.ContentType))) {
this.HttpContext.Response.ContentEncoding = Encoding.UTF8;
Source = WhitespaceRegex.Replace(Source, "$1");
Source = CommentsRegex.Replace(Source, string.Empty);
};
this.FilterStream.Write(Encoding.UTF8.GetBytes(Source), Offset, Encoding.UTF8.GetByteCount(Source));
}
}