非常に大きな値のHTMLテーブル(> 20MB)を生成しているMVCアプリケーションビューがあります。
圧縮フィルターを使用してコントローラーのビューを圧縮しています
internal class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding))
return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("GZIP"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.Contains("DEFLATE"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}
圧縮フィルターを実行する前に、ビューで生成された(かなり大量の)冗長な空白を削除する方法もありますか(圧縮のワークロードとサイズを削減するため)?
編集: 以下のWompが提案するWhiteSpaceFilterテクニックを使用して動作させました。
興味深いことに、Firebugによって分析された結果は次のとおりです。
1)圧縮なし、空白ストリップなし-21MB、2.59分
2)GZIP圧縮あり、空白ストリップなし-2MB、17.59秒
3)GZIP圧縮、空白ストリップ-558kB、12.77s
それだけの価値は確かにあります。
この男 は、正規表現を介してバイトの高速ブロックコピーを実行し、スペースのブロブを取り除くだけの、すっきりとした小さな空白コンパクターを作成しました。彼はそれをhttpモジュールとして作成しましたが、7行の主力コードを取り出して、関数に組み込むことができます。
@wompはすでにそれを行うための良い方法を提案していますが、そのモジュールはかなり時代遅れです。私はそれを使用していますが、それは最適な方法ではないことがわかりました。これが私が尋ねた質問です:
Html全体から空白を削除しますが、正規表現を使用してpre内を削除します
これが私がそれをする方法です:
public class RemoveWhitespacesAttribute : ActionFilterAttribute {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var response = filterContext.HttpContext.Response;
//Temp fix. I am not sure what causes this but ContentType is coming as text/html
if (filterContext.HttpContext.Request.RawUrl != "/sitemap.xml") {
if (response.ContentType == "text/html" && response.Filter != null) {
response.Filter = new HelperClass(response.Filter);
}
}
}
private class HelperClass : Stream {
private System.IO.Stream Base;
public HelperClass(System.IO.Stream ResponseStream) {
if (ResponseStream == null)
throw new ArgumentNullException("ResponseStream");
this.Base = ResponseStream;
}
StringBuilder s = new StringBuilder();
public override void Write(byte[] buffer, int offset, int count) {
string HTML = Encoding.UTF8.GetString(buffer, offset, count);
//Thanks to Qtax
//https://stackoverflow.com/questions/8762993/remove-white-space-from-entire-html-but-inside-pre-with-regular-expressions
Regex reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");
HTML = reg.Replace(HTML, string.Empty);
buffer = System.Text.Encoding.UTF8.GetBytes(HTML);
this.Base.Write(buffer, 0, buffer.Length);
}
#region Other Members
public override int Read(byte[] buffer, int offset, int count) {
throw new NotSupportedException();
}
public override bool CanRead{ get { return false; } }
public override bool CanSeek{ get { return false; } }
public override bool CanWrite{ get { return true; } }
public override long Length{ get { throw new NotSupportedException(); } }
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override void Flush() {
Base.Flush();
}
public override long Seek(long offset, SeekOrigin Origin) {
throw new NotSupportedException();
}
public override void SetLength(long value) {
throw new NotSupportedException();
}
#endregion
}
}
Razorを拡張することで、空白を削除できますコンパイル時。これにより、生成されたHTMLから空白を削除するという(私の測定では非常に重要な)実行時のヒットが排除されます。ヒットは、StackOverflowにあるRegExベースのコードを使用して100KBのドキュメントをトリミングするハイエンドi7で88msにもなります。
以下に、MVC3およびMVC4のコンパイル時ソリューションの実装を示します。
解決策はで説明されています
http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/
(ただし、ブログ投稿のコードはMVC 3のみを対象としているため、GitHubコードまたはNuGet DLLを使用してください)。
#region Stream filter
class StringFilterStream : Stream
{
private Stream _sink;
private Func<string, string> _filter;
public StringFilterStream(Stream sink, Func<string, string> filter) {
_sink = sink;
_filter = filter;
}
#region Mixin Properties/Methods
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; }
}
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();
}
#endregion
public override void Write(byte[] buffer, int offset, int count) {
// intercept the data and convert to string
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string s = Encoding.Default.GetString(buffer);
// apply the filter
s = _filter(s);
// write the data back to stream
byte[] outdata = Encoding.Default.GetBytes(s);
_sink.Write(outdata, 0, outdata.GetLength(0));
}
}
#endregion
public enum WebWhitespaceFilterContentType
{
Xml = 0, Css = 1, Javascript = 2
}
public class WebWhitespaceFilterAttribute : ActionFilterAttribute
{
private WebWhitespaceFilterContentType _contentType;
public WebWhitespaceFilterAttribute() {
_contentType = WebWhitespaceFilterContentType.Xml;
}
public WebWhitespaceFilterAttribute(WebWhitespaceFilterContentType contentType) {
_contentType = contentType;
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
switch (_contentType) {
case WebWhitespaceFilterContentType.Xml:
response.Filter = new StringFilterStream(response.Filter, s => {
s = Regex.Replace(s, @"\s+", " ");
s = Regex.Replace(s, @"\s*\n\s*", "\n");
s = Regex.Replace(s, @"\s*\>\s*\<\s*", "><");
// single-line doctype must be preserved
var firstEndBracketPosition = s.IndexOf(">");
if (firstEndBracketPosition >= 0) {
s = s.Remove(firstEndBracketPosition, 1);
s = s.Insert(firstEndBracketPosition, ">\n");
}
return s;
});
break;
case WebWhitespaceFilterContentType.Css:
case WebWhitespaceFilterContentType.Javascript:
response.Filter = new StringFilterStream(response.Filter, s => {
s = Regex.Replace(s, @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", "");
s = Regex.Replace(s, @"\s+", " ");
s = Regex.Replace(s, @"\s*{\s*", "{");
s = Regex.Replace(s, @"\s*}\s*", "}");
s = Regex.Replace(s, @"\s*;\s*", ";");
return s;
});
break;
}
}
}
ビューが20MBを超えるデータを生成している場合は、データを表示するさまざまな方法、おそらくページングを調査することをお勧めします。
これは、プロジェクトで使用している空白フィルター属性のVB.NETバージョンです。
#Region "Imports"
Imports System.IO
#End Region
Namespace MyCompany.Web.Mvc.Extensions.ActionFilters
''' <summary>
''' WhitespaceFilter attribute
''' </summary>
Public NotInheritable Class WhitespaceFilterAttribute
Inherits ActionFilterAttribute
''' <summary>
''' Called when action executing.
''' </summary>
''' <param name="filterContext">The filter context.</param>
''' <remarks></remarks>
Public Overrides Sub OnActionExecuting(filterContext As ActionExecutingContext)
filterContext.HttpContext.Response.Filter = New WhitespaceFilterStream(filterContext.HttpContext.Response.Filter)
End Sub
#Region "Whitespace stream filter"
''' <summary>
''' Whitespace stream filter
''' </summary>
Private Class WhitespaceFilterStream
Inherits Stream
#Region "Declarations"
' Member vars.
Private Shared regexPattern As New Regex("(?<=[^])\t{2,}|(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,11}(?=[<])|(?=[\n])\s{2,}")
' Property vars.
Private sinkStreamValue As Stream
Private positionValue As Long
#End Region
#Region "Constructor(s)"
''' <summary>
''' Contructor to create a new object.
''' </summary>
''' <param name="sink"></param>
''' <remarks></remarks>
Public Sub New(sink As Stream)
Me.sinkStreamValue = sink
End Sub
#End Region
#Region "Properites"
''' <summary>
''' Gets the CanRead value.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides ReadOnly Property CanRead() As Boolean
Get
Return True
End Get
End Property
''' <summary>
''' Gets the CanSeek value.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides ReadOnly Property CanSeek() As Boolean
Get
Return True
End Get
End Property
''' <summary>
''' Gets the CanWrite value.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides ReadOnly Property CanWrite() As Boolean
Get
Return True
End Get
End Property
''' <summary>
''' Get Length value.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides ReadOnly Property Length() As Long
Get
Return 0
End Get
End Property
''' <summary>
''' Get or sets Position value.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Property Position() As Long
Get
Return Me.positionValue
End Get
Set(value As Long)
Me.positionValue = value
End Set
End Property
#End Region
#Region "Stream Overrides Methods"
''' <summary>
''' Stream object Close method.
''' </summary>
''' <remarks></remarks>
Public Overrides Sub Close()
Me.sinkStreamValue.Close()
End Sub
''' <summary>
''' Stream object Close method.
''' </summary>
''' <remarks></remarks>
Public Overrides Sub Flush()
Me.sinkStreamValue.Flush()
End Sub
''' <summary>
''' Stream object Read method.
''' </summary>
''' <param name="buffer"></param>
''' <param name="offset"></param>
''' <param name="count"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function Read(buffer As Byte(), offset As Integer, count As Integer) As Integer
Return Me.sinkStreamValue.Read(buffer, offset, count)
End Function
''' <summary>
''' Stream object Seek method.
''' </summary>
''' <param name="offset"></param>
''' <param name="Origin"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function Seek(offset As Long, Origin As SeekOrigin) As Long
Return Me.sinkStreamValue.Seek(offset, Origin)
End Function
''' <summary>
''' Stream object SetLength method.
''' </summary>
''' <param name="value"></param>
''' <remarks></remarks>
Public Overrides Sub SetLength(value As Long)
Me.sinkStreamValue.SetLength(value)
End Sub
''' <summary>
''' Stream object Write method.
''' </summary>
''' <param name="bufferBytes"></param>
''' <param name="offset"></param>
''' <param name="count"></param>
''' <remarks></remarks>
Public Overrides Sub Write(bufferBytes As Byte(), offset As Integer, count As Integer)
Dim html As String = Encoding.Default.GetString(bufferBytes)
Buffer.BlockCopy(bufferBytes, offset, New Byte(count - 1) {}, 0, count)
html = regexPattern.Replace(html, String.Empty)
Me.sinkStreamValue.Write(Encoding.Default.GetBytes(html), 0, Encoding.Default.GetBytes(html).GetLength(0))
End Sub
#End Region
End Class
#End Region
End Class
End Namespace
そしてGlobal.asax.vbで:
Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
With filters
' Standard MVC filters
.Add(New HandleErrorAttribute())
' MyCompany MVC filters
.Add(New CompressionFilterAttribute)
.Add(New WhitespaceFilterAttribute)
End With
End Sub
空白はかなりよく圧縮されます。空白を削除してもそれほど節約できるとは思いません。
可能であれば、HTMLの一部をクライアントにオフロードして、JavaScriptを使用して繰り返されるものを再構成することをお勧めします。