ASP.NET Razor: решение некоторых проблем с архитектурой для модели представления +15

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
    /#18853245 / -1

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

    • mayorovp
      /#18853255 / +1

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

    • ParadoxFilm
      /#18853329

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

  2. ZOXEXIVO
    /#18853383

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

    • ParadoxFilm
      /#18853393

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

    • dmitry_dvm
      /#18854073

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

      • mayorovp
        /#18854503

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

    • Xandrmoro
      /#18854889 / +1

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

      • ZOXEXIVO
        /#18854903 / -1

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

      • RomanPokrovskij
        /#18856055

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

        • Xandrmoro
          /#18856111

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

          • RomanPokrovskij
            /#18856157

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

        • mayorovp
          /#18856303

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

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

          • RomanPokrovskij
            /#18856373

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

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

            • mayorovp
              /#18856435

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

              • RomanPokrovskij
                /#18856519

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

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

                • mayorovp
                  /#18856609

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

                  За счет чего?

                  • RomanPokrovskij
                    /#18856695 / -1

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

                    • mayorovp
                      /#18856703

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

                      • RomanPokrovskij
                        /#18856713

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

                        • mayorovp
                          /#18856813

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


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

                          • RomanPokrovskij
                            /#18857079 / -1

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

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

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

                            • mayorovp
                              /#18857551

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

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


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

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

                              • RomanPokrovskij
                                /#18857907 / -1

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

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

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

                                • mayorovp
                                  /#18857959

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

                                  • RomanPokrovskij
                                    /#18858019 / -1

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

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

  3. RomanPokrovskij
    /#18853395

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

    • ParadoxFilm
      /#18853405

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

      • RomanPokrovskij
        /#18853465

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

        • ParadoxFilm
          /#18853487

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

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

          • RomanPokrovskij
            /#18853511

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

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

            • ParadoxFilm
              /#18853541

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

              • RomanPokrovskij
                /#18853573

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

                • ParadoxFilm
                  /#18853589

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

                  • RomanPokrovskij
                    /#18853627

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

                    • ParadoxFilm
                      /#18853643

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

  4. RomanPokrovskij
    /#18853489

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

    • ParadoxFilm
      /#18853497

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

  5. corr256
    /#18854081 / +1

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

  6. FJCrux
    /#18854283

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

  7. ETCDema
    /#18855843

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

    • thinking
      /#18855873

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

      • ETCDema
        /#18855947

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

  8. olen
    /#18856681 / -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">

    • RomanPokrovskij
      /#18857147 / -1

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

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

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

      • olen
        /#18857767

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