Работа с разными браузерами

163

Учитывая широкое разнообразие средств, поддерживаемых разными браузерами, иногда непросто создать приложение, которое работает со всеми браузерами, обеспечивая наилучший из возможных пользовательский интерфейс. Платформа ASP.NET предлагает несколько средств, которые могут помочь в написании корректной разметки, работающей на разнообразных устройствах.

Класс HtmlTextWriter

Платформа ASP.NET предлагает очень разную разметку, которую видит клиент, так что один клиент получает HTML 3.2. другой - HTML 4.0. а третий - XHTML 1.1. Можно даже не подозревать о настолько существенных различиях.

Все это работает через класс HtmlTextWriter, который имеет несколько производных классов. Сам HtmlTextWriter спроектирован с учетом вывода разметки HTML 4.0. Но его производные классы отличаются: так, Html32TextWriter записывает разметку HTML 3.2 для клиентов нижнего уровня, a XhtmlTextWriter - разметку XHTML 1.1. Поскольку все эти классы унаследованы от HtmlTextWriter, вы вольны использовать в коде визуализации тот же самый базовый набор методов HtmlTextWriter. Однако реализация многих этих методов отличается, так что в зависимости от того, какой объект был получен, вывод может отличаться.

Например, предположим, что используется следующий код визуализации:

writer.RenderBeginTag(HtmlTextWriterTag.Div);

В этом случае ожидается такая разметка:

<div>

Но ниже показан результат, который будет получен от Html32TextWriter (исходя из предположения, что Html32TextWriter.ShouldPerformDivTableSubstitution равно true):

<table cellpadding="0" cellspacing="0" border="0" width="100%">
    <tr><td>

С другой стороны, если используется следующий код, то визуализированный вывод будет совершенно негибким и никогда не изменится в зависимости от возможностей целевого устройства:

writer.Write("<div>");

Аналогично, если производится наследование от WebControl для получения автоматической поддержки для свойств стиля, эта поддержка реализуется по-разному, в зависимости от визуализатора.

Из этого следует, что необходимо избегать написания низкоуровневой HTML-разметки (с использованием метода Write()), а вместо этого, где только возможно, использовать высокоуровневые методы (такие как RenderBeginTag(), RenderEndTag() и т.д.). В результате элементы управления станут более гибкими. ASP.NET создает и передает корректный HtmlTextWriter, который основан на функциональности браузера, запросившего страницу, и появится возможность адаптации HTML-разметки.

Эта проблема теперь не столь критична, как раньше, потому что большинство распространенных браузеров поддерживают XHTML. Тем не менее, это правило хорошего проектного решения, которое гарантирует надежную работоспособность кода в случае, когда ASP.NET будет обновлен для поддержки новых типов визуализации, которые не обладают пока широко распространенной поддержкой.

Определение браузера

Итак, каким же образом среда ASP.NET определяет тип класса записи текста, который подходит для конкретного клиента? Здесь все зависит от строки пользовательского агента, которая передается клиентом, когда он осуществляет запрос. ASP.NET пытается сопоставить эту строку с огромным каталогом известных браузеров. Вы можете найти этот каталог в C:\ [Каталог Windows] \Microsoft.NET\Framework\ [Версия] \Config\Browsers. Там можно найти множество файлов .browser. Каждый представляет собой HTML-файл, который отображает строку пользовательского агента для установки возможностей и класса записи текста.

Каждый файл .browser имеет следующую базовую структуру:

<browsers>
    <browser id="..." parentID="...">
        <identification>
            <!-- Здесь представлено одно регулярное выражение, которое производит 
            сопоставление со строкой user-agent. Также есть множество вариантов несовпадения, 
            с которыми в противном случае сравниваются строки user-agent. -->
        </identification>

        <capabilities>
            <!-- Предполагая соответствие строки user-agent, здесь указаны 
            возможности ASP.NET, которые должен иметь клиент. -->
        </capabilities>
        
        <controlAdapters>
            <!-- Для данного клиента некоторые элементы управления могут нуждаться 
            в нестандартной визуализации определенных элементов. 
            Это становится возможным через адаптеры. 
            Здесь приведен список всех специфичных для элементов управления адаптеров 
            ASP.NET, которые должны использоваться.
            -->
        </controlAdapters>
    </browser>

    <!-- Далее могут быть определены и другие браузеры. -->
</browsers>

Дальнейшее усложнение модели связано с возможностью создания подкатегорий браузеров. Для этого элемент <browser> включает атрибут parentID, ссылающийся на другое определение <browser>, от которого он должен наследовать установки.

Эта система может показаться довольно хрупкой. К сожалению, так оно и есть. Нет никаких гарантий, что не встретится браузер со строкой, которая не соответствует ни одному из известных шаблонов. Однако это неизбежный компромисс в слабо связанном мире Интернета, и создатели ASP.NET довольно неплохо поработали над тем, чтобы информация о браузерах, поставляемая с ASP.NET 4.0, была надежной и более полной, чем в предыдущих версиях. Настройки браузеров можно также подстраивать и даже добавлять новые определения различных строк пользовательских агентов.

Свойства браузеров

Определить конфигурацию текущего браузера можно с помощью свойства Browser объекта HttpRequest, которое возвращает ссылку на объект HttpBrowserCapabilities. (Получить строку user-agent можно также через свойство UserAgent.)

Когда клиент выполняет HTTP-запрос, создается объект HttpBrowserCapabilities, который заполняется информацией о возможностях браузера на основе соответствующего файла .browser. Информация, представленная в классе HttpBrowserCapabilities, включает вид браузера и его версию, поддерживает ли он выполнение сценариев на стороне клиента и т.д. Определив возможности браузера, можете скорректировать свой вывод для обеспечения различного поведения в разных браузерах. Таким образом, можно полностью раскрыть потенциальные возможности клиентов верхнего уровня, не нарушая работу низкоуровневых клиентов.

В таблице ниже перечислены свойства класса HttpBrowserCapabilities:

Свойства класса HttpBrowserCapabilities
Свойство Описание
Browser

Получает строку браузера, которая была отправлена вместе с запросом в заголовке пользовательского агента

MajorVersion

Получает старший номер версии клиентского браузера (например, для версии 4.5 это свойство вернет 4)

MinorVersion

Получает младший номер версии клиентского браузера (например, для версии 4.5 это свойство вернет 5)

Type

Получает имя и старший номер версии клиентского браузера

Version

Получает полный номер версии клиентского браузера

Beta

Возвращает признак бета-выпуска клиентского браузера

AOL

Возвращает true, если клиент является браузером AOL (America Online)

Platform

Предоставляет имя платформы операционной системы, используемой клиентом

Win16

Возвращает true, если клиент является компьютером на базе Win16

Win32

Возвращает true, если клиент является компьютером на базе Win32

ClrVersion

Предоставляет старший номер версии исполняющей среды .NET CLR, установленной на клиентском компьютере. Можно также использовать метод GetClrVersions() для извлечения информации обо всех версиях установленных CLR-сред. Эта настройка важна, только если на веб-странице есть встроенные элементы управления .NET Windows Forms. Клиентские браузеры не нуждаются в CLR для запуска обычных страниц ASP.NET

ActiveXControls

Возвращает true, если клиентский браузер поддерживает элементы управления ActiveX

BackgroundSounds

Возвращает true, если клиентский браузер поддерживает фоновый звук

Cookies

Возвращает true, если клиентский браузер поддерживает cookie-наборы

Frames

Возвращает true, если клиентский браузер поддерживает фреймы

Tables

Возвращает true, если клиентский браузер поддерживает таблицы HTML

JavaScript

Указывает на то, поддерживает ли браузер JavaScript. Это свойство считается устаревшим и вместо него рекомендуется проверять свойство EcmaScriptVersion

VBScript

Возвращает true, если клиентский браузер поддерживает VBScript

JavaApplets

Возвращает true, если клиентский браузер поддерживает Java-аплеты

EcmaScriptVersion

Получает номер версии сценария ECMA, поддерживаемой клиентским браузером

MSDomVersion

Получает версию Microsoft HTML DOM, поддерживаемую клиентским браузером

Crawler

Возвращает true, если клиентским браузером является поисковый робот

Класс HttpBrowserCapabilities имеет одно бросающееся в глаза ограничение - он может определить только ожидаемую встроенную функциональность браузера. Он не может определить текущее состояние этой функциональности.

Например, предположим, что оценивается поддержка JavaScript клиентской стороны, предоставляемая браузером. Если браузером, приславшим запрос, является Google Chrome, это свойство вернет true, поскольку у него есть поддержка JavaScript клиентской стороны. Однако даже если у пользователя средства поддержки сценариев отключены, свойство JavaScript все равно вернет true. Другими словами, есть возможность узнать не то, что браузер должен делать, а то, что он может делать.

Фактически все, что на самом деле делает ASP.NET - это читает информацию пользовательского агента, переданную от браузера на сервер во время запроса, и сравнивает эту строку с предопределенной информацией в файлах .browser. В файлах .browser перечислены возможности браузера, такие как функциональность поддержки сценариев, стилей, фреймов и т.д. К сожалению, клиент не присылает сведений о том, как сконфигурирован браузер.

Эта ситуация оставляет два выбора. Можно положиться на класс HttpBrowserCapabilities в получении информации о доступности определенных средств браузера и основывать программную логику на этой информации. В этом случае вы должны быть готовы к случайным ошибкам. Если же вы предпочитаете более устойчивый подход, придется писать собственный код так, чтобы он самостоятельно проверял поддержку нужного средства.

Например, с помощью cookie-наборов можно (имея две веб-страницы) попробовать установить cookie-набор и затем попытаться прочитать его. Если это не удастся, значит, поддержка cookie-наборов отключена. Обходной путь подобного рода можно использовать для проверки других свойств, таких как поддержка JavaScript. Например, можно добавить на страницу фрагмент кода JavaScript, который записывает значение переменной формы, и затем проверить это значение на сервере.

Эти шаги довольно неуклюжи и запутаны, но это единственный способ получить полную уверенность в доступности определенных средств браузера. К сожалению, при создании специальных элементов управления обычно вы лишены возможности выполнения подобных проверок.

Изменение определения типа браузера

Платформа ASP.NET предоставляет возможность явно задать, как будет визуализироваться страница, вместо того, чтобы полагаться на автоматическое определение браузера. Трюк состоит в установке свойства Page.ClientTarget, либо программно (на стадии Page.PreInit}, либо декларативно (с использованием директивы Page). В случае установки свойства ClientTarget автоматическое определение браузера отключается и для остальной части запроса ASP.NET использует установки браузера, указанные вами.

Единственный нюанс, связанный с использованием свойства ClientTarget, состоит в возможности использовать только определенные псевдонимы. Каждый псевдоним отображается на специфическую строку пользовательского агента (и установка браузера для этого пользовательского агента объявлена в соответствующем файле .browser).

Например, предположим, что необходимо проверить, как страница будет визуализирована в устаревшем браузере вроде Internet Explorer 6. Для начала потребуется создать псевдоним в разделе <clientTarget>, который отображает правильную строку пользовательского агента на любое выбранное имя. В данном случае псевдонимом будет ie6:

<configuration>
    <system.web>
      <clientTarget>
        <add alias="ie6"
             userAgent="Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)"/>
      </clientTarget>
      ...
    </system.web>
</configuration>

Теперь можно заставить страницу использовать этот псевдоним и визуализировать себя, как будто бы запрос поступил от Internet Explorer 6, установив в ie6 атрибут ClientTarget директивы Page. Вот как это делается:

<%@ Page =ClientTarget="ie6" ... %>

Адаптивная визуализация

В идеале удается сгенерировать разметку, которая будет работать во всех основных браузерах. Но в некоторых случаях придется писать специфическую для конкретного браузера логику визуализации. В худшем случае это выглядит примерно так:

protected override void RenderContents(HtmlTextWriter writer)
{
        base.RenderContents(writer);
        if (Page.Request.Browser.EcmaScriptVersion.Major >= 1) 
        {
            writer.Write ("<i>Ваш браузер поддерживает JavaScript</i><br />");
        }
        
        if (Page.Request.Browser.Browser == "IE") 
        {
            writer.Write("<i>Разметка сконфигурирована под браузер IE</i><br />");
        }
        else
            writer.Write("<i>Разметка сконфигурирована " +
                "под любой браузер, кроме IE</i><br>");
}

Лучший подход предусматривает использование элемента управления для вывода стандартной визуализации и создание адаптера элемента управления, который реализует специализированную визуализацию для определенного браузера. Модель адаптера элемента позволяет создать единственный элемент управления, который может быть адаптирован для различных типов устройств. Лучше всего то, что благодаря такому отделению элементов управления от адаптеров независимые разработчики могут писать адаптеры для существующих элементов, давая им возможность работать с другими платформами.

Любой элемент управления связывается с адаптером в файле .browser. Например, можно создать адаптер FirefoxSlideMenuAdapter, который изменяет код визуализации элемента управления SlideMenu так, что он лучше работает с браузерами Firefox. Затем понадобится отредактировать файл mozilla.browser, специально указав, что данный адаптер должен использоваться с браузерами Firefox.

Адаптер элемента управления работает за счет включения в процесс визуализации. Платформа ASP.NET вызывает адаптер на каждой стадии жизненного цикла веб-элемента управления, что позволяет адаптеру вмешиваться в процесс визуализации и обрабатывать прочие детали, такие как логику состояния представления, специфическую для устройства.

Чтобы создать адаптер, унаследуйте новый класс от System.Web.UI.Adapters.ControlAdapter (если специальный элемент управления унаследован от Control) или от System.Web.UI.WebControls.Adapters.WebControlAdapter (если специальный элемент управления унаследован от WebControl). Затем реализуйте необходимую функциональность, переопределяя нужные методы. Каждый метод соответствует методу класса специального элемента управления, и если метод в адаптере элемента управления переопределен, метод адаптера будет использоваться вместо метода элемента управления.

Как и в случае серверных элементов управления, адаптеры должны быть помещены в отдельную DLL-сборку. Если адаптеры относительно просты, их можно поместить в ту же самую сборку, которая содержит элементы управления. Однако если адаптеры сложны и спроектированы для поддержки специализированных сценариев использования, предпочтительнее поместить их в отдельную сборку.

Например, в ControlAdapter могут быть переопределены такие методы, как OnInit(), Render() и RenderChildren(). В WebControlAdapter можно также переопределить RenderBeginTag(), RenderEndTag() и RenderContents().

Пройди тесты
Лучший чат для C# программистов