image


Введение


Здравствуйте, коллеги!
Сегодня хочу поделиться с вами своим опытом разработки архитектуры View Model в рамках разработки веб-приложений на платформе ASP.NET, используя шаблонизатор Razor.
Описываемые в данной статье технические реализации подходят для всех актуальных на текущей момент версий ASP. NET (MVC 5, Core, etc). Сама статья предназначена для читателей, которые, по меньшей мере, уже имели опыт работы под данным стеком. Также стоит отметить, что в рамках данной мы не рассматриваем саму пользу View Model и её гипотетическое применение (предполагается, что читатель уже знаком с данными вещами), обсуждаем непосредственно реализацию.


Задача


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


Реализация


Пусть модель выглядит следующим образом (простоты ради в искомой не приведены такие вещи как навигационные свойства и прочее):


class Transport
{
    public int Id { get; set; }

    public int TransportTypeId { get; set; }

    public string Number { get; set; }
}

Разумеется, TransportTypeId — внешний ключ на объект типа TransportType:


class TransportType
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Для связи между frontend и backend будем использовать шаблон Data Transfer Object. Соответственно, DTO для добавления автомобиля будет выглядеть примерно следующим образом:


class TransportAddDTO
{
    [Required]
    public int TransportTypeId { get; set; }

    [Required]
    [MaxLength(10)]
    public string Number { get; set; }
}

* Используются стандартные атрибуты валидации из System.ComponentModel.DataAnnotations.


Настало время понять, что же будет View Model для страницы добавления автомобиля. Некоторые разработчики с радостью бы объявили, что таковой будет являться сам TransportAddDTO, однако, это в корне неверно, так как в данный класс нельзя "запихивать" ничего кроме непосредственно информации для backend, необходимой для добавления нового элемента (по определению). А помимо этого на странице добавления могут потребоваться и другие данные: например, справочник типов транспортных средств (на основе которого и выражается впоследствии TransportTypeId). В связи с этим напрашивается примерно следующая View Model:


class TransportAddViewModel
{
    public IEnumerable<TransportTypeDTO> TransportTypes { get; set; }
}

Где TransportTypeDTO в данном случае будет прямым отображением TransportType (а это далеко не всегда так — как в сторону усечения, так и в сторону расширения):


class TransportTypeDTO
{
    public int Id { get; set; }

    public string Name { get; set; }
}

На данном этапе встает резонный вопрос: в Razor можно будет передать только одну модель (и слава богу), как же тогда использовать TransportAddDTO для генерации HTML-кода внутри данной страницы?
Очень просто! Достаточно в View Model добавить, в частности, данный DTO, примерно так:


class TransportAddViewModel
{
    public TransportAddDTO AddDTO { get; set; }
    public IEnumerable<TransportTypeDTO> TransportTypes { get; set; }
}

Теперь то и начинаются первые проблемы. Попробуем добавить стандартный TextBox для "номера ТС" на страницу в нашем .cshtml файле (пусть это будет TransportAddView.cshtml):


@model TransportAddViewModel
@Html.TextBoxFor(m => m.AddDTO.Number)

Это отрендерится в HTML-код примерно следующего содержания:


<input id="AddDTO_Number" name="AddDTO.Number" />

Представим, что часть контроллера с методом добавления транспорта выглядит так (код в соответствии с MVC 5, для Core он будет чуть-чуть отличаться, но суть такая же):


[Route("add"), HttpPost]
public ActionResult Add(TransportAddDTO transportAddDto)
{
    // Некоторая работа с полученным transportAddDto...
}

Тут мы видим, по меньшей мере, две проблемы:


  1. Id и Name атрибуты имеют префикс AddDTO, и, в последствии, если метод добавления транспорта в контроллере по принципу привязки модели попробует сделать биндинг данных, которые пришли от клиента, в TransportAddDTO, то объект внутри будет состоять полностью из нулей (значений по умолчанию), т.е. это будет просто новый пустой экземпляр. Оно и логично — биндер ожидал имена вида Number, а не AddDTO_Number.
  2. Пропали все мета-атрибуты, т.е. data-val-required и все другие, которые мы так тщательно описывали в AddDTO в виде атрибутов валидации. Для тех кто использует всю мощь Razor это критично, так как это существенная потеря информации для frontend.
    Нам повезло, и они имеют соответственные решения.

Данные вещи "работают" и при использовании, например, враппера для Kendo UI (т.е. @Html.Kendo().TextBoxFor() и др.).


Начнем со второй проблемы: причина тут кроется в том, что в View Model переданный экземпляр TransportAddDTO имел значение null. А реализация механизмов рендеринга такова, что атрибуты при таком случае считываются по меньшей мере не полностью. Решение, соответственно, очевидно — предварительно во View Model инициализировать свойство TransportAddDTO экземпляром класса с помощью конструктора по умолчанию. Лучше это сделать в сервисе, который возвращает инициализированную View Model, однако, в рамках примера подойдет и так:


class TransportAddViewModel
{
    public TransportAddDTO AddDTO { get; set; } = new TransportAddDTO();
    public IEnumerable<TransportTypeDTO> TransportTypes { get; set; }
}

После данных изменений результат будет похож на:


<input data-val="true" id="AddDTO_Number" name="AddDTO.Number" data-val-required="The Number field is required." data-val-length="The field Number must be a string with a maximum length of 10." data-val-length-max="10" />

Уже лучше! Осталось разобраться с первой проблемой — с ней, кстати, всё несколько сложнее.
Для её понимания для начала стоит разобраться что в Razor (подразумевается WebViewPage, экземпляр которого внутри .cshtml доступен как this) представляет собой свойство Html, к которому мы обращаемся с целью вызова TextBoxFor.
Посмотрев на него, можно мгновенно понять, что оно имеет тип HtmlHelper<T>, в нашем случае HtmlHelper<TransportAddViewModel>. Возникает возможное решение проблемы — создать внутри свой HtmlHelper, и передать ему на вход наш TransportAddDTO. Находим минимально возможный конструктор для экземпляра данного класса:


HtmlHelper<T>.HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer);

ViewContext мы можем передать напрямую из нашего экземпляра WebViewPage через this.ViewContext. Разберемся теперь, где взять экземпляр класса, реализующего интерфейс IViewDataContainer. Например, создадим свою реализацию:


public class ViewDataContainer<T> : IViewDataContainer where T : class
{
    public ViewDataDictionary ViewData { get; set; }

    public ViewDataContainer(object model)
    {
        ViewData = new ViewDataDictionary(model);
    }
}

Как можно заметить, теперь мы упираемся в зависимость от некоторого объекта, передаваемого в конструктор с целью инициализации ViewDataDictionary, благо тут всё просто — это и есть экземпляр нашего TransportAddDTO из View Model. То есть получить заветный экземпляр можно так:


var vdc = new ViewDataContainer<TransportAddDTO>(Model.AddDTO);

Соответственно, в создании нового HtmlHelper'a также проблем не возникает:


var Helper = new HtmlHelper<T>(this.ViewContext, vdc);

Теперь можно воспользоваться следующим образом:


@model TransportAddViewModel
@{
var vdc = new ViewDataContainer<TransportAddDTO>(Model.AddDTO);
var Helper = new HtmlHelper<T>(this.ViewContext, vdc);
}
@Helper.TextBoxFor(m => m.Number)

Это отрендерится в HTML-код примерно следующего содержания:


<input data-val="true" id="Number" name="Number" data-val-required="The Number field is required." data-val-length="The field Number must be a string with a maximum length of 10." data-val-length-max="10" />

Как можно заметить, теперь с отрендеренным элементом никаких проблем нет, и он готов к полноценному использованию. Осталось только "причесать" код, дабы он выглядел менее громоздко. Например, расширим наш ViewDataContainer следующим образом:


public class ViewDataContainer<T> : IViewDataContainer where T : class
{
    public ViewDataDictionary ViewData { get; set; }

    public ViewDataContainer(object model)
    {
        ViewData = new ViewDataDictionary(model);
    }

    public HtmlHelper<T> GetHtmlHelper(ViewContext context)
    {
        return new HtmlHelper<T>(context, this);
    }
}

Тогда из Razor можно работать вот так:


@model TransportAddViewModel
@{
var Helper = new ViewDataContainer<TransportAddDTO>(Model.AddDTO).GetHtmlHelper(ViewContext);
}
@Helper.TextBoxFor(m => m.Number)

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


Заключение


На этом проблемы решены, а также получена архитектура View Model для работы с Razor, которая потенциально может содержать в себе все необходимые элементы.


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


Осталось добавить пару кнопок в наш .cshtml файл, и задача будет выполнена (не учитывая обработки на backend'e). Это я предлагаю сделать самостоятельно.


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


С уважением,
Петр Осетров

Комментарии (46)


  1. caballero
    05.07.2018 21:51
    -1

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


    1. mayorovp
      05.07.2018 21:57
      +1

      Нет, совсем не до фига. В реальном приложении будет раз в 10-20 больше свойств и разметки — а количество вспомогательных классов не увеличится.


    1. ParadoxFilm Автор
      05.07.2018 22:23

      Добрый вечер, Леонид. Простите, что простые примеры ввели в заблуждение. Разумеется, в реальном проекте они всегда сложнее. Тем не менее, идеи, описанные в данной статье, ни в коей степени не являются исключительно «академическими» — всё это, напротив, получено и придумано в непосредственном процессе разработки.
      Предлагаемая архитектура позволяет масштабироваться проекту (и, при необходимости, сужаться) при малых затратах благодаря малой связности компонентов системы.
      Действительно, это выглядит как оверхед на примере в статье, но ведь контент рассчитан на читателей, которые уже столкнулись с данными проблемами, и была надежда, что сложные примерны ни к чему. Удачного вам остатка дня!


  1. ZOXEXIVO
    05.07.2018 22:40

    Подскажите, зачем сейчас использовать Razor?
    Вы не знали, что Javascript победил?
    Да, все становится чуть сложнее, но такие задачи решаются куда меньшей кровью.


    1. ParadoxFilm Автор
      05.07.2018 22:45

      Здравствуйте! Если вы про то что будущее за SPA — быть может, вы и правы, однако, в статье данный вопрос не затрагивается. А до тех пор, пока есть MPA — есть и Razor (и другие server-side шаблонизаторы). Готов побеседовать на данную тему не рамках данного поста, а, например, в личной беседе.


    1. dmitry_dvm
      06.07.2018 08:43

      А изоморфный рендеринг?


      1. mayorovp
        06.07.2018 10:38

        Вот как раз для него Razor точно не нужен…


    1. Xandrmoro
      06.07.2018 12:05
      +1

      > Javascript победил
      Только в воображении сектантов. Клиентского рендеринга должен быть абсолютный минимум.


      1. ZOXEXIVO
        06.07.2018 12:08
        -1

        Сейчас сектант — это вы


      1. RomanPokrovskij
        06.07.2018 16:11

        Тем не менее, Razor, поправьте если не прав, не возможно динамически сгенерировать run time, т.е. сам шаблон. Это остановило развитие сервер сайд рендеринга ASP MVC (php и т.п. развивались вместе с развитием восхитительных, для меня, CMS). Метапрограммирование при помощи T4 и в compile time — это эрзац замена — сгенерировать что-то можно, но развивать вместе с генератором не получается.


        1. Xandrmoro
          06.07.2018 16:24

          А можно кейс генерации шаблона в рантайме?


          1. RomanPokrovskij
            06.07.2018 16:35

            Все кейсы где вы использовали T4 (для генерации Razor'а) в девтайм. Это не отмазка, просто вопрос так задан, что можно препдположить что в «девтайм» — кейсы вам понятны и ясны.


        1. mayorovp
          06.07.2018 17:11

          Но зачем его генерировать run time когда в нем самом можно взять и написать цикл по метаданным?

          Ну и для «особо буйных» ничто не мешает реализовать IViewEngine


          1. RomanPokrovskij
            06.07.2018 17:31

            Не надо IViewEngine сейчас самому. ServiceStack сейчас пытаются продвинуть шаблонизатор под ASP с возможностью генерирования run time.

            На вопрос «зачем его генерировать run time » — ответ такой же — затем же зачем в dev time… Если у вас нет потребности генерировать код в дев-тайм, у вас не будет потребности его генерировать в ран-тайм. Ран-тайм это такой дев-тайм только вам не надо заниматься контролем изменений сгенерированных файлов (но надо заниматься их кэшированием).


            1. mayorovp
              06.07.2018 17:51

              Вот только в run time существует альтернатива генерации шаблонов.


              1. RomanPokrovskij
                06.07.2018 18:21

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

                Особо странно что Razor то как раз имеет компиляцию и прикомпеляцию и по сути Razor мог быть легко промежуточным кодом.


                1. mayorovp
                  06.07.2018 19:03

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

                  За счет чего?


                  1. RomanPokrovskij
                    06.07.2018 19:21
                    -1

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


                    1. mayorovp
                      06.07.2018 19:23

                      Функции высших порядков можно создавать и без кодогенерации…


                      1. RomanPokrovskij
                        06.07.2018 19:30

                        Только если она не возвращает шаблон Razor'а (в ран-тайм). «Шаблон разора» конечно условно, дуализм фунция-объект. На самом деле Шаблон Разора это такая функция.
                        П.С. Мы говорил о IViewEngine я помню. Все же это решение без Razorа


                        1. mayorovp
                          06.07.2018 20:04

                          Зачем ей возвращать целый шаблон если можно вернуть Func<T, HelperResult>?


                          И почему вдруг IViewEngine — это без Razor? Кто-то запрещает его использовать или как?


                          1. RomanPokrovskij
                            06.07.2018 22:27
                            -1

                            А почему вы считаете что код возвращающи Func<T, HelperResult> будет проще чем код генерирующий шаблон Razor? Разве комбинировать сущности высокой абстракции не будет проще чем низкой (например в смысле количества операций и затрат времени). Допустим вам не нравится операция конкатенции из принципа, но разве все притензии к этой операции будут вас волновать после того как кодогенератор протестирован (кого волнует что T4 это таже конкатенция). Пользователю кодогенератора не надо ничего конкатенировать, у него просто два вызова Create(parameters) и Compile.

                            Я могу доверять в этом ServiceStack которые утверждают что это (Razor в runtime) это не возможно?

                            Вы используете в своей практике T4, Expression Trees или roslyn? Я не о том что мол «если не используете не о чем с вами говорить». Просто я до сих пор не понял вашу позицию. «вернуть Func<T, HelperResult>» это аргумент против кодогенерации вообще а не кодогенерации ран тайм.


                            1. mayorovp
                              07.07.2018 07:22

                              Я могу доверять в этом ServiceStack которые утверждают что это (Razor в runtime) это не возможно?

                              Либо вы не так поняли, либо лучше им не доверять...


                              Просто я до сих пор не понял вашу позицию. «вернуть Func<T, HelperResult>» это аргумент против кодогенерации вообще а не кодогенерации ран тайм.

                              Мне не нравится кодогенерация там где от нее нет никакой выгоды.


                              1. RomanPokrovskij
                                07.07.2018 11:53
                                -1

                                Еще для уточнения вашей позиции:

                                А когда вы давеча говорили что «в Razor нет eval из коробки» что вы имели ввиду?

                                Вы используете (вам нравится) возможность генерировать шаблоны Razor девтайм используя T4?


                                1. mayorovp
                                  07.07.2018 12:18

                                  Да что вы докопались до этой кодогенерации Razor через T4? Нет, подобные извращения мне не нравятся.


                                  1. RomanPokrovskij
                                    07.07.2018 12:52
                                    -1

                                    потому что мне казалось важным выяснить понимаете ли вы что аргументы «крутить по мете», «вернуть Func<T, HelperResult>» они против генерации Razor вообще, dev time или run time — безразницы. кажется, понимаете.

                                    а с мнением что генерировать разор — даже дев тайм — это извращение я не спорю, хватает показать, что оно очевидно спорно — Microsoft сам использует T4 для генерации razorа. Такие T4 входят в каждый дистрибутив Visual Studio.


  1. RomanPokrovskij
    05.07.2018 22:48

    Вы не обижайтесь, вы предложили бы архитектуру если бы вы смогли определить метаданные описывающие `Transport` (точнее операции над ним) из которых можно «произвести» (лучше всего сгенерировать): контроллер, дтошки, биндинги. Вьюшки — не обязательно. У вас в голове все это наверняка есть, но в коде — незадекларировано. Ставьте перед собой амбициозные цели.


    1. ParadoxFilm Автор
      05.07.2018 22:52

      Роман, верно ли я понимаю, что вы о названии статьи? Могли бы вы раскрыть содержание вашего комментария более подробно? С удовольствием выслушаю.
      // upd: превентивно изменил название, надеюсь, так будет более уместно.


      1. RomanPokrovskij
        05.07.2018 23:14

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


        1. ParadoxFilm Автор
          05.07.2018 23:25

          Понял вас. В действительности, многие разработчики задумываются о подобной генерации кода, и я не исключение. Однако, меня всегда останавливало следующее:
          Если данный генератор кода будет универсальным в предельно общем смысле, то его настройка будет более сложна, чем механическое написание данного кода.
          Это из той же серии (утрированно), что и написание некоторой универсальной системы в принципе.
          // Когда-то я с товарищами задумал написать программный комплекс, эмпирически решающий подавляющее большинство типичных задач по ТВиМСу с помощью моделирования на основе входных данных, да так, чтобы любой неподготовленный человек сумел воспользоваться. Однако, на стадии прототипирования стало понятно, что настройка и формирование входных данных на комплекс — задача более трудоемкая, чем «по-быстрому» закодить это на том же Python'e и посмотреть результаты. Это впоследствии послужило большим уроком в аспекте универсальных подходов.

          А вот шаблоны, гайдлайны — приветствую, ибо там действительно одно и тоже (и это хорошо!). Соответственно, написать генератор кода по заданным шаблонам — пожалуйста.


          1. RomanPokrovskij
            05.07.2018 23:36

            Все же создание «универсальной/общей в предельном смысле системы» и генерация ДТОшек с контроллером их туда-сюда преобразующим — вещи принципиально разные.

            Возможно ведь и такое рассуждение — решение ценно — если вы решили достаточно общую задачу?


            1. ParadoxFilm Автор
              05.07.2018 23:45

              Чтобы генератор кода мог генерировать DTO'шки, ему необходимо знать о заложенных правилах бизнес-логики (иными словами, что ожидается на вход от пользователя). Ведь добрая часть полей генерируется сервисом во время запроса (яркий пример — ID).
              Чтобы искомый генератор «знал» о данных правилах, их нужно описать. Описать в формализованном и общем виде. А данная операция, на мой взгляд, едва ли менее трудоемкая, чем сразу написать соответствий DTO вручную (это и будет, в частности, манифест который требуется «на вход» генератору).
              Однако, в действительности можно представить, что вы в используемой IDE графически выделяете нужные свойства в модели, нажимаете кнопку генерации и получается соответствующий DTO-класс, тогда неплохо.
              С контроллером вообще все сложно (впрочем, CRUD функции можно сгенерировать в некотором общем виде для сущности, и сопутствующие сервисы тоже, тут вы правы).
              Уверен, что это в любом случае задача IDE.


              1. RomanPokrovskij
                05.07.2018 23:58

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


                1. ParadoxFilm Автор
                  06.07.2018 00:04

                  Не вижу очевидных причин не соглашаться с вашим утверждением на тему формального описания. Однако, не для хабра это статья получится (а в данном случае даже плашка Tutorial висит). Да и на тему формализма у меня в целом двоякое чувство, ещё со времен работы с математическим аппаратом.


                  1. RomanPokrovskij
                    06.07.2018 00:23

                    Как Tutorial не воспринимается, возможно из за заголовка (не один я такой, вон бурчат на «академические идеи»). Раз мясо здесь — создание кастомного Html Helper'а TextBoxFor (смелая идея, мне нравится) — наверное так и стоило назвать.


                    1. ParadoxFilm Автор
                      06.07.2018 00:27

                      Роман, были порывы назвать статью в таком ключе. Но, к сожалению, далеко не все поймут, о каком HtmlHelper'e идет речь. Более того, тут достаточно «жестко» предлагается прятать DTO во ViewModel, это неразрывная концепция описанной мной идеи.
                      Также речь не только об TextBoxFor, речь о любом контроле, который поддерживается стандартной реализацией HtmlHelper.
                      Однако, после ваших слов, ухожу на очередную итерацию, направленную на более подходящее название. Спасибо!


  1. RomanPokrovskij
    05.07.2018 23:26

    Употребляемый термин «масштабирование модели» в контексте описания данных нуждается в определении. Model Scaling — кажется тоже не встречался.


    1. ParadoxFilm Автор
      05.07.2018 23:30

      Спасибо, уточнил искомое в статье.


  1. corr256
    06.07.2018 08:44
    +1

    Если на странице потребуется отобразить блок с информацией, не связанной с добавлением авто, тоже засунете всё в TransportAddViewModel? А для редактирования, наверное, сделаете TransportEditViewModel?


  1. FJCrux
    06.07.2018 09:51

    Не совсем понимаю, зачем вы во ViewModel помещаете DTO объект, который, по факту, не имеет к нему никакого логического отношения. Вы ведь смешиваете логику, которая нужна для создания новых объектов (различные валидации в DTO объекте) и объекты отображения, на которых никакой логики в принципе нет.
    Используйте в полной мере доступный функционал — есть ведь прекрасные PartialView, ChildAction(в ASP.NET) и ViewComponent(в ASP.NET Core).
    Появится у вас задача на той же страничке, отобразить еще какой-то блок информации, не будете же вы и её добавлять во ViewModel?
    Помимо того, что вы разделяете логику и отображение, вы еще и избавитесь от обеих ваших проблем


  1. ETCDema
    06.07.2018 15:19

    Эх, было время когда мы так же писали. Но прошло время, поменялся подход и из проекта выкинули примерно 60% кода с учетом того, что полезные для пользователя функции добавлялись.
    И у нас отлично уживается Razor с Vuejs в SPA.


    1. thinking
      06.07.2018 15:27

      Подсовываете razor-шаблоны в Vuejs? Каким образом?


      1. ETCDema
        06.07.2018 15:44

        Ужас какой, нет конечно. На Vuejs реализованы сложные части UI, которые через обычный DOM+jQuery сделать конечно можно, но получается на порядок сложнее Vuejs компонент.
        Vuejs компоненты являются частью других компонент, которые могут иметь свой Razor шаблон, так и рендерится обычними helperами. SPA работает с использованием HistoryJS и получает от сервера HTML, в котором может использоваться VueJS.


  1. olen
    06.07.2018 19:19
    -1

    Я предпочитаю ViewModel такого вида:

    class TransportAddViewModel
    {
        // public TransportAddDTO AddDTO { get; set; }
    
        [Required]
        public int TransportTypeId { get; set; }
    
        [Required]
        [MaxLength(10)]
        public string Number { get; set; }
    
        public IEnumerable<TransportTypeDTO> TransportTypes { get; set; }
    }
    

    Т.е. сам ViewModel является моделью для отображения данных + нужные словари. Контроллер ожидает ваш TransportAddDTO (с такими же полями). Явный минус такого подхода — дублирование атрибутов. Но их приходится дублировать и в Entity.

    Можно с вашей моделью использовать html tags и явно указывать Name:
    <input name="Number" asp-for="@Model.AddDTO.Number">


    1. RomanPokrovskij
      06.07.2018 22:59
      -1

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

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

      Все свободны даже (о боже!) передавать словари через ViewBag кто бы что не говорил. И парсить HttpRequest в свое удовольствие без всяких биндингов на реквест. Во многих случаях будет просто тупо меньше кода, что уже хорошо.


      1. olen
        07.07.2018 10:35

        Спасибо зо поддержку :-)
        Мне бы больше хотелось, чтобы минусующие предложили свой вариант. Т.к. ваш вариант мне не очень нравится созданием дополнительного костыля.