从 MVC 中生成的 HTML 中删除多余的空格
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/855526/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Removing extra whitespace from generated HTML in MVC
提问by WOPR
I have an MVC application view that is generating quite a large HTML table of values (>20MB).
我有一个 MVC 应用程序视图,它生成了一个相当大的 HTML 值表(> 20MB)。
I am compressing the view in the controller using a compression filter
我正在使用压缩过滤器压缩控制器中的视图
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);
}
}
}
Is there a way to also eliminate the (quite large) amount of redundant whitespace generated in the view before I run the compress filter (to reduce compression workload and size)?
在运行压缩过滤器(以减少压缩工作量和大小)之前,有没有办法消除视图中生成的(相当大的)冗余空白量?
EDIT:I got it working using the WhiteSpaceFilter technique suggested by Womp below.
编辑:我使用下面 Womp 建议的 WhiteSpaceFilter 技术让它工作。
For interest here's the results, as analysed by Firebug:
感兴趣的是这里的结果,如 Firebug 分析的:
1) No Compression, no whitespace strip - 21MB, 2.59 minutes
2) With GZIP compression, no whitespace strip - 2MB, 17.59s
3) With GZIP compression, whitespace strip - 558kB, 12.77s
So certainly worth it.
1) 无压缩,无空白条 - 21MB,2.59 分钟
2) 使用 GZIP 压缩,无空白条 - 2MB,17.59s
3) 使用 GZIP 压缩,空白条 - 558kB,12.77s
所以当然值得。
采纳答案by womp
This guywrote a neat little whitespace compactor that simply runs a fast block copy of your bytes through a regular expression to strip out blobs of space. He wrote it as an http module, but you could take the 7 lines of workhorse code out of it and plop it into your function.
这家伙写了一个整洁的小空白压缩器,它只是通过正则表达式运行一个快速的字节块副本,以去除空间块。他将其编写为一个 http 模块,但您可以从中取出 7 行主力代码并将其放入您的函数中。
回答by tugberk
@womp has already suggested a good way of doing it but that module is pretty outdated. I have been using that but it turns out that it is not an optimal way. Here is the question I asked about:
@womp 已经提出了一个很好的方法,但该模块已经过时了。我一直在使用它,但事实证明这不是最佳方式。这是我问的问题:
Remove white space from entire Html but inside pre with regular expressions
从整个 Html 中删除空白,但在 pre 内部使用正则表达式
Here is how I do it:
这是我如何做到的:
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
}
}
回答by Eric J.
One can remove whitespace at compile timeby extending Razor. That eliminates the (very significant by my measurements) runtime hit of removing white space from the generated HTML. The hit is as large as 88ms on a high end i7 trimming a 100KB document using RegEx-based code found on Stack Overflow.
可以通过扩展 Razor在编译时删除空格。这消除了(我的测量非常重要)从生成的 HTML 中删除空白的运行时命中。在使用 Stack Overflow 上找到的基于 RegEx 的代码修剪 100KB 文档的高端 i7 上,命中高达 88 毫秒。
The following provides an implementation of a compile-time solution for MVC 3 and MVC 4:
下面提供了 MVC 3 和 MVC 4 的编译时解决方案的实现:
The solution is described at
解决方案描述于
http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/
http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/
(but use the GitHub code or NuGet DLL, as the code in the blog post covers only MVC 3).
(但使用 GitHub 代码或 NuGet DLL,因为博客文章中的代码仅涵盖 MVC 3)。
回答by Eric J.
#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;
}
}
}
回答by Tom Anderson
I would say that if your View is generating over 20mb of data, you may want to investigate different ways to display the data, perhaps paging?
我想说的是,如果您的 View 生成了超过 20mb 的数据,您可能想要研究显示数据的不同方式,也许是分页?
回答by Ed DeGagne
Here is a VB.NET version of a whitespace filter attribute I am using in a project:
这是我在项目中使用的空白过滤器属性的 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
And in Global.asax.vb:
在 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
回答by great_llama
Whitespace compresses pretty well, I don't think removing it is going to save you much.
空白压缩得很好,我认为删除它不会为您节省很多。
I would suggest trying to offload some of the HTML to the client if possible, use JavaScript to reconstitute things that repeat.
如果可能,我建议尝试将一些 HTML 卸载到客户端,使用 JavaScript 重新构建重复的内容。
回答by Dave Swersky
If you are returning JSON from the View, it is already minified and should not contain any whitespace or CR/LF. You should use paging to keep from sending so much data to the browser at once.
如果您从视图返回 JSON,则它已经被缩小并且不应包含任何空格或 CR/LF。您应该使用分页来避免一次向浏览器发送如此多的数据。