Pull to refresh

Comments 86

>Возьмите на вооружение Частичные Представления (Partial Views) в связке с Ajax запросами
взял
Partial Views не только из-за Ajax советуют использовать но и чтоб не повторять свой код(DRY) та же форма логина может много где понадобится.
Например тут на хабре если не залогинен показывают сообщение:

«Только авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста.»

А можно было бы показать форму и она была бы точно такой же(валидация, html, POST uri).

Статья хорошая, html хелперы ещё не довелось самому юзать, попробую спасибо.

Пункт 3 немного не понял, наверное путают русские названия «Представления и Модели представления»:
Вы имеете ввиду для той же пресловутой логин формы делать свою модель например UsersLoginModel например = pastebin.com/THSTaaxQ?
То, о чем Вы говорите — это по сути и есть первоначальное назначение Partial Views.
html хелперы — попробуйте один раз и потом не сможете отказаться. это поистине удобно и универсально.
Удобно и универсально, но если уж говорить о совсем правильной концепции — то некошерно :)
Под-шаблоны и мастер-шаблоны вообще должны объявляться декларативно (и автоматически не показываться, если элемента, который они собираются отобразить, отсутствует в текущей ViewModel по какой-либо причине).
Однако я, собственно, не отрицаю. В текущем состоянии ASP.NET MVC деваться некуда (при использовании WebFormsViewEngine, и, как я понимаю, в Razor тоже).
Насчёт 3 пункта я правильно понял, что под каждый View нужно делать отдельный ViewModel например — pastebin.com/THSTaaxQ, а DomainModel использовать для манипуляции с бд?

Просто в том же Nerddiner они передают DomainModel в View и обратно и вроде говорят что так и нада. Не холивара ради, а что знать как правильно делать.
Да, почти все правильно. ViewModel — специфичная модель, заточенная под специфичный View. Одна ViewModel на один View.

То, что они передают DomainModel во View означает только то, что у них от DomainModel одно название. Они ее, по всей видимости используют и для получения данных во View, и для сохранения этих данных в базе. Можете назвать ее просто «модель», если хотите.

«Domain Model» по терминологии и концепции DDD (Domain-Driven Design) — набор сущностей в виде POCO-классов, ничего не знающих о том, где данные хранятся (об этом знают Репозитории) и как хранятся и ничего не знают о View (и о контроллерах), поскольку Domain Model — это в чистом виде бизнес-логика. Ее можно использовать не только в Web MVC-приложении, но и в любом другом, и она от этого не поменяется.
Вах, какой развёрнутый ответ, спасибо большое, всё встало на свои места.
Получается по мере нужды ViewModel практически имеет те же классы что и DomainModel, но на своё манер то что нужно под View.

У меня всё так как вы написали, DomainModel — репозиторий. Но в ViewModel если делал специфичную ViewModel использовал некоторые классы из DomainModel( Person etc...).

Видимо так не правильно?
«Получается по мере нужды ViewModel практически имеет те же классы что и DomainModel, но на своё манер то что нужно под View.»
Совершенно верно.

Так, идем дальше. Начнем с того, что DomainModel — это не репозиторий. Репозитории — это средство сохранения (persistence) для сущностей из Domain Model.

По поводу ViewModel — тут смотря что означает использование ViewModel некоторых классов из DomainModel.
Допустим, у меня есть вьюшка — PersonEditView, для нее есть, соответственно, PersonEditViewModel. Так вот, эта PersonEditViewModel вполне может использовать (содержать) как Value Object'ы, определенные специально для ViewModel'и (что рекомендуется), так и сущности доменной модели (в последнем случае необходимо следить, чтобы с сущностями на уровне View не происходило ничего лишнего — так как сущности могут содержать определенные методы, а на View нам их нужно только вывести).
Ага ясно, просто всё что не читал там в ViewModel(уточним данные которые посылаются из метода контролера в View) использовали Entity's домен модели(созданные Linq to Sql или EF).

Можно ещё вопрос тогда, если мы посылаем модель в view(соответственно view будет строго типизированно под эту модель). Если наша View предназначенна для редактирования назад в POST мы получим ту же модель, правильно?

Но что если мы хотим получить не всю модель, вот гляньте на этот пример pastebin.com/eQDkfYm0 а только часть, чтоб не гонять в POST ненужные данные
Вот как назад получить только RoleIDsCheckeDs и Model.UserToEdit.id
При этом не потеряв прелестей ModelState.IsValid

Если я несу бред не удивляйтесь, я тут 4день сижу над View с 3мя PartialView внутри(в нутри каждого PartialView форма...) и эти модели мне уже снятся.
«Ага ясно, просто всё что не читал там в ViewModel(уточним данные которые посылаются из метода контролера в View) использовали Entity's домен модели(созданные Linq to Sql или EF).»
Ну, технически то так можно, но это (IMHO) в высшей степени неверно с точки зрения поддерживаемой архитектуре. Получается, что у нас во View есть нечто (DataContext в случае L2S, ObjectContext в случае EF), что знает о способах доступа к данным. Немного меняется ситуация с использованием последней версии Entity Framework 4.0 — там есть поддержка POCO.

«Если наша View предназначена для редактирования назад в POST мы получим ту же модель, правильно?»
Уточните в чем именно вопрос? В общем случае — для конкретного View должен соблюдаться принцип "One Model In — One Model Out".

"… если мы хотим получить не всю модель"
Опять же, в общем случае — для этого ViewModel и существует, она в каком-то роде «огрызок» общей модели, и содержит только то, что нужно для текущего View и это касается не только самих объектов, но и их содержимого.
В кранем случае, для сохранения прелестей ModelState можно реализовать свой ModelBinder.

«Если я несу бред не удивляйтесь»
Отнюдь не бред. Со всеми этими вопросами неизменно сталкивается любой, кто пытается всю систему реализовать по уму.

Для лучшего понимания «как должно быть» (ибо MVC Framework позволяет многие вещи сделать слишком по-разному, что зачастую даже не очень гуд), советую посмотреть вот этот набор принципов, называемый Jeremy Miller'ом <ahref=«codebetter.com/blogs/jeremy.miller/archive/2008/10/23/our-opinions-on-the-asp-net-mvc-introducing-the-thunderdome-principle.aspx»>Thunderdome Principle.
Статья хорошая, её надо перечитывать каждый раз когда садишься за VS.
Принцип «One Model In — One Model Out» — именно и стал стеной в которую я упёрся.

Я решил на странице редактирования сделать 3 PartialView, чтоб редактировать данные по частям. Удивило что PartialView должен быть той же модели, что и view в котором мы его вызываем. Тут и начались танцы с бубном вокруг моделей. В конце они у меня выводятся, формы при валидации перенапрявляются на Action который я указал. Но это всё неправильно, вообщем намучился.

Я думал PartialView подгружает html где я укажу(например форму), а выходит он для отображения данных той же модели View в которой и вызывается.

Спасибо за ответы, очень многое прояснили — буду исправлять ошибки.

Вот думаю может RenderAction попробовать для этой цели, они свою модель могут брать из ActionResult.
Кстати вот статья не последнего человека в asp.net mvc насчёт валидации и модолей. bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html

Там кроме всего прочего обсуждается проблема «Over-Posting», при ViewModel которую вы предлагаете, такого вообще не должно случатся.

Странно, что многие гуру asp.net mvc пропагандируют в своих книжках как раз таки использовать Entity от EF или L2S.

Получается если накосячить и неправильно использовать Entity там не только Over-Posting этого Entity светит, но и всех связанных с ним.
Вот здесь еще посмотрите, может быть найдете ответы на какие-нибудь вопросы.

Тема сама по себе сложная, очень много вариантов всего и вся, много всего можно сделать по-разному, что сильно усложняет поиск оптимального решения.
Статью которую вы давали раньше было написано никогда желательно не использовать ViewData[""] а тут ViewData.Eval.

То как он описывает строготипизированный Page view, всё исходит от одной большой ViewModel и расходится по разным частям. Тоесть мы должны иметь одну большую модель, в которой будут под модели. И это всё надо поддерживать в том плане, что если RenderPartial наш имеет форму, то при возврате назад из формы на страницу мы должны заново создать всю модель.

У меня было нечто похожее(связи в самих Entity:() и это то чего я хотел избежать, не таскать за собой лишнее:(

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

Это вроде цикл статей? Пойду дальше смотреть что пишет. Но то что он описал в части статьи про «Page view» именно то что я делал))
«Получается, что у нас во View есть нечто (DataContext в случае L2S, ObjectContext в случае EF), что знает о способах доступа к данным.»
Это, на самом деле, не так. И если отрубить lazy loading, то с точки зрения view объектиы L2S или EF будут мало чем отличаться от POCO.
Однако это ТЕ самые объекты, которых там быть не должно бы.
Аналогично, во время тестирования на всех уровнях, бремя этих самых контекстов мешает нормально создавать Mock-объекты.
«Однако это ТЕ самые объекты, которых там быть не должно бы.»
датаконтексты? так их во вью и нет нигде, вообще-то. Они фигурируют только в контроллере или даже дальше.
что такое хелперы? что такое Модели Представлений?
кто вы такие? я вас не звал, идите… (с)
Ну для начала хоть уж спросите, что такое MVC. Ради приличия, ну типа, как будто вообще не в теме и все такое :)
Ну MVC я как бы знаю, книжку Сандерса читал и видеолекции Магданурова смотрел, но как-то сразу во все въехать не получилось
1. По книжке Сандерсона — Using HTML Helper Methods стр. 332
2.
public ViewResult SuperAction()
{
var viewData = new SuperClass(...); // SuperClass — это модель представления
return View(viewData);
}
Просто интересно, зачем он «var» использует, когда можно было SuperClass?
п. 2 не из книги, а от меня:) Много было холиваров на тему того использовать ли var и когда. В данном случае (когда имеем дело с конструктором) даже противники var могут стерпеть его использование (наглядно видно какой тип), другое дело, когда имеет место такой код:

var order = OrdersManager.GetOrderById(id);

здесь тип явно не понятен и по этому спорят тут.
Хм, а мне всегда казалось, что Partial Views — это зло. (Но мне не приходилось на практике сталкиваться с asp.net mvc.)
Они — обязательный атрибут. Единственное, что пока в них не устраивает — они вызываются через Helper-класс.
Именно так все и использую. Долго доходить до этих советов не пришлось. Но хелперы использовать стараюсь везде где можно.
Все верно. Разве что еще забыли про бонус моделей, заточенных под представление — простота отображения, валидации и получения формы.

А дальше придете к WCF-сервисам + клиент на javascript (с шаблонизацией на клиенте же) =)
А зачем WCF + шаблонизация на клиенте? MVC подходит для большинства веб-сценариев. Для каких нужен WCF+js?

Хотя MS MVC не идеален некоторой перегруженностью и чрезмерной обобщенностью.
Я бы использовал, что-нибудь вроде Fubu MVC, если бы оно было в релизе — легковесное и по концепции ближе к Ruby On Rails.
Там, где нужна достаточно серьезная клиентская логика (т.е. там, где без js-а сильно сложнее/хуже/вообще невозможно обойтись). Тогда это решение позволить собрать всю логику представления в одном месте — в js.
Это да, но если, скажем, весь остальной проект использует MVC и его систему представлений — зачем тут использовать что-то другое?

Хотя может я не совсем понял, каким именно образом предполагается использовать здесь WCF-сервисы?
Ну, скажем в моем текущем проекте (это больше веб-приложение, нежели презентационный сайт) используется ASP.MVC только для формирования xml-я и обработки входящих xml-данных. А на клиенте эти данные (при помощи js-фреймворка Freja, блин, парсер оригинальную ссылку съедает) шаблонизируются, распихиваются по нужным местам страницы, отправляются на сервер.
С учетом того, что вся бизнес-логика находится вообще в отдельном проекте (вернее, даже нескольких), от ASP.MVC используется не так уж много, и эту функциональность можно реализовать через WCF-сервисы. Т.е. в браузере у меня полноценный js-клиент, а не просто html. Минус вижу пока только один — необходимость поддержки js на клиенте (что не так уж существенно). Зато плюсов очень много: это и малое время реакции (сервер всегда обрабатывает только необходимые данные), и очень слабая связанность компонентов, и нагрузка на сервер существенно снижается.
Понятно, ну то есть это просто вопрос архитектуры конкретного приложения (а точнее, даже целый подход к архитектуре). Мое личное мнение — не всегда то, что «можно» значит «удобнее». Вполне можно не перегружать сервер ненужными трансформациями шаблонов, выдавая собственный тип ActionResult'а, возвращаемого контроллером. Ну, впрочем, это зависит от остальной инфраструктуры, архитектуры, задачи и сценариев…

Действительно, если это больше JS-приложение, нежели HTML-приложение, тогда оно, по сути, ведет себя как «обычное old-good-десктопное приложение», тут из этого можно и исходить. Однако, я не думаю, что этот подход — в каком-то роде «выше» или «продвинутее» того, который предлагается Rails/ASP.NET MVC/Django — просто решаются разные задачи с использованием разной архитектуры — так что вовсе не факт, что от одного обязательно придем (read «дорастем») к другому.

По поводу бизнес-логики вообще отдельная большая тема. Кроме того, что она в «отдельных проектах» — это вообще отдельный слой (Domain Model), а за ним еще… и еще :)

Насчет же «слабой связанности компонентов» — эта тема еще больше и еще отдельнее, для начала тут надо сказать, что необходимо использовать IoC-контейнеры для любых объектов.
«но вы не должны определять формат даты время или парсить строки — это то, что для Представления должна делать Модель (Model)»
А вот это, очевидно, глупость.

То, что сегодня мы выводим дату по-русски, а завтра — по-американски — это в чистом виде представление данных. Если мы на каждый подобный чих будем менять модель, вся прелесть separation of concerns пропадет.
Я думаю тут имелось в виду вот что: в модели хранится дата в виде DateTime или как его там. Плюс хранится поле Timezone и Culture. При подготовке данных для представления (т.е. во ViewModel) дата пересчитывается на основе Timezone, форматируется в зависимости от текущей Culture и отдается в виде строки.
«Плюс хранится поле Timezone и Culture. При подготовке данных для представления (т.е. во ViewModel) дата пересчитывается на основе Timezone, форматируется в зависимости от текущей Culture и отдается в виде строки.»
Зачем? Это означает, что стоит нам захотеть поменять формат вывода (длинная дата — короткая) нам придется править код где-то в маппере model-viewmodel. В то время как это отчетливая функция презентационного слоя.
«Это означает, что стоит нам захотеть поменять формат вывода (длинная дата — короткая) нам придется править код где-то в маппере model-viewmodel. В то время как это отчетливая функция презентационного слоя. „
А как вам такой пример использования: пользователю дается выбор текущей культуры и формата. Такие настройки явно будут храниться в модели. И тут либо мы имеем логику по формированию строки во ViewModel (не забываем, что этот уровень отвечает за подготовку данных для View) либо логику во View.

Причем если логику работы ViewModel в данном случае мы можем проверить тестами, то логику работы View — тока визуально оценив как оно отформатировалось в HTML.

Summary: в случае использования паттерна Model-View-Controller — ваш подход оправдан, но имеем проблемы с тестированием View.
В случае использования паттерна Model-View-ViewModel подготовка данных для отображения должна быть максимально сконцентрирована во ViewModel, при этом View должна быть тупой с минимумом логики. В этом случае можно будет на ViewModel написать тесты на проверку форматирования/подготовки данных, реакции на команды, etc. А т.к. задача View только отобразить уже готовые данные — скорее всего все сразу и заработает.
«А как вам такой пример использования: пользователю дается выбор текущей культуры и формата.»
А если не пользователю (а в дизайне жестко прошито)?

«в случае использования паттерна Model-View-Controller — ваш подход оправдан, но имеем проблемы с тестированием View.»
Зато не имеем проблем с тем, где что определяется и меняется. Особенно это место становится смешным, когда выясняется, что за отображение даты начинает отвечать control на стороне view, и ему надо передавать datetime объектом.
>А если не пользователю (а в дизайне жестко прошито)?
ну тогда либо культуру жестко во ViewModel зашить, либо форматировать во вьюхе, я вам об этом уже написал.

>Зато не имеем проблем с тем, где что определяется и меняется.
В случае MVVM тоже — все данные для представления подготавливаются во ViewModel, это его ответственность. Ответсвенность вьюхи — взять данные и пульнуть на страницу _без какой-либо обработки_.

>Особенно это место становится смешным, когда выясняется, что за отображение даты начинает отвечать control на стороне view, и ему надо передавать datetime объектом.

Я не пытаюсь вам продать MVVM (паттерны всякие нужны, паттерны всякие важны), я пытаюсь показать вам, в каких случаях ваше изначальное замечание «А вот это, очевидно, глупость.» неверно.
«Ответсвенность вьюхи — взять данные и пульнуть на страницу _без какой-либо обработки_.»
То есть, никакие контролы не используются вообще?

«Я не пытаюсь вам продать MVVM (паттерны всякие нужны, паттерны всякие важны), я пытаюсь показать вам, в каких случаях ваше изначальное замечание «А вот это, очевидно, глупость.» неверно.»
Угу. Вот только в оригинале — MVC (в цитате — Model и View, а не ViewModel). И в оригинале это дается безапеляционно, под лозунгом «всегда».

Это, очевидно, неверно.
Стоп, непонял. А что, для контролов данные не нужно подготавливать?

Пример: есть контрол, отображающий данные в таблице. На входе у контрола — коллекция каких-либо Item-ов. Контрол умеет отобразить эту коллекцию.
Т.е. задача вьюхи — связать контрол с коллекцией

Теперь представим себе такую ситуацию: нужно отобразить только те Item-ы для которых Criteria(Item) == true.
В случае MVC — получаем все items из модели, проверяем и берем только те которые соответствуют критерию.
В случае MVVM — ViewModel отвечает за подготовку данных, в том числе и фильтрацию. Т.е. View тупо, без какой либо логики берет коллекцию из ViewModel и отдает контролу. Логика проверки Item-ов на соответствие критерию — во ViewModel.
«Стоп, непонял. А что, для контролов данные не нужно подготавливать?»
Надо. Но separation of concerns съезжает. Там, где раньше за форматирование отвечала viewmodel, теперь отвечает контрол, который часть view.

«Теперь представим себе такую ситуацию: нужно отобразить только те Item-ы для которых Criteria(Item) == true.
В случае MVC — получаем все items из модели, проверяем и берем только те которые соответствуют критерию.»
Конечно, нет. Контроллер формирует модель, содержащую только нужные данные, а view их отображает все целиком.

Так что работа view в обоих случаях одинакова — достаньте данные из модели, отобразите их.
>Но separation of concerns съезжает.
>теперь отвечает контрол, который часть view
А для контрола — свой ViewModel :)

>Контроллер формирует модель, содержащую только нужные данные, а view их
>отображает все целиком.
… ну или так :)
«А для контрола — свой ViewModel»
Тем не менее, форматирование оказывается в двух местах. И очень высокая связанность.
Забавно, спорите и спорите тут :) Читаю, и думаю — когда же уже дойдут до решения :)

Размещение логики форматирования во View — нарушает Single Responsibility Principle для View — отобразить данные.

Размещение логики форматирования во ViewModel напрямую — нарушает Single Responsibility Principle для ViewModel.

ViewModel, как правильно было замечено, менять ни в коем случае нельзя из за конкретных Culture и Timezone, соответственно прошить форматтер для конкретного языка (или всех языков) — за такое надо бить по рукам.

Правильный способ заключается в создании интерфейса ICultureFormatterService, который будет внедряться во ViewModel (или View) с помощью IoC-контейнера. Конкретная реализация этого интерфейса и будет содержать логику обработки на основе значений Culture и Timezone.

Таким образом, получаем и соответствие Single Responsibility Principle, убираем зависимость ViewModel'и от форматтера и отдельных его реализаций, что в итоге дает возможность легко тестировать логику всех элементов по-отдельности и создавать Fake-объекты форматтера для тестов.
Спасибо, вы только что наглядно продемонстрировали, как любую идею можно довести до абсурда. В вашем случае — single responsibility.

Что такое «отобразить данные»? Данные отображает браузер на основе полученного от view кода. А если быть точнее — на основе кода, который сгенерил MVC-хэндлер на основе view. Когда view распихивает фамилию, имя и отчество по разным колонкам таблицы — это вас не удивляет? А это отображение данных. А когда дизайнер говорит, что в этой колонке теперь надо показывать не текстбокс, а календарь — это ведь тоже отображение данных, и им тоже занимается view?

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

Совершенно верно. Но форматирование, как и валидация, и тому подобное — больше отношения имеет, скажем так, к инфраструктуре, что ли, нежели к конкретному View.

Все очень просто. Нужно написать алгоритм форматирования для (например) русского языка в системе где-то ровно один единственный раз. Привязывать его к конкретному View или ViewModel — глупо. Каждый раз придется делать то же самое.

Шаблоны? Контролы? Хорошо, но что если отформатированная дата понадобится нам где-то еще, в другом представлении? Или вообще еще бог знает где. Это такая универсальная логика ведь, не специфичная для конкретного View.

«Отобразить данные» — я и имел в виду, по сути, передать «дальше» (в нашем случае — браузеру).

Тут спор из за того, чем считать операцию форматирования — логикой представления или инфраструктурной логикой. Я считаю, что это второе, и выше объяснил почему.

P.S. Где вы увидели доведение чего-либо до абсурда? В данном случае, это совершенно очевидный и кошерный способ «of doing things», такскзть.

Вы же отделяете доменную модель от контроллеров и от уровня доступа к данным? Так почему бы здесь не отделить что-то от чего-то, где что-то не вяжется.
«Но форматирование, как и валидация, и тому подобное — больше отношения имеет, скажем так, к инфраструктуре, что ли, нежели к конкретному View.»
Да нет же. В одном view один формат даты, в другом — другой. Это совершенно нормально.

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

«Хорошо, но что если отформатированная дата понадобится нам где-то еще, в другом представлении?»
Я вас уверяю, вызов ShortDateFormatterService.Format(dt) ничем не отличается от dt.Format(«d»). Только вокруг первого много геморроя.

«Где вы увидели доведение чего-либо до абсурда? В данном случае, это совершенно очевидный и кошерный способ «of doing things», такскзть.»
Вот именно из-за «очевидных и кошерных способов» и рождаются монстры, в которых нет ни одного живого места, сплошные паттерны. Разобраться в них невозможно, поддерживать — тоже.

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

Про монстра — это вы зря. Слабые связи в системе — это всегда хорошо, на любом уровне. Разумеется, бывают разные ситуации, но в данном случае развязался именно горячий спор, когда «ни так нельзя, ни эдак» и все контр-аргументы объективные.

«Я вас уверяю, вызов ShortDateFormatterService.Format(dt) ничем не отличается от dt.Format(«d»). Только вокруг первого много геморроя.»
Никакого ShortDateFormatterService.Format(dt) нигде и близко не вызывается. В способе, который я предложил, ViewModel возвращает уже отформатированную дату и логика форматирования, вместе с определением контекста (язык, культура, формат) находится ни во View, ни во ViewModel.

«Я еще раз напоминаю, что форматирование даты в разных местах сайта может быть разным. Оно даже на одной странице может быть разным.»
Может. Тогда форматтер-сервис может использоваться несколько раз, либо предлагать независимый от языка интерфейс. В любом случае, во View (или ViewModel) должно быть
— FormatHelper.FormatDate(«d») и
— FormatHelper.FormatDateShort(«d»),
а не
— FormatHelper.FormatDate(«d», «RU-ru») и
— FormatHelper.FormatDateShort(«d», «RU-ru»).
Ибо последнее привязывает View к конкретному языку.

Я уже не говорю про случаи, когда вместо явного формата даты нужно использовать относительные величины типа «Вчера», «Сегодня», «Только что».
«Слабые связи в системе — это всегда хорошо, на любом уровне.»
Нет ни одного известного мне архитектурного решения, которое было бы «всегда хорошо, на любом уровне». Слабые связи сложнее отследить при разборе приложения, они менее контролируемы.

«В любом случае, во View (или ViewModel) должно быть — FormatHelper.FormatDate(«d»)»
Угу. А теперь посмотрите в пост, где написано «вы не должны определять формат даты время». А вы только что определили формат на уровне представления… где ему и место (но только не по мнению автора текста).

А то, что при слове «форматирование даты» все почему-то думают про культуру, а не про формат — это я уж не знаю, почему.
«А теперь посмотрите в пост, где написано «вы не должны определять формат даты время». А вы только что определили формат на уровне представления… где ему и место (но только не по мнению автора текста).»
Это я для упрощения, потому что разговор был о другом (вы говорили о том, что будет вызываться ShortDateFormatterService.Format(dt), с чем я не согласен — явно он нигде не будет вызываться разработчиком).

«А то, что при слове «форматирование даты» все почему-то думают про культуру, а не про формат — это я уж не знаю, почему.»
Так потому что мы в контексте культуры это все и рассматриваем! Вы сами с этого и начали эту ветку :)

«Слабые связи сложнее отследить при разборе приложения, они менее контролируемы.»
Ну тут я даже не знаю что и сказать. Правильно, давайте все свяжем как следует, чтоб не развязать потом никак.
Слабые связи дают куда больший контроль над приложением в целом, потому что отдельные части становятся легко взаимозаменяемыми и тестируемыми! Ну ладно, это начало нового большого спора.
«явно он нигде не будет вызываться разработчиком»
А что будет вызываться разработчиком (в парадигме MVC, не MVVM) для вывода даты, отформатированной dd/mm/yyyy (именно так, вне зависимости от языка)?

«Вы сами с этого и начали эту ветку»
Я? Нет, ни капли. Прочитайте внимательно. Я начал ветку с разговора о форматировании даты, вслед за строчкой из поста. И регулярно напоминаю об этом.

«Слабые связи дают куда больший контроль над приложением в целом, потому что отдельные части становятся легко взаимозаменяемыми и тестируемыми!»
Отнюдь. Слабые связи упрощают модификацию (благодаря взаимозаменяемости) и тестирование (благодаря подмене), но они усложняют разработку в силу (а) просто большего количество уровней абстракции и (б) потери контроля над тем, что реально сделает вызываемый мной код. Если я, как разработчик, не знаю, что по ту сторону интерфейса, я теряю контроль над этим местом, я не могу удержать его в голове.

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

Классический пример потери контроля — это… типовое приложение NerdDinner из MVC-шного туториала. В нем предлагается использовать Linq2Sql для доступа к данным, потом обернуть его в репозиторий и спрятать туда. Все прекрасно. В одном из мест туториала во view происходит обращение к свойству объекта в модели (item.Supplier.Name) — и это прекрасно работает благодаря lazy execution в Linq2Sql. А вот теперь представьте, что пришел человек оптимизировать систему, и, как и положено, первое, что сделал — обернул обращение к датаконтексту L2S в using, чтобы не держать соединение с базой. Все внутри репозитория, черный ящик… На мок-тестах этого видно не будет, L2S не вызывается. А вот при тестах против реальной базы приложение упадет, потому что на момент lazy load датаконтекст уже закрыт. Красота.

А все — из-за слабых связей (точнее, конечно, из-за недостаточной формализованности интерфейса, но я еще не видел настолько детализированных требований к интерфейсу, чтобы они покрывали такие приятные тонкости).

А если репозиторий (который туда впихнули ради слабых связей и IoC) выкинуть, то дырка с диспозом видна сразу.
«А что будет вызываться разработчиком (в парадигме MVC, не MVVM) для вывода даты, отформатированной dd/mm/yyyy (именно так, вне зависимости от языка)?»
Хелпер или метод во ViewModel, который внутри себя вызовет метод IFormatterService.Format(), который в свою очередь и содержит всю логику. В реализацию хелпера или ViewModel этот сервис подставляется контейнером, так что и ViewModel'и, и View, и хелперу все равно, как именно разруливаются правила форматирования (в соответствии ли с языками, или же нет).

P.S. Без многоязычности, действительно, можно и не городить новых сервисов.
Осталось понять, как же программист объяснит этому хелперу/методу, что формат вывода должен быть такой и только такой (и только в этом месте такой, а в других местах — другой).
Сервис получает язык и культуру из ViewModel'и и решает там дальше сам, что и как. Программист же задает не сам формат, а «тип формата» что ли (типа «Короткая дата», «Полная дата», «Дата со временем» и так далее). Тем самым мы саму логику форматирования никуда не привязываем.

Однако, чтобы прекратить этот спор — никто не мешает определять в каждом конкретном месте во View, если так больше нравится, но только для одноязычных сценариев.
«никто не мешает определять в каждом конкретном месте во View, если так больше нравится»
Мешает автор поста, который пишет «вы никогда не должны так делать». С каковым утверждением я и спорил. И он писал не про многоязычность, а именно про форматы.

Кстати, именно за «сервис… решает дальше сам, что делать» я и ненавижу слабосвязанные системы. Спасибо, но я сам хочу решать, что будет в этом месте страницы — точно так же, как я сам решаю, будет здесь td или th.
Вы и решаете сами. Но один раз. Все остальные разы это будет происходить автоматически на основании того, что вы задали один раз.

Тут важный момент то, что эти несчастные даты — только один пример. Если во всей системе всегда будет «хотеться» указывать все самому — это просто пустая трата времени.

Предлагаю на этом спор по теме закончить.

Чтобы быть объективными, надо рассматривать все это заново, с чистого листа, в определенном контексте определенной задачи. И на свежую голову :)
Иначе получилось, что мы слишком много аспектов затронули в целом, даже напрямую не касающихся вопроса.
"… и (б) потери контроля над тем, что реально сделает вызываемый мной код. Если я, как разработчик, не знаю, что по ту сторону интерфейса, я теряю контроль над этим местом, я не могу удержать его в голове."
Именно для этого и пишутся тесты. Именно для этого.

«вот теперь представьте, что пришел человек оптимизировать систему, и, как и положено, первое, что сделал»
Так просто это сделать нельзя — это такое низкоуровневое архитектурное решение, что это надо закладывать с самого начала.

«Именно для этого и пишутся тесты. Именно для этого.»
Угу. И они прекрасно работают с моками, ради которых и городили изоляцию, поэтому в итоге все равно приходится тестировать все целиком.

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

В частности, чтобы это сработало в конкретно взятом Linq2Sql, надо для каждого связанного объекта, который будет доставаться во view, сказать LoadWith; а тем самым, по сути, мы связали view не просто с контроллером — а с репозиторием. Ну или мы будем всегда грузить все данные, привет производительности.

Это к тому, что чем слабее связана система, тем меньше побочных эффектов должно быть в каждом компоненте (в частности, в туториале NerdDinner есть неучтенный побочный эффект, состоящий в том, что контроллер не должен быть собран GC раньше, чем начнется отрисовка модели, иначе — прощай данные; а вы вот точно знаете lifecycle всех объектов в MVC? а без рефлектора?). И это, конечно, очень круто, но в реальности так пишется очень редко (и занимает в разы больше времени и сил).
«Угу. И они прекрасно работают с моками, ради которых и городили изоляцию, поэтому в итоге все равно приходится тестировать все целиком.»
Вопрос правильного тестирования. Тесты должны тестировать, а не просто «быть».

«Понимаете, в чем беда — для того, чтобы это заложить, надо точно знать, как работает каждый компонент. Снова получаем повышение связности.»
Вопрос опыта/компетентности архитектора.

«В частности, чтобы это сработало в конкретно взятом Linq2Sql, надо для каждого связанного объекта, который будет доставаться во view, сказать LoadWith; а тем самым, по сути, мы связали view не просто с контроллером — а с репозиторием. Ну или мы будем всегда грузить все данные, привет производительности.»
Вопрос архитектуры. Грузить все данные — ни в коем случае, привязка модели к View — ни в коем случае.

«контроллер не должен быть собран GC раньше, чем начнется отрисовка модели, иначе — прощай данные»
А как там такое возможно? А как же ExecuteResult в ActionResult? View начинает отрисовываться именно в этот момент (если смотреть реализацию ViewResult).
«Вопрос опыта/компетентности архитектора.»
Код пишет не архитектор, вот в чем беда. И какая бы ни была компетентность, чем проще — тем надежнее.

«привязка модели к View — ни в коем случае»
NerdDinner, в котором в модель (да, это уже viewmodel, но она все равно из репозитория добывается контроллером) грузятся данный для комбобоксов (т.е., привязанные ко view) смеется над нами.

«А как там такое возможно?»
Да легко. Достаточно выкинуть ActionResult за пределы скопа, в котором был создан контроллер. Вы готовы, не глядя в код, сказать. как там это сделано?
«Да легко. Достаточно выкинуть ActionResult за пределы скопа, в котором был создан контроллер. Вы готовы, не глядя в код, сказать. как там это сделано?»
Особых тонкостей могу не знать, ясное дело, надо смотреть код.

«Код пишет не архитектор, вот в чем беда.»
Не пишет — не значит «не разбирается» ;)

«NerdDinner, в котором в модель (да, это уже viewmodel, но она все равно из репозитория добывается контроллером) грузятся данный для комбобоксов (т.е., привязанные ко view) смеется над нами.»
Надо смотреть код. Но не надо путать модель и ViewModel — это совершенно разные вещи. Из репозитория контроллером добываются данные, а не ViewModel.
«надо смотреть код»
Вот я не могу назвать «слабосвязанной» систему, в которой для оценки побочного эффекта тривиального действия надо смотреть код.

«Не пишет — не значит «не разбирается»»
Я о другом. Какую бы красивую систему не придумал архитектор, реализовывать ее будут программисты. Чем больше в системе ограничений и правил (особенно — неверифицируемых компилятором), тем больше вероятность того, что будет ошибка.
«Все внутри репозитория, черный ящик…»
И хорошо, что черный ящик — модель домена не должна зависеть от конкретной реализации способа хранения этих данных.

«На мок-тестах этого видно не будет, L2S не вызывается. А вот при тестах против реальной базы приложение упадет, потому что на момент lazy load датаконтекст уже закрыт. Красота.»
Как это так? Значит то, что инкапсулирует в себе обращение к L2S, не соответствует тому интерфейсу, который используется в тестах. И да, не забываем про интеграционные тесты, специфичные для каждой технологии.

По поводу конкретно L2S — возможно, в этом случае это именно его проблема (я бы не стал его использовать для Domain-Driven Design-архитектуры), его DataContext содержит в себе слишком многое (включая необходимый для подключения к базе Unit Of Work) и не совместим с концепцией DDD.
«Значит то, что инкапсулирует в себе обращение к L2S, не соответствует тому интерфейсу, который используется в тестах.»
Да нет. В интерфейсе написано IQueryable of Product — мы его честно вернули. В обоих случаях. А глубину дерева зависимостей интерфейс не спицифицирует.

Повторюсь — можно специфицировать, да. Только никто этим не занимается никогда, слишком много сил и времени уходит. А 90% систем этого не требует.

«И да, не забываем про интеграционные тесты, специфичные для каждой технологии.»
Угу. И ради введения IoC мы вынуждены написать в n раз больше кода. Это не всегда оправдано.
«И ради введения IoC мы вынуждены написать в n раз больше кода.»
Не, это IoC внедряется для того, чтобы в итоге написать куда меньше кода и он был более поддерживаемым.

«Да нет. В интерфейсе написано IQueryable of Product — мы его честно вернули. В обоих случаях. А глубину дерева зависимостей интерфейс не спицифицирует.»
А он и не должен.
А вот IQueryable, кстати, очень специфичный интерфейс, он позволяется «извне» заглянуть слишком глубоко, слишком близко к уровню хранения данных — особенно в случае с L2S. Поэтому при использовании DDD, в контроллерах IQueryable совершенно не фигурирует.

«Повторюсь — можно специфицировать, да. Только никто этим не занимается никогда, слишком много сил и времени уходит. А 90% систем этого не требует.»
Ну тут можно слишком долго спорить. Поэтому — не буду :)
«Не, это IoC внедряется для того, чтобы в итоге написать куда меньше кода и он был более поддерживаемым.»
К сожалению, это только в теории. А в реальности в 80% случаев IoC, которые я видел, объем кода был больше, чем без него, а поддерживать это было без поллитры невозможно. Т.е., да, где-то в далекой перспективе IoC упрощает жизнь. В сложных проектах. Но далеко не все проекты доживают до этой стадии.

«А вот IQueryable, кстати, очень специфичный интерфейс, он позволяется «извне» заглянуть слишком глубоко, слишком близко к уровню хранения данных»
Это, кстати, неправда. С точки зрения стороннего разработчика (пользователя интерфейса), IQueryable не позволяет ничего такого, чего не позволял бы IEnumerable. Никаких операций доступа к данным в нем нет — в нем вообще нет ничего такого, что нельзя было бы сделать с клиентским массивом данных.
Про IoC — прежде чем выдавать утверждение, если работаете с MVC, попробуйте, посмотрите примеры, например, вот для этого контейнера: ninject.org/

Про IQueryable — он позволяет слишком неконтролируемо копаться в источнике данных снаружи, что не всегда нужно (а в случае с DDD — и вовсе) и сильно ухудшает тестируемость.
Про IoC — я говорю про real-life applications, которые я видел, поддерживал, переписывал. В 80% из них выбрасывание IoC приводило к упрощению системы — просто потому, что модифицируемость там не была никому нужна.

«Про IQueryable — он позволяет слишком неконтролируемо копаться в источнике данных снаружи»
Приведите пример, что ли.

Только не забывайте, что, вообще-то, все действия над IQueryable ограничиваются тем провайдером, который этот IQueryable выдал.

«что не всегда нужно»
Да ладно. Вот типовой пример (НЕ для MVC). Берем грид. В гриде нужна сортировка, фильтрация, пейджинг, группировка.

Если мы говорим, что датасоурсом для грида является IQueryable, у того есть стандартный способ выполнить все операции, причем их механизм от него полностью спрятан (привет separation of concerns).

А теперь попробуйте все то же самое сделать просто, универсально и читаемо любым другим способом. IEnumerable? Операции станут клиентскими, прощай, производительность. Типизированный интерфейс с Get(Func order, Func filter, Func group...)? Прощай возможность воспользоваться стандартными провайдерами данных без враппера.
Ах, да, по поводу вашего примера с ФИО и таблицей.

Размещение ФИО — это разметка (прерогатива View).
Формат даты — это форматирование (сервис инфраструктуры).

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

А текстбокс и календарь — это уже способ работать с данными. Тут общее правило такое — это календарь должен знать, как мы форматируем дату (или наш сервис форматирует дату), а не наоборот.
А чем отличается «разметка» (давайте разместим фамилию сюда, жирно, имя на следующей строчке, нормально) от «форматирования» (давайте напишем на этой строчке месяц-день, жирно, а на следующей — время, нормально)?

«А текстбокс и календарь — это уже способ работать с данными.»
Угу. Вот только для выбора, взять текстбокс или календарь, никто пока еще (если только это не надо конфигурировать на лету) не использует сервисы, а просто вызывают во вью нужный хелпер.
«если только это не надо конфигурировать на лету»
В этом то и суть! Мы тут это обсуждаем в таком контексте только потому что «Timezone» и потому что «Culture».
«А чем отличается «разметка» (давайте разместим фамилию сюда, жирно, имя на следующей строчке, нормально) от «форматирования» (давайте напишем на этой строчке месяц-день, жирно, а на следующей — время, нормально)?»

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

По поводу хелпера — возможно, это вопрос предпочтений, но тогда в любом случае, если хелпер — календарь, то формат физически должен задаваться не в нем. Хелпер вполне может внутри себя использовать тот же сервис (либо же иметь свой независимый механизм определения формата на основе тех же «Timezone» и «Culture»). В любом случае, логика форматирования внутри хелпера — это ни логика View, ни логика ViewModel.
«Ну я бы их просто разделил — в результате первого — HTML-разметка, а в результате второго строчка»
Вы невнимательны. В результате обоих — html-разметка.

«В любом случае, логика форматирования внутри хелпера — это ни логика View, ни логика ViewModel. „
Тоже ошибка. Логика форматирования внутри хелпера — это, по сути, логика внутри view. То, что хелпер выносит ее в другой класс, не меняет компонентной принадлежности этой логики.

Впрочем, это не важно, речь идет в данном случае о выборе того или иного хелпера в зависимости от пожеланий дизайнера.
«Тоже ошибка. Логика форматирования внутри хелпера — это, по сути, логика внутри view. То, что хелпер выносит ее в другой класс, не меняет компонентной принадлежности этой логики.»
Ха! Вот где собака то и порылась. Компетентная принадлежность элементов вообще не меняется при их развязывании ибо цель то, как раз, снизить связанность и увеличить «code reusability».
И то что хелпер выносит логику в другой класс имеет для этой цели огромнейшее значение.
Вот только связанность не понижается.

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

И, кстати, выбор, какой хелпер вызвать — shortdateformathelper или longdateformathelper — это, в итоге, все равно выбор форматирования на стороне view. Вы никуда от этого не уйдете.
«И, кстати, выбор, какой хелпер вызвать — shortdateformathelper или longdateformathelper — это, в итоге, все равно выбор форматирования на стороне view. Вы никуда от этого не уйдете.»
Да, но язык-то и его тонкости разруливаются далеко-далеко от View. И только сервис знает, что, например, короткую дату нужно в нужный момент заменить на «Сегодня» или «Вчера», а длинную нет.
Программист об этом не думает во время формирования View — вот в этом и преимущество. И развязанность в том, что реализация находится НЕ в хелпере.
«И только сервис знает, что, например, короткую дату нужно в нужный момент заменить на «Сегодня» или «Вчера», а длинную нет.»
В этот момент дизайнер, который считает, что в списке новостей на главной странице надо заменять короткую дату на вчера и сегодня, а в архивном отображении — не надо, начинает ненавидеть вас и ваш сервис.
Ну почему же — вполне могут быть опции, настройки. Главное здесь то, что алгоритм обработки формата находится где нужно:
View < — Helper < — ViewModel < — (Service — здесь).
Но _выбор_ (ваши опции и настройки) формата — на стороне view. Вернулись к началу треда.

Давайте уже сойдемся на том, что автор поста привел (а) некорректный и (б) максималистский пример.
«но вы не должны определять формат даты время или парсить строки — это то, что для Представления должна делать Модель (Model)»

Ну, тут то все верно, ясное дело, что не модель.
«Т.е. View тупо, без какой либо логики берет коллекцию из ViewModel и отдает контролу.»
Именно так.

«Логика проверки Item-ов на соответствие критерию — во ViewModel.»
Нет. Логика фильтрации на соответствие критерию — в Domain Model.
«Причем если логику работы ViewModel в данном случае мы можем проверить тестами, то логику работы View — тока визуально оценив как оно отформатировалось в HTML.»
А что вы еще тестируете для View (на серверной стороне)?

«View должна быть тупой с минимумом логики»
И не просто логики — а _исключительно_ логики представления — где что вывести, или не вывести и на каких условиях.
Sign up to leave a comment.

Articles