我有一个MVC应用程序视图,它生成了一个非常大的HTML表值(> 20MB)。
我正在使用压缩过滤器压缩控制器中的视图
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.59s
3)使用GZIP压缩,空白条 - 558kB,12.77s
所以当然值得。
答案 0 :(得分:20)
This guy编写了一个简洁的小空白压缩器,只需通过正则表达式运行字节的快速块副本即可去掉空间的blob。他把它写成了一个http模块,但是你可以从中获取7行的主力代码并将它放入你的函数中。
答案 1 :(得分:6)
@womp已经提出了一个很好的方法,但该模块已经过时了。我一直在使用它,但事实证明它不是一种最佳方式。这是我问过的问题:
Remove white space from entire Html but inside pre with regular expressions
我是这样做的:
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
}
}
答案 2 :(得分:4)
可以通过扩展Razor来删除编译时的空白。这消除了(通过我的测量非常重要)从生成的HTML中删除空白的运行时命中。在高端i7上使用Stack Overflow上的基于RegEx的代码修剪100KB文档时,命中率高达88ms。
以下提供了MVC 3和MVC 4的编译时解决方案的实现:
该解决方案在
中描述http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/
(但使用GitHub代码或NuGet DLL,因为博客文章中的代码仅涵盖MVC 3)。
答案 3 :(得分:2)
我想说如果您的View生成超过20mb的数据,您可能想要研究显示数据的不同方式,也许是分页?
答案 4 :(得分:2)
#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;
}
}
}
答案 5 :(得分:1)
以下是我在项目中使用的空白过滤器属性的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
答案 6 :(得分:0)
Whitespace压缩得很好,我不认为删除它会为你节省太多。
如果可能的话,我建议尝试将部分HTML卸载到客户端,使用JavaScript重新构建重复的内容。
答案 7 :(得分:-3)
如果从View返回JSON,它已经缩小,不应包含任何空格或CR / LF。您应该使用分页来防止一次向浏览器发送这么多数据。