Удаление лишних пробелов из сгенерированного HTML в MVC
У меня есть вид приложения MVC, который генерирует довольно большую таблицу таблиц HTML ( > 20 МБ).
Я сжимаю представление в контроллере с помощью фильтра сжатия
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);
}
}
}
Есть ли способ исключить (достаточно большое) количество избыточных пробелов, сгенерированных в представлении, прежде чем запускать фильтр сжатия (чтобы уменьшить рабочую нагрузку и размер сжатия)?
РЕДАКТИРОВАТЬ:
Я получил его работу с использованием метода WhiteSpaceFilter, предложенного Womp ниже.
Здесь интересны результаты, проанализированные Firebug:
1) Без сжатия, без пробела - 21 МБ, 2,59 минуты
2) С сжатием GZIP нет полосы пропускания - 2 МБ, 17,59 с
3) При сжатии GZIP полоса пропускания - 558 кБ, 12,77 с
Так что, конечно, стоит того.
Ответы
Ответ 1
Этот парень написал аккуратный маленький пробойник, который просто запускает быструю блочную копию ваших байтов через регулярное выражение, чтобы вырезать капли из пространство. Он написал это как http-модуль, но вы могли бы извлечь из него 7 строк кода рабочей лошади и использовать его в своей функции.
Ответ 2
@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
///questions/177720/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
}
}
Ответ 3
Можно удалять пробелы во время компиляции, расширяя Razor. Это устраняет (очень значимые по моим измерениям) врезки во время выполнения удаления пробела из сгенерированного HTML. Достижение достигает 88 мс на высоком конце i7, обрезая 100 КБ-документ, используя код на основе RegEx, найденный в Stack Overflow.
Ниже приведена реализация решения времени компиляции для MVC 3 и MVC 4:
Meleze.Web
Решение описано в
http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/
(но используйте код GitHub или NuGet DLL, поскольку код в блоге содержит только MVC 3).
Ответ 4
Я бы сказал, что если ваш вид генерирует более 20 МБ данных, вы можете изучить различные способы отображения данных, возможно, подкачки?
Ответ 5
#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;
}
}
}
Ответ 6
Вот версия 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
Ответ 7
Пробелы сжимаются довольно хорошо, я не думаю, что удаление будет сэкономить вам много.
Я хотел бы предложить попытаться выгрузить некоторые из HTML для клиента, если это возможно, использовать JavaScript для восстановления повторяющихся вещей.
Ответ 8
Если вы возвращаете JSON из представления, он уже минимизирован и не должен содержать пробелов или CR/LF. Вы должны использовать пейджинг, чтобы не отправлять сразу столько данных в браузер.