Если вы даже поверхностно рассмотрите частые дискуссии о программировании сайтов для мобильных устройств, то обнаружите присущее им противоречие. С одной стороны, вы слышите, что многие отдают (или хотят делать это) приоритет мобильным устройствам в своем подходе к программированию приложений и сайтов. С другой стороны, те же люди часто превозносят media-запросы CSS и адаптивные разметки (liquid layouts). Я вижу противоречие в том, что такие запросы и разметки никак не ставят мобильные устройства на первое место — эта методология вообще не соответствует принципу «мобильные устройства в первую очередь» (mobile-first technique). В этой статье я поясню, как с помощью логики на серверной стороне обеспечить наилучшее отображение для конкретного устройства, в том числе с применением новой функциональности в ASP.NET MVC 4 — режимов отображения (display modes).
Проблема не в media-запросах CSS как технологии. Проблема даже не в адаптивном веб-проектировании (responsive Web design, RWD) как вспомогательной методологии media-запросов CSS. Что же делает media-запросы CSS и адаптивные разметки подходом «мобильные устройства в последнюю очередь» (mobile-last approach)? Ключ к разгадке кроется в самом слогане, используемом для продвижения этого подхода: одна кодовая база может обслуживать множество представлений. С этой точки зрения, CSS — клиентская технология — используется, чтобы переключаться между представлениями для дополнительной адаптации, когда одной CSS недостаточно.
На мой взгляд в этом подходе предлагается обслуживать все устройства одинаковым контентом, просто подгоняя разметку страницы под размеры экрана. Делая так, вы не сможете обеспечить максимально возможное удобство всем пользователям. Я считаю, что вполне разумно ориентироваться на единую кодовую базу (это стандартный подход в Web API), но определенно имеет смысл уделять особое внимание специфическим сценариям использования для каждого класса устройств, которые вы намерены поддерживать. Термин «мобильный» в наши дни вообще стал весьма размытым, поскольку широко распространились такие классы устройств, как смартфоны, планшеты, лэптопы и смарт-телевизоры, не говоря уже о всяких гаджетах вроде очков дополненной реальности и интеллектуальных наручных часов.
Примерно год назад я представил в этой рубрике серверный подход к разработке сайтов на основе ASP.NET MVC — создание специализированных представлений для каждого класса поддерживаемых устройств («Mobile Site Development: Markup», msdn.microsoft.com/magazine/jj133814). Я сделал это в контексте ASP.NET MVC 3. Приятно отметить, что в ASP.NET MVC 4 теперь имеются ранее упомянутые режимы отображения, с помощью которых легко реализовать логику на серверной стороне, способную создавать оптимальные представления и контент для конкретного устройства. Чтобы добиться максимальной эффективности при таком подходе, вы должны как можно больше знать о функциональности запрашивающего устройства. Однако, помимо базовой информации о размере экрана и его текущей ориентации, вы больше ничего особенного получить от клиента не сможете. И тогда вы должны прибегнуть к серверному хранилищу информации об устройствах.
Введение в режимы отображения в ASP.NET MVC 4
Прежде чем углубиться в обсуждение режимов отображения, позвольте мне заранее сказать, что эта статья (равно как и сама технология режимов отображения) относится в основном к созданию нового уникального сайта, динамически связывающего один и тот же URL с разными представлениями. Если у вас уже есть какой-то веб-сайт и вы хотите организовать сопутствующий ему сайт, оптимизированный для некоторых (мобильных) устройств, то это совсем другая история. Вы все равно можете использовать эту статью как руководство по созданию сопутствующего сайта, но унификация URL с существующим родительским сайтом потребует другого инструментария.
В ASP.NET MVC 4 режимы отображения являются системной функциональностью, которая расширяет классическое поведение механизмов представлений возможностью выбора файла представления, наиболее подходящего для запрашивающего устройства. В ранее упомянутой статье по ASP.NET MVC 3 я использовал для этой цели собственный механизм представлений. Кроме того, в том решении я был ограничен представлениями Razor. Благодаря функционалу режимов отображения методы вашего контроллера по-прежнему будут вызывать, скажем, представление с именем Index, а исполняющая среда ASP.NET MVC будет вместо этого выбирать файл представления с именем index.mobile.cshtml, если о запрашивающем устройстве известно, что оно мобильное.
Это отличные новости, поскольку вы сможете по-прежнему использовать одну кодовую базу для своего сайта. Вам просто потребуется добавить дополнительные CSHTML-файлы представлений для каждого класса устройств, который вы намерены поддерживать. Для начала рассмотрим пример кода на рис. 1.
Рис. 1. Стандартный список поддерживаемых режимов отображения
<h2>
Display Modes currently active
(@DisplayModeProvider.Instance.Modes.Count mode(s))
</h2>
<ul>
@{
foreach(var d in DisplayModeProvider.Instance.Modes)
{
<li>@(String.IsNullOrEmpty(d.DisplayModeId)
?"default" :d.DisplayModeId)</li>
}
}
</ul>
Страница с кодом, приведенным на рис. 1, выводит стандартный список поддерживаемых режимов отображения. Вывод, генерируемый этой страницей, показан на рис. 2.
Рис. 2. Исходный список режимов отображения
Режимы отображения в ASP.NET MVC 4 следуют нескольким соглашениям. В частности, каждый режим отображения сопоставляется с ключевым словом. Это ключевое слово используется для формирования имени соответствующего файла представления. Режим отображения по умолчанию (default display mode) связывается с пустой строкой. В итоге следующие файлы представлений корректно обрабатываются любым приложением ASP.NET MVC 4 без дополнительного вмешательства с вашей стороны: index.cshtml и index.mobile.cshtml.
Для демонстрации скопируйте файл index.cshtml в новый файл с именем index.mobile.cshtml и добавьте его в проект. Чтобы различать эти два файла, добавьте в «мобильный» файл такую строку:
<div style="border-bottom: solid 1px #000">Mobile view</div>
Если вы запустите приложение и протестируете его, используя Internet Explorer или другой настольный браузер, то ничего не меняется. Попробуйте нажать F12, чтобы открыть Internet Explorer Developer Tools и указать агент мобильного пользователя (user agent, UA), выбрав Tools | Change user agent string, как показано на рис. 3.
Рис. 3. Принудительный запуск агента мобильного пользователя в Internet Explorer для тестовых целей
Я уже сконфигурировал несколько мобильных и планшетных UA. Например, чтобы запрашивающий браузер был идентифицирован как смартфон HTC Desire Android, используйте следующее:
Mozilla/5.0 (Linux; U; Android 2.1; xx-xx; HTC Desire Build/ERE27)
AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2
На рис. 4 показано, что вы получите с сайта ASP.NET MVC 4. Страница, обслуживаемая той же парой методов контроллера и действия (action methods), является index.mobile.cshtml. Что важнее, все это происходит безо всяких изменений в стиле программирования и не требует обучения новым навыкам.
Рис. 4. Переключение в мобильное представление
Идем дальше
То, что обсуждалось до сих пор, — это самый минимум того, что вы можете (и должны) сделать при разработке своего мобильного сайта. Нужно продумать два ключевых момента, чтобы превратить функциональность режимов отображения в решение для реального сайта. Один из них — как добавить несколько режимов отображения. Другой — встраивание какой-то логики для более надежного распознавания устройств.
Встроенная в ASP.NET MVC логика для распознавания мобильных устройств оставляет желать много лучшего. Вероятно, она работает с большинством смартфонов, но бесполезна для обнаружения более старых сотовых телефонов. Рассмотрим, к примеру, следующий UA:
SAMSUNG-GT-S3370/S3370DDJD4 SHP/VPP/R5 Dolfin/1.5 Qtv/5.3
SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1 OPN-N
Этот UA относится к устаревшему смартфону (довольно популярному несколько лет назад) под управлением закрытой ОС и с нестандартным браузером на основе WebKit. Этот смартфон не поддерживает соединения по Wi-Fi, но обладает удивительно хорошими возможностями рендеринга HTML. Его экран меньше, чем у большинства смартфонов, но поддерживает сенсорный ввод. При использовании базовой поддержки режимов отображения из ASP.NET MVC этот смартфон не распознается как мобильное устройство и получает полные версии страниц. Здесь проявляются два недостатка. Во-первых, пользователи с трудом могут просматривать контент, поскольку он растягивается и выходит за пределы экрана. Во-вторых, загружается слишком много контента, а поскольку данный смартфон не поддерживает Wi-Fi, весь этот контент скорее всего будет загружаться по 3G-соединению, т. е. медленно и весьма дорого для пользователя.
Когда я поднимаю этот вопрос, некоторые отвечают, что их сайты просто не поддерживают такие типы устаревших устройств. Замечательно, но в таком случае разве не лучше было бы отправлять вежливое сообщение пользователю вместо того, чтобы пускать все на самотек? Чтобы отправить сообщение наподобие «Извините, этот сайт нельзя просматривать с вашего устройства», вам все равно нужно правильно распознавать данное устройство и понимать, что оно отличается, скажем, от iPhone. Более того, вопрос игнорирования устаревших устройств является бизнес-решением, а не проблемой реализации. Отказ от обслуживания прежних поколений устройств может отразиться не только на вашем имидже, но и на бизнесе. Поэтому давайте рассмотрим, как добавить несколько режимов отображения на сайт для корректного обслуживания нескольких классов устройств.
Классы устройств
Современный веб-сайт обеспечивает максимально возможное удобство использования независимо от типа устройства. «Максимально возможное удобство использования» означает, что предусматриваются специфические сценарии применения, избирательная передача данных и специфические функции. Конечная разметка должна зависеть от конкретного устройства. Если вы вместо этого подстраиваете все на клиенте, как это бывает, когда полагаются на media-запросы CSS, то на самом деле у вас есть унифицированное представление страниц, которое затем просто адаптируется под экраны разных размеров. По большей части это требует скрытия некоторых блоков, смены размещения некоторых других блоков на вертикальное и, возможно, уменьшения набора визуальных элементов. Унифицированное представление зачастую является страницей для настольного браузера. Лично я не считаю этот вариант тем, где на первое место ставятся мобильные устройства.
Говоря «тип устройства», я не имею в виду то, как различать iPhone от Windows Phone. Вместо этого я подразумеваю применение логики, способной генерировать разную разметку для смартфонов, планшетов и лэптопов. В ASP.NET MVC 4 у нас есть минимум три режима отображения: smartphone, tablet и default (для настольных браузеров). Я добавлю новый класс DisplayConfig, вызываемый из App_Start (рис. 5).
Рис. 5. Класс DisplayConfig
public class DisplayConfig
{
public static void RegisterDisplayModes(IList<IDisplayMode> displayModes)
{
var modeDesktop = new DefaultDisplayMode("")
{
ContextCondition = (c => c.Request.IsDesktop())
};
var modeSmartphone = new DefaultDisplayMode("smart")
{
ContextCondition = (c => c.Request.IsSmartphone())
};
var modeTablet = new DefaultDisplayMode("tablet")
{
ContextCondition = (c => c.Request.IsTablet())
};
displayModes.Clear();
displayModes.Add(modeSmartphone);
displayModes.Add(modeTablet);
displayModes.Add(modeDesktop);
}
}
Этот класс сначала опустошает предоставляемый набор режимов отображения. Тем самым он избавляется от режимов по умолчанию. Затем код заполняет предоставленный системный набор только что созданным списком режимов отображения. Новый режим отображения — это экземпляр класса DefaultDisplayMode. Имя режима задается через конструктор. Логика, которая определяет, подходит ли данный UA, устанавливается через свойство ContextCondition.
Свойство ContextCondition — это делегат, который принимает объект HttpContextBase и возвращает булево значение. В теле делегата анализируется HTTP-контекст текущего запроса, чтобы определить, годится ли данный режим отображения. На рис. 5 я использую несколько методов расширения, чтобы сохранить читаемость кода, а на рис. 6 перечислены эти методы расширения.
Рис. 6. Методы расширения, сохраняющие читаемость кода
public static class HttpRequestBaseExtensions
{
public static Boolean IsDesktop(this HttpRequestBase request)
{
return true;
}
public static Boolean IsSmartphone(this HttpRequestBase request)
{
return IsSmartPhoneInternal(request.UserAgent);
}
public static Boolean IsTablet(this HttpRequestBase request)
{
return IsTabletInternal(request.UserAgent);
}
// Здесь находится другой код
}
Весь рассмотренный до сих пор код является чисто инфраструктурным. В конечном счете вы пишете по одному методу для каждого режима отображения. Каждый метод принимает UA и должен возвращать булево значение. Вот самая базовая процедура для проверки на запрос с какого-либо планшета:
private static Boolean IsTabletInternal(String userAgent)
{
var ua = userAgent.ToLower();
return ua.Contains("ipad") || ua.Contains("gt-");
}
Эта процедура гарантированно распознает только iPad и Galaxy Tab, но из нее видно, как нужно писать такие процедуры. Как минимум, вы могли бы добавить код для проверки на обращения со смартфонов. Для обнаружения планшетов и смартфонов можно задействовать преимущества любой инфраструктуры Device Description Repository (DDR) — коммерческой или с открытым исходным кодом. Об этом мы поговорим в следующей статье.
Серьезный бизнес
Серверный подход к мобильным сайтам обязателен не всегда, но он очень важен, когда сайт используется для бизнеса. Я бы не рекомендовал серверный подход, скажем, для сайта конференции или любого другого сайта с малым сроком жизни. Однако бизнес-сайт, ориентированный на максимально широкую аудиторию, должен быть оптимизирован для всех устройств.
На клиенте вы ограничены размером окна браузера и его ориентацией и не можете проверить ОС или возможности сенсорного ввода, не говоря уже о более сложных вещах, например, поддерживает ли данное устройство беспроводную связь, потоковую передачу, подставляемые изображения, SMS и др. Режимы отображения особенно заметно упрощают реализацию подхода с несколькими представлениями в ASP.NET MVC 4.