Архитектурный паттерн MVC. Разделение кода. Модель MVC

Что такое MVC?

Итак, MVC - это про пользовательский интерфейс (UI). Не обязательно графический, голосовое управление тоже годится. Не забудем, что программа может не иметь пользовательского интерфейса, может иметь программный интерфейс (API) или вообще никакого не иметь и всё ещё быть полезной.

Но если у нас есть пользователь, значит должен быть пользовательский интерфейс. Что же такое интерфейс? Это смежная граница между двумя системами. В нашем случае: с одной стороны - программа, с другой - пользователь. Вот они.

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

Юзкейсы

В качестве примера представьте терминал для торговли на бирже. Пользователь терминала выставляет заявку, в которой указывает, что он хочет купить акции компании «Светлый путь» в количестве 20 штук по цене 1500 рублей за акцию. Также указывает, что заявка действительна в течение четырёх часов, и с какого из его счетов списать деньги, в случае успешной сделки.

Ощутимое количество атрибутов. Проходит некоторое время, и он понимает, что по такой цене купить не удастся и готов поднять цену до 1550 рублей, оставив все остальные значения. Тогда он выбирает эту заявку, нажимает кнопку «изменить», указывает новую цену, да. Это удобно.

Но на бирже нельзя изменить заявку, в предметной области нет такого понятия. Заявку можно только выставить и отменить. Чтобы дать пользователю возможность в один клик менять заявку, надо запоминать старые значения, снимать заявку, давать редактировать то, что запомнили, и выставлять новую заявку. Такая комбинация. Но для пользователя она выглядит как одно простое действие: изменение заявки. Это называется - use case.

Дополним нашу диаграмму местом под юзкейсы.

Ещё пользователю надо дать возможность дёргать эти юзкейсы и получать результат. Это могут быть кнопки и другие графические элементы ввода-вывода, жесты, распознавание и синтез речи. Любой вариант обмена данными и командами. Вуаля:

Пользователь дёргает какой-то из юзкейсов, который, в свою очередь, производит манипуляции над программой. Программа публикует результат или изменения в её состоянии.

Так где же тут все-таки MVC?

Все, что осталось - это только раздать знакомые имена образовавшимся компонентам.

Когда модель публикует изменения, её не волнует для кого, она ничего не знает про View. Вместо или вместе со View на том конце может быть другая подсистема.

Теперь немного частностей.

Это был классический вариант MVC - Active Model. Бывает и так, что модель не оповещает об изменениях. Тогда эту обязанность берёт на себя контроллер. Он знает, какие манипуляции производит над моделью, и, очевидно, знает, какие изменения в состоянии модели могут последовать. Это Passive Model.

И ещё один момент. Деление кода на предметный и не предметный - условное и зависит от того, насколько педантично мы хотим смоделировать предметную область. Иногда это рациональное решение - включить какой-то юзкейс в модель. Возможно, это уменьшит количество кода в целом и упростит его.

За материал благодарим нашего подписчика Станислава Ильичева

Контроллеры

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

В инфраструктуре ASP.NET MVC Framework контроллеры являются классами.NET, которые содержат логику, требуемую для обработки запроса. Роль контроллера заключается в инкапсуляции логики приложения. Другими словами, контроллеры отвечают за обработку входящих запросов, выполнение операций над моделью предметной области и выбор представлений для визуализации пользователю.

Пример приложения

Для целей этой и следующих статей мы создадим новый проект MVC по имени ControllersAndActions с использованием шаблона Empty (Пустой), отметив флажок MVC в разделе Add folders and core references for (Добавить папки и основные ссылки для), а также проект модульного тестирования под названием ControllersAndActions.Tests. Модульные тесты, которые будут создаваться, не требуют имитированных реализаций, поэтому пакет Moq устанавливать не придется, но нужно установить пакет MVC, чтобы тесты имели доступ к базовым классам контроллеров.

В окне консоли диспетчера пакетов NuGet среды Visual Studio введите следующую команду:

Install-Package Microsoft.Aspnet.Mvc -version 5.0.0 -projectname ControllersAndActions.Tests

После создания проекта выберите пункт ControllersAndActions Properties (Свойства ControllersAndActions) в меню Project (Проект) среды Visual Studio, в открывшемся диалоговом окне перейдите на вкладку Web (Веб) и отметьте переключатель Specific Page (Определенная страница) в категории Start Action (Начальное действие). Вводить какое-либо значение не нужно - достаточно только выбора переключателя.

Понятие контроллера

Вы сталкивались со случаями использования контроллеров почти во всех предшествующих статьях, посвященных ASP.NET MVC. Наступило время заглянуть "за кулисы".

Создание контроллера с помощью интерфейса IController

В MVC Framework классы контроллеров должны реализовать интерфейс IController из пространства имен System.Web.Mvc , показанный в примере ниже:

Using System.Web.Routing; namespace System.Web.Mvc { public interface IController { void Execute(RequestContext requestContext); } }

Чтобы получить определение этого интерфейса, необходимо загрузить исходный код MVC Framework , который чрезвычайно полезен для выяснения внутренней работы средств инфраструктуры.

Как видите, интерфейс IController очень прост. Его единственный метод Execute() вызывается, когда запрос направляется этому классу контроллера. Инфраструктура MVC Framework выясняет, на какой класс ориентирован запрос, за счет чтения значения свойства controller, сгенерированного данными маршрутизации, или через специальные классы маршрутизации.

Создавать классы контроллеров можно путем реализации интерфейса IController, однако поскольку этот интерфейс является низкоуровневым, потребуется проделать немало работы, чтобы получить, в конечном счете, что-нибудь полезное. Тем не менее, интерфейс IController полезен для демонстрации оперирования контроллеров и с этой целью в папке Controllers создается новый файл класса по имени BasicController.cs, содержимое которого приведено в примере ниже:

Using System.Web.Mvc; using System.Web.Routing; namespace ControllersAndActions.Controllers { public class BasicController: IController { public void Execute(RequestContext requestContext) { string controller = (string)requestContext.RouteData.Values["controller"]; string action = (string)requestContext.RouteData.Values["action"]; requestContext.HttpContext.Response.Write(string.Format("Контроллер: {0}, Метод действия: {1}", controller, action)); } } }

Методу Execute() интерфейса IController передается объект RequestContext , предоставляющий информацию о текущем запросе и маршруте, который ему соответствует (и приводит к вызову данного контроллера для обработки этого запроса). В классе RequestContext определены два свойства, описанные в таблице ниже:

Объект HttpContextBase предоставляет доступ к набору объектов, описывающих текущий запрос, которые называются объектами контекста; мы еще вернемся к ним позже. Объект RouteData описывает маршрут. Важные свойства класса RouteData перечислены в таблице ниже:

В статье Настройка системы маршрутизации было показано, как использовать типы RouteBase и IRouteHandler для настройки системы маршрутизации. В рассматриваемом примере с помощью свойства Values получаются значения переменных сегментов controller и action, которые затем записываются в ответ.

Часть проблемы, возникающей при создании специальных контроллеров, связана с отсутствием доступа к таким средствам, как представления. Это означает, что придется работать на более низком уровне, чем и объясняется запись содержимого напрямую в ответ клиенту. Свойство HttpContextBase.Response возвращает объект HttpResponseBase , который позволяет конфигурировать и добавлять содержимое к ответу, предназначенному для отправки клиенту. Это еще одна точка соприкосновения между платформой ASP.NET и инфраструктурой MVC Framework.

Если запустить приложение и перейти на URL вида /Basic/Index, то специальный контроллер сгенерирует вывод, показанный на рисунке ниже:

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

Классы с именами, заканчивающимися на Base

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

В Microsoft решили ввести возможность тестирования, поддерживая при этом совместимость с существующими приложениями ASP.NET Web Forms, так что в результате появились классы Base . Эти классы так называются из-за того, что они имеют те же самые имена, как у основных классов платформы ASP.NET, за которыми следует слово Base.

Так, например, платформа ASP.NET предоставляет контекстную информацию о текущем запросе и ряде ключевых служб приложения через объект HttpContext. Соответствующим ему классом Base является HttpContextBase, экземпляр которого передается методу Execute(), определенному в интерфейсе IController (в последующих примерах будут продемонстрированы и другие классы Base). В первоначальных классах и классах Base определены одни и те же свойства и методы, но классы Base всегда абстрактны , а это значит, что их легко применять для модульного тестирования.

Иногда вы получите экземпляр одного из первоначальных классов ASP.NET, такого как HttpContext. В таком случае необходимо создать дружественный к MVC класс Base, подобный HttpContextBase. Это делается с использованием одного из классов Wrapper , которые имеют такие же имена, как первоначальные классы, дополненные словом Wrapper, например, HttpContextWrapper . Классы Wrapper являются производными от классов Base и имеют конструкторы, которые принимают экземпляры первоначальных классов:

Первоначальные классы, классы Base и классы Wrapper определены в пространстве имен System.Web, поэтому платформа ASP.NET может гладко поддерживать приложения MVC Framework и более старые приложения Web Forms.

Создание контроллера за счет наследования от класса Controller

Как было продемонстрировано в предыдущем примере, инфраструктура MVC Framework допускает практически неограниченную настройку и расширение. Чтобы обеспечить любой требуемый вид обработки запросов и генерации результатов, можно реализовать интерфейс IController. Вам не нравятся методы действий? Вы не хотите беспокоиться по поводу визуализированных представлений? В таком случае можете взять дело в свои руки и реализовать лучший, быстрый и более элегантный способ обработки запросов. Либо же вы можете воспользоваться средствами, предлагаемыми командой разработчиков MVC Framework из Microsoft, и унаследовать свои контроллеры от класса System.Web.Mvc.Controller .

Класс Controller обеспечивает поддержку обработки запросов, которая знакома большинству разработчиков приложений MVC. Она применялась во всех примерах, рассмотренных в предыдущих статьях. Класс Controller предоставляет три ключевых средства, которые описаны ниже:

Методы действий

Поведение контроллера разнесено по множеству методов (вместо реализации в виде единственного метода Execute()). Каждый метод действия отображается на соответствующий URL и вызывается с параметрами, извлеченными из входящего запроса.

Результаты действий

Можно возвращать объект, описывающий результат выполнения действия (например, визуализация представления либо перенаправление на другой URL или метод действия), и затем обрабатывать его каким угодно образом. Разделение между указанием результатов и их выполнением упрощает модульное тестирование.

Фильтры

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

Если только вы не имеете дело со специфичным требованием, то лучшим подходом к созданию контроллеров будет их наследование от класса Controller, что, как и можно было ожидать, делает среда Visual Studio, когда создает новый класс в ответ на выбор пункта меню Add --> Scaffold (Добавить --> Шаблон).

В примере ниже приведен код простого контроллера под названием DerivedController, созданного подобным образом. Он сгенерирован с применением варианта MVC 5 Controller - Empty (Контроллер MVC 5 - Пустой) с несколькими простыми изменениями, предназначенными для установки свойства ViewBag и выбора представления:

Using System; using System.Web; using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class DerivedController: Controller { public ActionResult Index() { ViewBag.Message = "Привет из контроллера DerivedController метода действия Index"; return View("MyView"); } } }

Класс Controller также обеспечивает связь с системой представлений Razor. В этом примере мы возвращаем результат вызова метода View(), которому в качестве параметра передается имя представления для визуализации клиенту. Чтобы создать это представление, создайте папку Views/Derived, щелкните на ней правой кнопкой мыши и выберите в контекстном меню пункт Add --> MVC 5 View Page (Razor) (Добавить --> Страница представления MVC 5 (Razor)). Укажите в качестве имени MyView.cshtml и щелкните на кнопке ОК для создания файла представления.

Приведите содержимое файла в соответствие с примером:

@{ ViewBag.Title = "Index"; }

MyView

Сообщение от контроллера: « @ViewBag.Message »

После запуска приложения и перехода на URL вида /Derived/Index этот метод действия вызывается, а представление MyView визуализируется:

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

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

MVC расшифровывается как Model- view- controller, а дословно перевести можно как Модель-Представление-Контроллер.

Несмотря на то, что модель разработки кажется новой оно уже давно себя зарекомендовала и получила повсеместное использования при разработке в том числе и сайтов. Впервые концепция MVC была описана Trygve Reenskaug в 1979 году.

Концепция MVC или из чего она состоит

Модель MVC включает в себя три компонента: Модель, Представление и Контроллер.

Самое главное конечно здесь первое – это Модель. Модель представляет собой совокупность процедур и алгоритмов обработки данных. Сама по себе Модель не содержит данных, но как правило черпает их из базы данных и обрабатывает по заранее прописанным алгоритмам. Если говорить о Web разработке, то модель будет содержать набор классов и функций, например на языке PHP.

Второй элемент – это представление View. Позволяет отобразить информацию. Если это сайт, то информация отображается в браузере. Представление при разработке сайтов содержит HTML код, в который подставляются переменные, которые берутся, нет не из модели, а из контроллера.

Итак, третий элемент – это Контроллер. Его главная функция это обеспечение связи в пользователем и моделью. Также может содержать PHP код.

Многие начинающие разработчики не используют Модель или используют ее только для доступа к базе данных, что является главной ошибкой, неправильно и противоречит логике MVC модели. А весь основной код помещают в Контроллер. Последствиями этого является во-первых огромный код, а во-вторых более низкая скорость работы приложения. Ну и вносить изменения в код таких приложений значительно сложнее.

Для примера рассмотрим модель MVC для новостного сайта

Пользователь попадает на страницу сайта. Ему отображается страница по умолчанию с определенным списком новостей. Информация для формирования новостей берется из базы данных.

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

Модель, получив необходимы данные, уже обработанные и сгруппированные в объект или массив сгенерирует запрос к базе данных. Получив данные, Модель формирует их в определенный вид и передает их Контроллеру, а затем передадутся Представлению. Не будет явятся ошибкой, если данные передадутся сразу в Представление. Но опять таки используются лишние действия, которые приводят в итоге к более долгой загрузке сайта и приложений.

Представление, получив массив или объект с новостями подгружает определенный код HTML, CSS, если нужно и jаvascriptи отображает все это пользователю.

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

Таким образом, используя модель MVC можно без проблем составить систему администрирования для сайта , Интернет-приложение. Так фреймфорк CodeIgniter использует именно эту модель.

Разработка приложения в соответствии с шаблоном проектирования MVC (модель-представление-контроллёр) характерна для Java и применительно к DroidScript кажется непонятной и ненужной. Для чего всё усложнять? Ореол сложности и "магичности" MVC приобрёл по причине использования при его рассмотрении красивых, но непонятных слов (концепция, модель, бизнес-логика, паттерн) и сложных демонстраций в контексте Java. Всё намного проще: MVC - это один из шаблонов проектирования, при котором производится дополнительное разделении кода в объектно-ориентированной среде .

Центральным элементом MVC-модели является контроллёр - обычное приложение DroidScript, из которого вынесен код, относящийся к визуальной разметке и внешнему оформлению виджетов, а также данные и методы доступа к ним. Под данными мы привыкли понимать информацию, хранящуюся в массивах, файлах, базах данных. Но в концепции MVC данные понимаются в широком смысле слова - это всё, что не является кодом приложения:

  • внешние данные из файлов и баз данных - метаданные, текст, графика, звуки, музыка и др.
  • внутренние данные приложения - строки с надписями на кнопках и других элементах управления, текст в диалоговых окнах, описание стилей, константы, программно генерируемая графика и др.

С точки зрения пользователя его работа с приложением при использовании MVC не изменилась: он также нажимает на кнопки, выбирает данные и способ их отображения. Изменения могут касаться удобства этой работы. А на стороне разработки изменения ощутимы: взаимодействие между данными и их отобржением в концепции MVC происходит через контроллёр и под его управлением .

Рассмотрим для начала простой пример использования MVC в однофайловом приложении.

Однофайловая реализация MVC-модели

Возьмём простое приложение.

Function OnStart(){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton("Показать версию", 0.3, 0.1); _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _btnShowVersion.SetOnTouch(function(){ _btnShowVersion.SetText("Версия приложения 1.0"); }); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay); }

На первый взгляд всё кажется неплохо, но предположим, что необходимо изменить цветовую схему приложения и выводить надписи на нескольких языках. Это приведёт к сложностям, так как все данные в показанном примере являются фиксированными значениями (литералами). Это заметно снижает гибкость кода и усложняет его отладку и поддержку.

Другой недостаток состоит в том, что данные - надписи на кнопке, разметка - методы отображения виджетов и действие - блок кода, изменяющий надпись на кнопке при нажатии на неё находятся в одном блоке и в одном файле. То есть, для изменения надписи нужно открыть этот файл и получить доступ ко всему коду приложения. Это похоже на то, как если бы для замены колеса автомобиля нужно было разбирать корпус автомобиля для получения доступа ко всему содержимому. Для чего? В процессе разборки корпуса автомобиля можно что-то случайно зацепить и привести его в нерабочее состояние. Также возможно и в коде: хотел заменить название строки в одном месте, но замена произошла во всём файле, что привело к появлению россыпи ошибок. Или хотел только изменить цвет кнопки, но случайно зацепил находящийся рядом код и перестало работать всё приложение.

Одна из задач шаблона MVC как раз и состоит в разграничении доступа: сначала определяется модуль (или блок кода), являющийся источником ошибки, а затем даётся доступ только к нему. Для чего давать доступ к электронике и мотору автомобиля, если нужно заменить колесо?

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

Реализуем показанный выше пример в контексте MVC. Для этого весь код нужно разделить и сгруппировать в соответствующих блоках. Порядок следования блоков в коде не важен, но лучше придерживаться логики: для работы контроллёра необходимы и данные, и элементы для их отображения, поэтому он ставится последним. В момент отображения данных они должны существовать. Значит, блок модели идёт первым:

  1. Модель
  2. Представление
  3. Контроллёр
//+++ модель (function(){ var _obj = ; //+++ данные var _version = "Версия приложения 1.0"; var _titleShowVersion = "Показать версию"; //--- данные
//+++ открытые методы для доступа к данным _obj.getVersion = function(){ return _version; } _obj.btnGetTitle = function(){ return _titleShowVersion; } //--- открытые методы для доступа к данным window.model = _obj; // открываем доступ к локальному объекту })(); //--- модель //+++ представление (function (){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton(window.model.btnGetTitle(), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion"; _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay);

})(); //--- представление //+++ контроллёр (function(p_object){ var _obj = ; // открытый метод поиска объекта _obj.findObjectById = function(p_name){ var _objectList = app.GetObjects(); for (var _i in _objectList){ if(_objectList[_i].name == p_name){ return _objectList[ _i]; } } return null; } window.control = _obj; })(); function OnStart(){ var _buttonShowVersion = window.control.findObjectById("_btnShowVersion"); //+++ действие _buttonShowVersion.SetOnTouch(function(){ this.SetText(window.model.getVersion()); }); // --- действие } //--- контроллёр

Из-за разделения функций код приложения увеличился в несколько раз.

Изначально все переменные сделаны закрытыми и только в конце при необходимости открывается доступ к ним через глобальный объект window, что позволяет обойтись без глобальных переменных.

В примере реализован поиск виджета, как в Java, но можно поступить проще и сделать код более эффективным, открыв доступ к объекту через глобальный ассоциативный массив:

Window.controls = ;
window.controls.buttonShowVersion = _btnShowVersion;

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

Не обязательно отделять друг от друга все эти три составляющие. Существует несколько вариаций MVC, а также неполные реализации этой модели. Например, можно отделить данные, а код действий объединить с элементами управления при помощи анонимных функций обратного вызова.

При работе в объектно-ориентированном среде разделение кода и данных уже присутствует изначально: данные и действия группируются в классах, объекты взаимодействуют друг с другом посредством открытых методов и тд. Благодаря MVC осуществляется более тонкое и явное разделение кода и данных по их основным функциям.

Для более глубокого понимания преимуществ использования модели MVC рассмотрим разделение кода по отдельным файлам.

Трёхфайловая реализация MVC-модели

Разделение кода по разным файлам используется для более удобной работы с ним. Огромное количество мелких файлов, которые можно видеть в MVC проектах, может поставить под сомнение это утверждение, но видеть файлы - это одно, а работать с ними - совсем другое. В каждый момент времени разработчик взаимодействует с одним файлом из какого-то небольшого их множества. Для этого необходимо хорошо понимать структуру организации проекта и постоянно отслеживать тройку файлов - модель, представление и контроллёр, чтобы случайно не отредактировать сторонний код. Из-за ограничений редактора DroidScript такая группировка возможна только по именам файлов в корневой директории, например:

myproject_model.js - модель
myproject_view.js - представление
myproject_control.js - контроллёр

Ниже показан пример разделения кода предыдущего примера по файлам.

myproject_model.js - модель (function(){ var _obj = ; //+++ данные var _version = "Версия приложения 1.0"; //--- данные //+++ строковый ресурс var _titleShowVersion = "Показать версию"; //+++ строковый ресурс _obj.getVersion = function(){ return _version; } _obj.btnGetTitle = function(){ return _titleShowVersion; } window.model = _obj; })(); myproject_view.js - представление (function (){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton(window.model.btnGetTitle(), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion"; _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay); })(); myproject_control.js - контроллёр app.LoadScript("myproject_model.js"); app.LoadScript("myproject_view.js"); (function(p_object){ var _obj = ; // метод поиска объекта _obj.findObjectById = function(p_name){ var _objectList = app.GetObjects(); for (var _i in _objectList){ if(_objectList[_i].name == p_name){ return _objectList[ _i]; } } return null; } window.control = _obj; })(); function OnStart(){ var _buttonShowVersion = window.control.findObjectById("_btnShowVersion"); //+++ действие _buttonShowVersion.SetOnTouch(function(){ this.SetText(window.model.getVersion()); }); // --- действие }

Такое простое разделение кода по файлам получилось не спроста. Для этого заранее была установлена связь с моделью через открытое свойство глобального корневого объекта - window.model , а связь с представлением через глобальный массив _map посредством метода app.GetObjects .

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

Из сказанного следует то, что качественно спроектированный интерфейс позволяет заметно упростить последующую интеграцию модулей.

В JavaScript объекты передаются по ссылке. Изменение свойств виджета в контроллёре изменит свойства самого виджета. Теоретически, можно отделить объекты представлений от объектов кода, как это сделано в Java, где в качестве первых используются xml-структуры, но большого смысла в этом нет по двум причинам - отсутствия в DroidScript визуального редактора интерфейса и ограниченного набора доступных свойств API-объектов.

Многофайловая реализация MVC-модели

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

Создание HTML-элементов

Итак, в предыдущей статье мы рассмотрели создание и настройку HTML-дескрипторов веб-форм. HTML-форма не имеет особого смысла до тех пор, пока не будут созданы некоторые элементы управления вводом (например, ). В таблице ниже описаны базовые вспомогательные методы, которые доступны для создания элементов , и приведены примеры генерируемой ими HTML-разметки. Во всех этих вспомогательных методах первый аргумент используется для установки значений атрибутов id и name элемента , а второй аргумент служит для установки значения атрибута value.

Базовые вспомогательные методы HTML для создания элементов управления вводом
Метод Пример Генерируемый HTML-элемент
Html.CheckBox() Html.CheckBox("myCheckbox", false)
Html.Hidden() Html.Hidden("myHidden", "val")
Html.RadioButton() Html.RadioButton("myRadiobutton", "val", true)
Html.Password() Html.Password("myPassword", "val")
Html.TextArea() Html.TextArea("myTextarea", "val", 5, 20, null)
Html.TextBox() Html.TextBox("myTextbox", "val")

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

в предыдущей статье.

Обратите внимание, что вспомогательный метод для флажка (Html.CheckBox()) генерирует два элемента : собственно флажок и скрытый элемент с тем же самым именем. Причина в том, что браузеры не отправляют значения для флажков, если те не отмечены. Наличие скрытого элемента управления гарантирует, что ASP.NET MVC Framework получит значение из скрытого поля, когда такое произойдет.

Использование этих базовых вспомогательных методов для создания элементов управления вводом демонстрируется в примере ниже:

Создать пользователя

@Html.TextBox("userId", @Model.UserId)
@Html.TextBox("firstName", @Model.FirstName)
@Html.TextBox("lastName", @Model.LastName)
}

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

...

....

Генерация элемента управления вводом из свойства модели

Вспомогательные методы, используемые в предыдущем разделе, работают хорошо, но нам по-прежнему необходимо гарантировать, что значение, передаваемое в первом аргументе, соответствует значению модели, которое передается во втором аргументе. Если эти значения не согласованы, инфраструктура ASP.NET MVC Framework не сможет воссоздать объект модели из данных формы, поскольку атрибуты name и значения элементов формы не соответствуют друг другу.

Для каждого метода, перечисленного в таблице выше, доступна перегруженная версия, которая принимает единственный аргумент типа string:

@model HelperMethods.Models.User @{ ViewBag.Title = "CreateUser"; }

Создать пользователя

@using (Html.BeginRouteForm("FormRoute", null, FormMethod.Post, new { @class = "userCssClass", data_formType="user" })) {
@Html.TextBox("userId")
@Html.TextBox("firstName")
@Html.TextBox("lastName")
}

Аргумент string применяется для поиска в данных представления, в объекте ViewBag и в модели представления соответствующего элемента данных, который может использоваться в качестве основы для элемента input. Таким образом, например, если вызвать @Html.TextBox("DataValue"), то инфраструктура ASP.NET MVC Framework попытается найти элемент данных, который соответствует ключу DataValue. Будут проверяться следующие местоположения:

    ViewBag.DataValue

    @Model.DataValue

Первое найденное значение используется для установки атрибута value генерируемой HTML-разметки. (Последняя проверка, в @Model.DataValue, предпринимается, только если модель представления содержит свойство или поле по имени DataValue.)

Если указать строку вроде DataValue.First.Name, поиск становится более сложным. Инфраструктура ASP.NET MVC Framework опробует различные комбинации элементов, разделяемых точками, такие как перечисленные ниже:

    ViewBag.DataValue.First.Name

    ViewBag.DataValue["First"].Name

    ViewBag.DataValue["First.Name"]

    ViewBag.DataValue["First"]["Name"]

Проверяются многие перестановки. И снова будет использоваться первое найденное значение, завершая поиск. С этим приемом связана очевидная проблема производительности, однако помните, что внутри объекта ViewBag обычно содержится совсем немного элементов, так что поиск среди них не должен отнимать значительное время.

Использование строго типизированных вспомогательных методов для создания элементов управления вводом

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

Строго типизированные вспомогательные методы для создания элементов управления вводом
Метод Пример Генерируемый HTML-элемент
Html.CheckBoxFor() Html.CheckBoxFor(х => х.IsApproved)
Html.HiddenFor() Html.HiddenFor(x => x.FirstName)
Html.RadioButtonFor() Html.RadioButtonFor(x => x.IsApproved, "val")
Html.PasswordFor() Html.PasswordFor(x => x.Password)
Html.TextAreaFor() Html.TextAreaFor(x => x.Bio, 5, 20, new{})
Html.TextBoxFor() Html.TextBoxFor(x => x.FirstName)

Строго типизированные вспомогательные методы для создания элементов управления вводом работают с лямбда-выражениями. В такое выражение передается объект модели представления, в котором можно выбрать поле или свойство, используемое для установки атрибута value. В примере ниже показано, как этот вид вспомогательного метода применяется в представлении CreateUser.cshtml из примера приложения:

@model HelperMethods.Models.User @{ ViewBag.Title = "CreateUser"; }

Создать пользователя

@using (Html.BeginRouteForm("FormRoute", null, FormMethod.Post, new { @class = "userCssClass", data_formType="user" })) {
@Html.TextBoxFor(user => user.UserId)
@Html.TextBoxFor(user => user.FirstName)
@Html.TextBoxFor(user => user.LastName)
}

Генерируемая этими вспомогательными методами HTML-разметка ничем не отличается от приведенной ранее. Тем не менее, использовать в проектах именно строго типизированные вспомогательные методы предпочтительнее, т.к. это уменьшает вероятность внесения ошибок за счет некорректного написания имен свойств.

Создание элементов выбора

В таблице ниже описаны вспомогательные методы HTML, предназначенные для создания элементов выбора (select). Они могут применяться для выбора одиночного элемента из раскрывающегося списка или для обеспечения одновременного выбора множества элементов. Как и в случае других элементов формы, доступны слабо и строго типизированные версии этих вспомогательных методов:

Вспомогательные методы HTML, генерирующие элементы выбора
Метод Описание Пример Генерируемый HTML-элемент
Html.DropDownList() Раскрывающийся список Html.DropDownList("myList",
new SelectList(new {"A", "B"}), "Выбрать")
Html.DropDownListFor() Раскрывающийся список Html.DropDownListFor(x => x.Gender,
new SelectList(new {"M", "F"}))
Html.ListBox() Html.ListBox("myList",
Html.ListBoxFor() Список с множественным выбором Html.ListBoxFor(x => x.Vals,
new MultiSelectList(new {"A", "B"}))

Вспомогательные методы, генерирующие элементы выбора, принимают параметры типа SelectList или MultiSelectList . Разница между этими классами заключается в том, что MultiSelectList имеет опции конструктора, которые позволяют указывать возможность выбора более одного элемента при начальной визуализации страницы.