Я бы хотел начать серию статей на Хабре о фреймворке DotVVM. Для ознакомления начнем с простого TODO списка.
Фреймворк разработан на базе ASP.NET. Он прост в изучении и позволяет создавать бизнес-приложения и SPA без JavaScript кода. Все за счет большого количества готовых элементов управления.
Следующие статьи будут посвящены тому, с чего все началось, каково было писать собственный фреймворк и его поддержку в Visual Studio, а также какие фичи есть в последнем релизе, который вышел в начале этого года.

Для первого проекта я буду использовать Visual Studio 2017 расширением для DotVVM. После установки расширения при создании должны появится новые типы проектов. Выберем новый DotVVM .NET Core проект.



После создания нового DotVVM проекта, появится следующая файловая структура.



Основные файлы

  • Program.cs является основной точкой входа приложения, которое присутствует во всех .NET-приложениях .NET Core. Если вы заглянете внутрь этого файла, вы увидите, что он настраивает Kestrel, интеграцию IIS, устанавливает содержимое корневого каталога и запускает веб-хост.
  • DotvvmStartup.cs — это класс, который содержит конфигурацию для DotVVM. Вы можете найти метод ConfigureRoutes со строкой которая регистрирует маршрут по умолчанию:

    config.RouteTable.Add ("Default", "", "Views/default.dothtml");

    Если пользователь посетит домашнюю страницу, DotVVM отобразит страницу Views /default.dothtml.
  • Startup.cs содержит два метода, вызываемых при запуске приложения. Метод ConfigureServices используется для регистрации служб в объекте IServiceCollection. Понадобится вызвать services.AddDotVVM для регистрации услуг, требуемых DotVVM. Метод Configure настраивает все промежуточный слой ASP.NET. Также нужно вызвать

    app.UseDotVVM <DotvvmStartup>

    для регистрации промежуточного слоя DotVVM, которое будет обрабатывать HTTP-запросы.

Кроме того, проект содержит папки Views и ViewModels. Первая папка содержит файл с именем default.dothtml, вторая папка ViewModel класс DefaultViewModel.cs. Эти два файла вместе создают веб-страницу DotVVM.

DotVVM использует паттерн Model-View-ViewModel (MVVM).

View (Представление) — это файл с расширением .dothtml. В принципе, это HTML-файл с несколькими синтаксическими атрибутами: директивы, элементами управления и привязки данных.

ViewModel — это класс C# с двумя целями:
Он содержит состояние страницы. Другими словами, все, что может быть изменено пользователем на странице. Если у вас есть TextBox на странице, вам нужно где-то сохранить его значение. В DotVVM вам просто нужно объявить свойство типа string в классе ViewModel и привязать ее к TextBox при помощи свойства Text.
Для обработки команды нужно просто объявить публичный метод в ViewModel.

В наш список мы сможем добавлять текущие дела как обычный текст и проверять их валидность при добавлении. Также дела можно будет отмечать их как сделаные.
Сначала добавим ToDoItem.cs класс.


    public class ToDoItem
    {
        [Required]
        public string Text { get; set; }

        public bool IsDone { get; set; }
    }

Так, как мы не хотим добавлять пустые дела без текста, мы будем использовать DataAnnotations [Required].

Добавим код в View и ViewModel

ViewModelDefaultViewModel.cs


public class DefaultViewModel : MasterPageViewModel
    {
//ToDoItem, который мы будем добавлять
        public ToDoItem ToDoItem { get; set; } = new ToDoItem();
        public DefaultViewModel()
        {
        }

Представление (View)Default.dothml

   <label for="todotext">Item:</label>
   
   //привязка к тексту ToDoItem
    <dot:TextBox  ID="todotext"Text="{value: ToDoItem.Text}" />

В DotVVM работает со встроенными элементами управления и их свойствами. Для работы со списками используется <dot:Repeater>. Привязка образуется через свойство DataSource. При этом все внутренние элементы автоматически изменят свой контекст объекта в списке. Добавим список типа List в ViewModel и Repeater в View.

ViewModelDefaultViewModel.cs


public class DefaultViewModel : MasterPageViewModel
    {
        //ToDoItem, который мы будем добавлять
        public ToDoItem ToDoItem { get; set; } = new ToDoItem();

        public List<ToDoItem> ToDoItems { get; set; } = new List<ToDoItem>();

        public DefaultViewModel()
        {
        }
    }

    <label for="todotext">Item:</label>
    
    //привязка к тексту ToDoItem
    <dot:TextBox  ID="todotext"Text="{value: ToDoItem.Text}" />

     <hr />
     <dot:Repeater DataSource="{value: ToDoItems}">
            <span>{{value: Text}}</span>
            <span Visible="{value: IsDone}">Done!</span>
     </dot:Repeater>

 

Осталось добавить функции. Одну для добавления и одну для пометки дела в списке. Добавим их в ViewModel.


    public class DefaultViewModel : MasterPageViewModel
        {
            //ToDoItem, который мы будем добавлять
            public ToDoItem ToDoItem { get; set; } = new ToDoItem();
    
            public List<ToDoItem> ToDoItems { get; set; } = new List<ToDoItem>();
    
            public DefaultViewModel()
            {
            }

            public void AddTodoItem(ToDoItem item)
            {
                ToDoItems.Add(ToDoItem);
                ToDoItem = new ToDoItem();
            }
    
            public void MarkAsDone(ToDoItem item)
            {
                item.IsDone = true;
            }
        }

В View добавим две кнопки <dot:Button/>. Текст кнопки передается через свойство Text а метод выполнения через Click. Также можно использовать свойство IsSubmitButton, при помощи которого кнопка будет нажиматься автоматически после нажатия Enter. Используем это свойство у кнопки, которая добавляет новое дело в список.

    <label for="todotext">Item:</label>
    
    //привязка к тексту ToDoItem
    <dot:TextBox  ID="todotext"Text="{value: ToDoItem.Text}" />

     <dot:Button Text="Add Item" IsSubmitButton="true" Click="{command: AddTodoItem(ToDoItem)}"></dot:Button>

     <hr />
     <dot:Repeater DataSource="{value: ToDoItems}">
            <span>{{value: Text}}</span>
            <span Visible="{value: IsDone}">Done!</span>
     </dot:Repeater>

 

Осталось к каждому делу в списке добавить кнопку, которая обозначит дело как сделанное. Должна отобразится надпись «Done» а кнопка должна исчезнуть. Чтобы сделать привязку в контекст DefaultViewModel из елемента Repeater, добавим _parent. Чтобы передать весь обьект из списка, передадим в метод _this.

Последнее свойство, которое мы будем использовать в этом уроке, это Validation.Enabled. Валидация по умолчанию работает так, что любой постбэк блокируется если валидация не проходит. В нашем случае может возникнуть ситуация, когда мы захотим пометить дело как сделанное, но поле с текстом останется пустым. Чтобы разрешить постбэк и игнорировать валидацию при нажатии кнопки, изменим Validation.Enabled на значение false.

   <label for="todotext">Item:</label>
    
    //привязка к тексту ToDoItem
    <dot:TextBox  ID="todotext"Text="{value: ToDoItem.Text}" />

     <dot:Button Text="Add Item" IsSubmitButton="true" Click="{command: AddTodoItem(ToDoItem)}"></dot:Button>

     <hr />
     <dot:Repeater DataSource="{value: ToDoItems}">
            <span>{{value: Text}}</span>
            <p><dot:Button Validation.Enabled="false" Visible="{value: !IsDone}" Text="Mark as done" Click="{command: _parent.MarkAsDone(_this)}"></dot:Button></p>
        <hr />
     </dot:Repeater>

 

Существуют также расширенные средства управления, такие как GridView или FileUpload, а также элемент управления SpaContentPlaceHolder, который превратит веб-приложение в Одностраничное приложение.
Вы также можете использовать разные типы привязок, локализацию RESX и Action фильтры, но об этом в следующей статье.
Спасибо за внимание.

Ссылки:

DotVVM на GitHub
Финальный проект

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


  1. Mikluho
    16.04.2018 16:49
    +2

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


    1. SbWereWolf
      16.04.2018 18:13
      +1

      +1. У меня чисто один вопрос зачем это нужно? что бы писать SPA на C# без JavaScript? В смысле DotVVM это замена использованию JavaScript на фронт энде?


  1. ZonerM Автор
    16.04.2018 22:34

    Простите за неопытность)
    Именно так. Фреймворк разрабатывался для того, чтобы за счет большого количества готовых элементов управления и понятного синтаксиса создавать бизнес-приложения и SPA без JavaScript кода.


    1. Mikluho
      17.04.2018 13:15

      Говоря «SPA без JavaScript кода» вы имеете ввиду отсутствие js в клиентском коде или то, что программисту не нужно его писать?
      Да и чем ваш фреймворк лучше какой0нибуль библиотеки компонентов, типа devexpress?
      И ещё вопрос — вы упоминаете postback, это именно отправка данных через post формы? Из-за которой нельзя пользоваться кнопкой back в браузере?


      1. ZonerM Автор
        17.04.2018 20:29

        Devexpress это только элементы управления. DotVVM это готовый фреймворк, который отвечает также за взаимодействия между сервером и клиентом.
        Кстати, елементы управления Devexpress можно использовать в DotVVM проекте.


        1. Mikluho
          18.04.2018 09:33

          Ну… с компонентами devexpress обмен клиент-сервер тоже работает… примерно сам собой, т.е. не требует особых усилий со стороны программиста.

          И да, хочется увидеть ответы и на другие вопросы.


  1. kekekeks
    17.04.2018 08:59

    _parent.MarkAsDone

    Я правильно понимаю, что вот эта штука приводит к вызову на сервер а ля ASP.NET Web Forms? Что происходит с вью-моделью? Она всё это время на сервере висит в памяти? Что происходит в таком случае при перезапуске сервера?


    1. oddmanout
      17.04.2018 11:34

      Вью-модель шлётся с клиента при постбэке. После выполнения команды, модифицированная модель сериализуется и шлётся обратно клиенту.


      1. ZonerM Автор
        17.04.2018 20:08

        ViewModel посылается весь не всегда. Существует атрибут Bind и Static command. Разбору взаимодействия между сервером и клиентом будет посвящена отдельная статья.


        1. kekekeks
          17.04.2018 21:04

          Меня интересует наличие стейта в памяти сервера, как это происходит в том же Ooui.


          1. ZonerM Автор
            17.04.2018 22:02

            В двух словах, ViewModel находящийся на сервере, находится там только на время http запроса. Перезапуск на ViewModel никак не повлияет, так-как в самом ViewModel есть все необходимое для обработки запроса.


            1. lair
              17.04.2018 22:11

              То есть в вашем примере весь список ToDo будет летать между сервером и клиентом на каждом запросе?


              1. ZonerM Автор
                17.04.2018 23:34

                Нет, только запакованный в JSON ToDoItem, Velidation rules и ещё пара вещей. Подробный разбор выйдет как можно скорее в следующей статье.


                1. lair
                  18.04.2018 00:44

                  А где же хранится список?