• Microsoft .NET
  • ASP.NET
  • Динамическая загрузка мастер-страниц в ASP.NET 3.5

ASP.NET: Очищаем HTML от лишних знаков

Однажды посмотрев HTML-документ, генерируемый ASP.NET и содержащий GridView, я заметил, что большое количество символов, содержащихся на странице - пробелы и знаки табуляции. Очевидно, ASP.NET щедро расставлял их, где только можно. Конечно, избавиться от лишних килобайтов HTML-документа можно, воспользовавшись библиотеками, которые сжимают HTML-документы при помощи различных алгоритмов. Но такой метод заставит сервер сначала сжать генерируемый HTML-документ, а браузеру пользователя придется распаковывать пришедший ему документ.
Я же хочу в своей статье предложить способ как избавиться от различных ненужных символов, которые содержатся на страницах. Этот способ основан на использовании сборки HTTPModule.
Так что же такое HTTPModule? Вот что о нем говорится в библиотеке MSDN:

HttpModule — это сборка, реализующая интерфейс IHttpModule и обрабатывающая события. ASP.NET включает набор сборок HttpModule, которые могут использоваться в приложениях пользователей. Например, SessionStateModule предоставляется ASP.NET для поставки в приложение служб состояния сеанса. Пользовательские обработчики HttpModule могут быть созданы в качестве ответа на событие ASP.NET или событие пользователя.
Общая процедура написания обработчика HttpModule следующая:
• Реализация интерфейса IHttpModule.
• Обработка метода Init и регистрация необходимых событий.
• Обработка событий.
• Реализация (при необходимости) метода Dispose, если требуется выполнить очистку.
• Регистрация модуля в файле Web.config.

HTTP Module включается в процесс обработки запроса пользователя после создания объекта HTTP Application и перед созданием HTTP Handle, так что HTTP Module позволяет обработать следующие события объекта HTTP Application:
• BeginRequest
• AuthenticateRequest
• PostAuthenticateRequest
• AuthorizeRequest
• PostAuthorizeRequest
• ResolveRequestCache
• PostResolveRequestCache
• PostMapRequestHandler
• AcquireRequestState
• PostAcquireRequestState
• PreRequestHandlerExecute
• PostRequestHandlerExecute
• ReleaseRequestState
• PostReleaseRequestState
• UpdateRequestCache
• PostUpdateRequestCache
• EndRequest

Подключение обработчиков событий выполняется в методе Init класса HTTPModule.
В своем примере мне необходимо подключить обработчик на 2 события ReleaseRequestState и PreSendRequestHeaders для того что бы созданные обработчики вызывался перед обработчикам созданным библиотекой для сжатия HTML-документов (если такая так же подключена к проекту).

/// <summary>
/// Подключение обработчиков событий
/// </summary>
public void Init(HttpApplication context)
{
     //Подключаем обработчик на событие ReleaseRequestState
     context.ReleaseRequestState += new EventHandler(this.context_Clear);
     //Подключаем обработчик на событие PreSendRequestHeaders
     context.PreSendRequestHeaders += new EventHandler(this.context_Clear);
     //Два обработчика необходимы для совместимости с библиотеками сжатия HTML-документов
}

Сам обработчик будет иметь следующий вид:

/// <summary>
/// Обработчик события PostRequestHandlerExecute
/// </summary>
void context_Clear(object sender, EventArgs e)
{
     HttpApplication app = (HttpApplication)sender; //Получение HTTP Application
     //Получаем имя файла который обрабатывается
     string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length + 1);
     //Проверяем не является ли он ссылкой на ресурс сборки           
     if (realPath == "WebResource.axd")
          return;

     //Проверяем тип содержимого
     if (app.Response.ContentType == "text/html")         
         //Устанавливаем фильтр-обработчик
         app.Context.Response.Filter = new HTMLClearer(app.Context.Response.Filter);
}

Фильтр-обработчик - это самое главное. Он позволяет изменять содержимое объекта Response. А дополнительные проверки необходимы, чтобы исключить обработку ресурсов сборок и документов, тип которых отличен от text/html (в документах другого типа нет необходимости убирать лишние символы).
 Теперь уделим внимание обработчику содержимого Response.
Это класс, являющийся наследником System.IO.Stream. В его реализации нам интересен только один метод - это метод Write:

/// <summary>
/// Обрабатываем данные поступающие в Response
/// </summary>
public override void Write(byte[] buffer, int offset, int count)
    {
        //Преобразовываем массив байт в строку
        string s = System.Text.Encoding.UTF8.GetString(buffer);
        //Используя регулярные выражения убираем все ненужные символы
        s = Regex.Replace(s,
">(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}(\r\n){0,10} {0,20}(\r\n){0,10} {0,20}<", ">
<", RegexOptions.Compiled);
        s = Regex.Replace(s,
";(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}", ";", RegexOptions.Compiled);
        s = Regex.Replace(s,
"{(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}", "{", RegexOptions.Compiled);
        s = Regex.Replace(s, ">(\r\n){0,10}\t{0,10}<", "><", RegexOptions.Compiled);
        s = Regex.Replace(s, ">\r{0,10}\t{0,10}<", "><", RegexOptions.Compiled);
        //Получивщуюся строку преобразовываем обратно в byte
        byte[] outdata = System.Text.Encoding.UTF8.GetBytes(s);
        //Записываем ее в Response
        _HTML.Write(outdata, 0, outdata.Length);
     }
А также конструктор класса:
public HTMLClearer(System.IO.Stream HTML)
        { _HTML = HTML; }

Для демонстрации примера использования HTTP Module и обработчика содержимого объекта HTTP Response создадим проект Class Library и назовем его - HTMLClearer. В этом проекте следует создать файл HTMLClearer.cs, содержащей следующий текст: 

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Text.RegularExpressions;
namespace HTMLClearer
{
    public class HTMLClearer : System.IO.Stream
    {
        System.IO.Stream _HTML;
        public HTMLClearer(System.IO.Stream HTML)
        { _HTML = HTML; }

        #region Стандартные методы и свойства
        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 { return _HTML.Length; } }

        public override long Position
        {
            get { return _HTML.Position ; }
            set { _HTML.Position  = value; }
        }
        public override long Seek(long offset, System.IO.SeekOrigin origin)
        { return _HTML.Seek(offset, origin); }

        public override void SetLength(long value)
        { _HTML.SetLength(value); }

        public override void Flush()
        { _HTML.Flush(); }

        public override int Read(byte[] buffer, int offset, int count)
        { return _HTML.Read(buffer, offset, count); }
        #endregion
        /// <summary>
        /// Обрабатываем данные поступающие в Response
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            //Преобразовываем массив байт в строку
            string s = System.Text.Encoding.UTF8.GetString(buffer);
            //Используя регулярные выражения убираем все ненужные символы
            s = Regex.Replace(s, ">(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}(\r\n)

{0,10} {0,20}(\r\n){0,10} {0,20}<", "><", RegexOptions.Compiled);
            s = Regex.Replace(s, ";(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}", ";",

RegexOptions.Compiled);
            s = Regex.Replace(s, "{(\r\n){0,10} {0,20}\t{0,10}(\r\n){0,10}\t{0,10}", "{",

RegexOptions.Compiled);
            s = Regex.Replace(s, ">(\r\n){0,10}\t{0,10}<", "><", RegexOptions.Compiled);
            s = Regex.Replace(s, ">\r{0,10}\t{0,10}<", "><", RegexOptions.Compiled);
            //Получивщуюся строку преобразовываем обратно в byte
            byte[] outdata = System.Text.Encoding.UTF8.GetBytes(s);
            //Записываем ее в Response
            _HTML.Write(outdata, 0, outdata.Length);
        }

    }
    public class HTTPModule_Clearer : IHttpModule
    {
        #region IHttpModule Members
        public void Dispose()
        {         
        }
        /// <summary>
        /// Подключение обработчиков событий
        /// </summary>
        public void Init(HttpApplication context)
        {
           //Подключаем обработчик на событие ReleaseRequestState
           context.ReleaseRequestState += new EventHandler(this.context_Clear);
           //Подключаем обработчик на событие PreSendRequestHeaders
           context.PreSendRequestHeaders += new EventHandler(this.context_Clear);
           //Два обработчика необходимы для совместимости с библиотеками сжатия HTML-документов
        }
        /// <summary>
        /// Обработчик события PostRequestHandlerExecute
        /// </summary>
        void context_Clear(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender; //Получение HTTP Application
            string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length + 1);

//Получаем имя файла который обрабатывается
            if (realPath == "WebResource.axd") //Проверяем не является ли он ссылкой на ресурс сборки
                return;

            if (app.Response.ContentType == "text/html")//Проверяем тип содержимого
                app.Context.Response.Filter = new HTMLClearer(app.Context.Response.Filter);

//Устанавливаем фильтр обработчик
        }
        #endregion
    }
}

После всех этих манипуляций компилируем проект, и получившуюся библиотеку через Add Reference подключаем к Веб-сайту.
Теперь нам необходимо подключить HTTP Module к общему потоку обработки запросов (если в проекте используется модуль сжатия HTML-документов, то он должен быть добавлен после модуля HTMLClearer). Для этого в файле web.config необходимо сделать некоторые изменения, а именно в раздел system.web добавить ссылку на модуль:

<httpModules>
      <add name="HTTPModule_Clearer"  type="HTMLClearer.HTTPModule_Clearer, HTMLClearer"/>
</httpModules>

Общий вид тега <httpModules> взятый из MSDN выглядит так:
<httpModules>
   <add type="classname,assemblyname" name="modulename"/>
   <remove name="modulename"/>
   <clear/>
</httpModules>

Подтег Описание
<add> Добавляет в приложение класс HttpModule.
Обратите внимание, что в случае сочетания команда-путь, идентичного уже указанному ранее (например, в файле Web.config родительской папки), второй вызов <add> переопределяет предыдущее значение.
<remove> Удаляет из приложения класс HttpModule.
<clear>
 Удаляет из приложения все сопоставления HttpModule.

 При использовании данного модуля размер HTML-страничек, отправляемых пользователю, уменьшается примерно на 10%, что не может не сказаться на трафике, как пользователя, так и сервера.
P.S.
Использование HTTP Module позволяет также вносить изменения в отправляемый пользователю ответ (например, если есть необходимость добавить к странице Header или Footer).
Также, дописав метод Write, можно сохранять определенные части страницы или страницы целиком в базу данных или  в файл.

Автор: C...R...a...S...H