(MVC) MVC (2010 год)

Мой первый сайт на MVC 3 Razor

Visual Studio 2010 В конце 2010-го года я поставил себе новую микрософтовскую студию и сделал свой первый сайт на ASP.NET MVC 3 RAZOR. Надо сказать, что эта технология гораздо больше похожа на простой ASP десятилетней давности, чем на ASP.NET - и только поэтому у меня получилось сделать этот сайт. Весь багаж знаний и отработанных за последние пять лет библиотек на NET 2.0 - мне ни пригодился ничуть, а устоявшиеся за последние годы стереотипы только мешали мне программировать в NET 4.0

Возможно для меня лично сменить основную рабочую платформу хостинга с Windows на Linux и заодно и сменить SQL-сервер (чтобы начать делать сайты в MONO) оказалось проще, чем перейти от ASP.NET к ASP.NET MVC. В MVC утерялась простота и прозрачность Web-программирования - основные обработки QueryString (которые я делал своими собственными, упакованными в библиотеки функциями) стали теперь делаться в мутном коде обработки маршрутов, утопленнх в классе Route. Кроме того, в IIS 7 появился новый режим работы, в который тоже зашит код маршрутизации URL. В сущности мой собственный простой и понятный код в сто строчек оказался заменен сложнопонимаемым монстром, которого теперь нам навязал MS - и под который теперь всем придется подстраиваться.

Пропала также красота и лаконичность бейсика 2005, в нем появились странные синтаксические конструкции - типа анонимных функций, лямбда-выражений и языковых конструкций Linq. Появились новые чемоданы знаний, которые надо таскать с собой чтобы программировать на бейсике, например концепция ковариация/контрвариация. А этих чемоданов в языке и так прибавлялось немеряно каждый год (считая от Visual Basic 6). Хотя, сознаюсь, кое что меня в новой версии бейсика откровенно порадовало - прямой замес XML в код бейсика и автопродолжение команды на следующую строчку.

Более ограниченным и убогим стал функционал Visual Studio 2010 по сравнению с предыдущей Visual Studio 2008. Начиная от того, что нас кинули с обещанной API поддержки в NET формата MS HELP. И вот нате-здрасте - в VS2010 help в привычном виде с перечнем топиков изчез вообще. Правда это возможно вылечить сторонней прогой H3Viewer. Более органиченной стала и бесплатная редакция Visual Studio. Теперь в бесплатной версии Visual Stusio 2010 сайты делать уже нельзя - скомпилировать сайт в указанную директорию и выложить ее на хостинг не разрешается. В бесплатной Visual Studio 2010 Express Edition разрешается только немного поиграться с собственным кодом на локальной машине. Зато потребление ресурсов в VS2010 вдвое увеличилось по сравнению с VS2008 и на старых кампутерах все работает еще медленнее.

У новой микрософтовской студии есть и некоторые положительные моменты, сокращающие отставание микрософтовской девелоперской среды от бесплатных OpenSource сред разработки, таких как NetBeans и Eclipse. Менеджер расширений в новой Visual Studio 2010 уже почти такой же как в NetBeans. Правда не настолько продвинутый функционально. В NetBeans можно каждое отдельное расширение догружать из самой среды (с предварительным просмотром классов), отдельно сбрасывать на диск и подключать из IDE позже или вообще вручную в XML-файл дополнений. Можно четко видеть версии всех библиотек для каждого функционального дополнения, видеть зависимости функциональных расширений друг от друга, менять любые версии дополнений. Пока такого делать в микрософтовкой студии нельзя - но радует что менеджер дополнений наконец-то хотя бы появился.

В Visual Studio 2010 (для движка RAZOR) вообще исчез визуальный дизайнер ASP.NET, а для движка ASPX визуальный дизайнер превратился в некий бессмысленный предпросмотр (аналогичный браузерному) - в сущности мы опять вернулись к программированию в Нотепаде. Если бы не подсветка и подсказка - программирования в MVC в точности совпадало бы с программированием в Нотепаде. А ведь вся идея ASP.NET была в том, чтобы навсегда уйти от программирования в Нотепаде, уйти от огромных и непрогнозируемо работающих за кулисами библиотек (типа библиотеки маршрутизации в MVC), создать технологию быстрой визуальной разработки. В сущности для этого в ASP.NET работает лишь один JAVASCRIPT-код и ASP.NET-конвеер, который вполне прогнозируемо увязывает клиенские события с серверным кодом. Теперь старого ASP.NET конвеера Page_Load->Comtrol_Load->MasterPage_Load уже нет. Хотя конечно этого конвеера не жалко - уж больно гнилой он был, когда надо было увязать множество вложенных контролов, саму страничку и masterpage. Но я (и все остальные) довольно таки неплохо научились с ним работать за последние восемь лет. Но теперь этого конвеера из событий странички уже нет - и все на чем набивали руку последние восемь лет - навсегда исчезло.

В MVC код разметки страницы опять превратился в адскую лапшу из тегов разных языков - html-теги прихотливо перемешиваются со странными директивами бейсика (новой версии). Все что мы уяснили для себя при переходе от простого ASP к ASP.NET в 2002-м году - нет ничего хуже лапшеобразного кода (когда впеременку идет html-разметка и поверх нее код бейсика For Each). Теперь опять - да здравствует лапша из кода разных языков! Если уж так, то лучше бы напрямую (без всякого Linq) черезстрочно мешали бейсик и SQL, чем додумались замешивать венегред из бейсика и html.

Теперь в MVC предполагается что для отображения страничке подается обьект, который надо предварительно обязательно приготовить - а потом странным мастером создать View (шаблон странички), который предназначен для отображения именно этого обьекта. В начале страничек теперь появились странные директивы, описывающие какой именно обьект способна отображать эта страничка. При чем в web-программировании вообще обьекты? Ведь браузер передает на сервер в виде постбека просто строку текста, Web-сервер должен принять эту строку текста и сформировать новую html-строку. В ASP.NET обьекты обрабатываются в коде и отдаются либо контролам в виде коллекций, либо ASP.NET страничка может вообще состоять из одного тега literal, в котором собтсвенным ASP.NET-кодом можно сформировать любой нужный html. Нелишне напомнить, что наиболее распространенные и быстрые языки вообще не поддерживают обьекты - например Perl или SQL. С этой точки зрения - подход MVC это какой-то обьектный экстремизм за гранью разумного.

Правда, кое что в MVC меня порадовало. Опять (как в простом ASP) - можно создавать странички из нескольких тегов forms. При переходе в 2002-м году с ASP на ASP.NET у меня именно наличие единственного тега forms в ASP.NET вызвало наибольший внутренний протест. Но, увы, вместо доработки ASP.NET (в части усовершенствования конвеера, поддержки множества тегов forms, поддержки фреймов и так далее) - микрософт в очередной раз нас всех тупо кинул - и бросил разработку ASP.NET вообще. Похоже MS наняло каких-то индусов (никогда не слышавших об ASP.NET) и, видимо, эти индусы сделали для MS совершенно иной продукт - MVC (никак не связанный с существущими технологиями MS). А отдел маркетинга MS просто прикрутил к названию нового продукта префикс ASP.NET (чтобы сохранить видимость преемственности и сгладить впечатление от кидка).


Итак, далее я покажу как я сделал свой первый сайт на MVC 3 RAZOR. Это может послужить хорошим step-by-step руководством для тех, кому тоже придется отказаться от своих наработок на ASP.NET и с нуля начать Web-программирование в парадигме десятилетней давности. При этом на тех деталях, которые в новой логике программирования совпадают с ASP.NET, я останавливаться не буду (собственно разметка страниц и код классов моего сайта), а покажу лишь ключевые моменты MVC-программирования, которых не было в ASP.NET.


Движков шаблонов страниц (View) в MVC существует два - ASPX и RAZOR. Первый хоть и носит название ASPX, но к ASPX прямого отношения не имеет - гораздо больше напоминает древний движок простого ASP из Visual IntedDev. RAZOR движок произвел на меня более приятное впечатление - поэтому этот сайт я сделал на нем.

Сайт в NET 4.0 (как и в NET 2.0) можно делать в проектной и в беспроектной архитектуре. В беспроектной это просто каталог - как в ASP.NET и у него обычный global.asax из простого ASP и ASP.NET. В проектной архитектуре есть некое подобие свойств Win-приложения, другой конвеер и другой Global.asax. Далее я буду делать все в проектной архитектуре, а ненужные файлики примера MVC-сайта удалю потом. Разверну я свой сайт в IIS6.



Далее я выполняю все предварительные косметические действия по настройке шаблонного проекта. Добавляю папочки WebClient и Images, в которой лежат нужные мне работы для сайта рисуночки, файлик со стилями проекта, убираю ненужную папку для MDF-файла, из которой умеет подхватывать базы SQLExpress, а вместо этого устанавливаю нормальную connectionstring к моей версии MS SQL.

Вставляю волшебные строчки, чтобы в этом проете могли работать ASHX-хандлеры и ASPX-странички. Я выбрал себе для работы имя контроллера Payment - поэтому его и впишу в имя дефолтного правила для обработки URL. После создания шаблона сайта его можно попробовать запустить - чтобы увидеть html-форму стандартного приложения.



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

Далее смотрю, как в движке Razor стартовая страничка приложения запускает мастер_page и вписываю туда свою начальную страничку приложения.



Создаю папку Payment для шаблонов страниц и добавляю туда шаблон страницы Index.vbhtml (после первой компиляции классы для подсказки уже есть). При создании стартовой странички указываю мастер-пейдж проекта M1.vbhtml - появляется первая шаблонная страничка, теперь надо создать шаблон Master-page, который был указан для этой странички. Это обычная html-страничка, в теле которой есть @RenderBody - в это место будут включено тело шаблона Index (и других).



Теперь создаю собственно первую простейшую страничку проекта Paymetnt/index.vbhtml. Модель, по которой работает шаблон странички указана в директиве @ModelType, поэтому все работает с подсказкой.



Теперь создаю контроллер для заполнения этого шаблона - в нем должно быть минимум Index (Page_load для начального заполнения странички) и указанный мною метод SetBarcode, который примет Postback от странички. Это будет начальное правило по умолчанию в моей таблице URL-маршритизации. Имя контроллера (в соответствии с правилом роутинга URL, прописанным в Global.asax) должно совпадать с именем папки с View - шаблонами страниц.



Создаю код метода SetBarcode, в котором принимаю переменную отправленную с html-странички.



Теперь можно попробовать запустить проект. Ура, первая страничка с заглушкой вместо модели работает - проверяю как контроллер ловит постбек.


Постбек проходит нормально (дальше видно что следующей странички еще нет) - значит начинаем делать вторую страничку с более сложной моделью.


Для начала создаю модель, с которой будет работать мой шаблон SetBarcode. У меня есть SQL-база, по которой будет работать страничка.



В ASP.NET MVC модели реляционных данных бывают двух типов - Entity framework и Linq to SQL. Entity framework считается последней, более сложной и универсальной версией. В этом проекте мне это совсем не нужно, зато нужна прямая поддержка XML. Поэтому решаю делать на более простом Linq to SQL.



У меня есть табличка MobileOperator, есть вьюшка, которая парсит XML в этой табличке и есть пейджинговая процедурка GetOperatorPage, которая выдает нужную страничку данных из этой процедурки. Создаю метод GetOperatorPage который должен выдавать обьекты типа GetOperator - на этом все, модель для изготовления шаблона (View) готова.



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



В контроллере указываю новое View - куда будет переход после постбека и набор данных, который сгенерирован вызовом процедуры GetOperatorPage запускаю страничку - все как-то запускается и отображается.



Это простейший вариант обработчика события. В принципе обработчик можно вернуть следующие результаты:

В целом контроллер программируется просто, никаких особенностей по сравнению с ASP.NET нет - доступны обычные User, Session, Server, Request, Response , на входе в параметрах контроллеру поступает FormCollection, также что-то может поступить (и можно передать) через ViewDataDictionary. В контроллере нужно только одним из вышеперечисленных методов сформировать на выходе требуемый результат, которым нужно накормить View (или редиректнутся).

Другое дело - View. В MVC присутствует много совершенно новых объектов, которых не было в ASP.NET. Например используемый мною литерал Model совсем не является ключевым словом (как может показаться сначала), а является обычным свойством странички:

Все эти параметры работы MVC-странички можно увидеть в отладчике:



Ну а можно поставить пакет Glimpse - которое вытащит все эти параметры прямо в браузер и покажет рядом с FireBug.

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


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

В ASP.NET вестка этого сайта у меня выглядела вот так:


   1:  <div style="height: 10px"></div>
   2:  <asp:DataList ID="DataList1" runat="server" RepeatColumns="3" CellSpacing="10">
   3:          <ItemTemplate>
   4:              <table border="1" cellpadding="5px" cellspacing="0px">
   5:                  <tr>
   6:                      <td>
   7:                          <asp:ImageButton ID="ImageButton1" runat="server" OnClick="ImageButton1_Click" /><br />
   8:                      </td>
   9:                  </tr>
  10:                  <tr>
  11:                      <td>
  12:                          <asp:LinkButton ID="LinkButton1" Font-Size="Small" runat="server" OnClick="LinkButton1_Click">LinkButton</asp:LinkButton>
  13:                      </td>
  14:                  </tr>
  15:              </table>
  16:              &nbsp;
  17:          </ItemTemplate>
  18:  </asp:DataList>
  19:  <asp:Literal ID="Literal1" runat="server"></asp:Literal>
  20:  <div style="height: 10px"></div>

А весь код этой странички был такой:


   1:   
   2:      Sub FillCurrentPage()
   3:          'текущая отображаемая пейджером страничка данных
   4:           Dim DVC As Data.DataView = MobileOperatorCount.Select(New DataSourceSelectArguments)
   5:          Dim Count1 As Integer = DVC(0)("Count")
   6:          'заполнили пейджер 
   7:          Dim MyPager As New NewPager
   8:          Literal1.Text = MyPager.GetHtmlTag(Count1, MobileOperatorPage.SelectParameters("PageNum").DefaultValue)
   9:          'и собственно запрос за страничкой данных
  10:          Dim DV1 As Data.DataView = MobileOperatorPage.Select(New DataSourceSelectArguments)
  11:          DataList1.DataSource = DV1
  12:          DataList1.DataBind()
  13:      End Sub
  14:   
  15:      Protected Sub DataList1_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataListItemEventArgs) Handles DataList1.ItemDataBound
  16:          If e.Item.DataItem IsNot Nothing Then
  17:              Dim ImageButton1 As ImageButton = CType(e.Item.FindControl("ImageButton1"), ImageButton)
  18:              ImageButton1.CommandName = CType(e.Item.DataItem, Data.DataRowView)("ID")
  19:              Dim LinkButton1 As LinkButton = CType(e.Item.FindControl("LinkButton1"), LinkButton)
  20:              LinkButton1.Text = CType(e.Item.DataItem, Data.DataRowView)("name")
  21:              LinkButton1.CommandName = CType(e.Item.DataItem, Data.DataRowView)("ID")
  22:              ImageButton1.ImageUrl = Go.BaseURL & "WebClient/interface/default/images/" & CType(e.Item.DataItem, Data.DataRowView)("image")
  23:          End If
  24:      End Sub

В MVC у меня получилась вот такая верстка (для самого убогого из возможных пейджера - лишь бы работало). Особенности синтаксиса RAZOR вы можете посмотреть здесь.


   1:  @ModelType IEnumerable(Of Terminal_MVC.GetOperatorPageResult)
   2:  @Code
   3:      Layout = "~/Views/Shared/M1.vbhtml"
   4:  End Code
   5:  <center><h3>Выберите оператора</h3></center>
   6:  <table cellspacing="10">
   7:      @For row As Integer = 0 To ViewData("PageSize") / 3 - 1
   8:          @<tr>
   9:              @Code
  10:              Dim ItemIndex1 As Integer = row * 3
  11:              Dim ItemIndex2 As Integer = row * 3 + 1
  12:              Dim ItemIndex3 As Integer = row * 3 + 2
  13:              End Code
  14:              <td>
  15:                  @If ItemIndex1 <= Model(.Count - 1 Then
  16:                      @<a href="@Url.Action("SetOperator", New With {.id = Model(ItemIndex1).ID})" >
  17:                          <img src="@Href("~/WebClient/interface/default/images/" & Model(ItemIndex1).image)" alt="@Model(ItemIndex1).name" style="border:0;" />
  18:                      </a>
  19:                  End If
  20:              </td>
  21:              <td>
  22:                  @If ItemIndex2 <= Model(.Count - 1 Then
  23:                      @<a href="@Url.Action("SetOperator",New With {.id = Model(ItemIndex2).ID})" >
  24:                          <img src="@Href("~/WebClient/interface/default/images/" & Model(ItemIndex2).image)" alt="@Model(ItemIndex2).name" style="border:0;" />
  25:                      </a>
  26:                  End If
  27:              </td>
  28:              <td>
  29:                  @If ItemIndex3 <= Model(.Count - 1 Then
  30:                      @<a href="@Url.Action("SetOperator",New With {.id = Model(ItemIndex3).ID})" >
  31:                         <img src="@Href("~/WebClient/interface/default/images/" & Model(ItemIndex3).image)" alt="@Model(ItemIndex3).name " style="border:0;" />
  32:                      </a>
  33:                  End If
  34:              </td>
  35:          </tr>
  36:      Next
  37:  </table>
  38:  <center>
  39:      <ul>
  40:          @For i = 0 To ViewData ("PagerCount") - 1
  41:              @<li style="display: inline; padding: 10px">@Html.ActionLink(i.ToString, "Pager", New With {.id = i})
  42:              </li>
  43:          Next
  44:      </ul>
  45:  </center>

Это конечно совершенно другой уровень программирования. На ASP.NET достаточно было тыкныть в Datalist и выбрать заполнение таблички - построчное или поколоночное. Люди, которые делали MVC - в глаза не видели ASP.NET. Проблемы не только в подходе, игнорирующем наработки последних восьми лет, но в самой Visual Studio. Проклятые микрософтовцы так и не добились чтобы программный код НЕ ПЕРЕФОРМАТИРОВАЛСЯ при открытии/закрытии странички в дизайнере. Открываешь/закрываешь HTML-шаблон странички - а там уже вместо твоего собственного кода оказываются перемешанные помои. Впрочем, шаблон странички в форме View открыть в дизайнере нельзя вообще!

Код этого фрагмента получается довольно простой (хотя и не настолько простой, как код ItemDataBound в ASP.NET):


   1:  Namespace Terminal_MVC
   2:      Public Class PaymentController
   3:          Inherits System.Web.Mvc.Controller
   4:   
   5:          Dim OperatorGroupID As Integer = 1
   7:          '
   8:          'Для пейджера
   9:          Dim RowCount As Integer
  10:          Dim PageSize As Integer = 30
  11:          '
  12:          ' GET: /Payment
  13:          Function Index() As ActionResult
  14:              Return View()
  15:          End Function
  16:          '
  17:          ' POST: /Payment/SetBarcode
  18:          <HttpPost()>
  19:          Function SetBarcode(ByVal collection As FormCollection) As ActionResult
  20:              ViewData("Barcode")  = collection("BarCode")
  21:              Return RedirectToAction("OperatorPage")
  22:          End Function
  23:          '
  24:          ' GET: /Payment/OperatorPage
  25:          Function OperatorPage() As ActionResult
  26:              Return RefreshPage(1)
  27:          End Function
  28:          '
  29:          ' GET: /Payment/Pager/1
  30:          Function Pager(ByVal id As Integer) As ActionResult
  31:              Return RefreshPage(id)
  32:          End Function
  33:          '
  34:          'рефреш заданной странички
  35:          Function RefreshPage(ByVal id As String) As ActionResult
  36:              GetRowCount()
  37:              ViewData("PagerCount") = RowCount \ PageSize + 1
  38:              ViewData("PageSize") = PageSize
  39:              Return View("OperatorPage", GetOnePage(id))
  40:          End Function
  41:          '
  42:          'считать общее количество записей
  43:          Function GetRowCount() As Integer
  44:              Dim SqlDataContext As New MobileOperatorDataContext()
  45:              RowCount = SqlDataContext.ExecuteQuery(Of Integer)("select count(*) as [Count] from GISIS.dbo.MobileOperator where GroupID=" & OperatorGroupID.ToString)(0)
  46:          End Function
  47:          '
  48:          'Считать текущую страничку
  49:          Function GetOnePage(ByVal PageNum As Integer)
  50:              Dim dataContext As New MobileOperatorDataContext
  51:              Return dataContext.GetOperatorPage(OperatorGroupID, PageSize, PageNum).ToList()
  52:          End Function
  53:   
  54:          ' GET: /Payment/SetOpetator/1
  55:          Function SetOperator(ByVal id As Integer) As ActionResult
  56:              Return View()
  57:          End Function
  58:   
  59:      End Class
  60:  End Namespace

Я уже писал в топике Знакомство с Visual Studio 2010 что один из радикальных глюков Билогейтсовских индусов при разработке Visual Studio был в том, что они не сделали нормального отладчика в Visual Studio. В сущности это не столько глюк идеологии MVC, сколько Visual Studio, которая является инструментом программирования для MVC. В MVC все закручено на Лямбда-выражениях, анонимных функциях и LINQ - а отладчика-то для них и нет !!!

Второй адский глюк Билогейтсовских индусов в том, что код контроллера нельзя менять на ходу !!! Любой ASP.NET-программист будет просто в шоке, узнав это. В это трудно поверить, тем не менее это так. То есть надо остановить web-сервер, исправить одну буковку и перезапустить все заново! Похоже PHP-шные уроды, которых нанял Билл Гейтс в каких-то курятниках и свинарниках Бомбея - понятия не имеют об ASP.NET, не видели как это работает, не понимают чего ждут программисты в качестве развития ASP.NET. Эти PHP-шные уроды никогда не слышали о фоновой компиляции и тупо предлагают нам взамен ASP.NET среду программирования, которая у нас была 20 лет назад в Visual InterDev .

Но, тем не менее, рожаем мозгом этот код и верстку, тыкаем чтобы проверить - все работает!

Кое каких рисунков у меня не хвататает в каталоге WebClient - но это уже не важно. Пейджер тоже можно украсить получше. А пока можно двигаться дальше и делать следующую форму.

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


   1:  <operator id="890" cyber_id="890" group_id="8">
   2:    <name>Госпошлины ГИБДД</name>
   3:    <name_for_cheque>УВД ГИББД</name_for_cheque>
   4:    <inn_for_cheque />
   5:    <limit min="50" max="15000" />
   6:    <fields>
   7:      <field id="104" type="enum">
   8:        <name>Тип госпошлины</name>
   9:        <enum>
  10:          <item value="Регистрация ТС">Регистрация ТС</item>
  11:          <item value="Снятие ТС с учета">Снятие ТС с учета</item>
  12:          <item value="Выдача транзитных номеров">Выдача транзитных номеров</item>
  13:          <item value="Повторная выдача свидетельства">Повторная выдача свидетельства</item>
  14:          <item value="Внесение изменений в ПТС">Внесение изменений в ПТС</item>
  15:          <item value="ПТС">ПТС</item>
  16:          <item value="Регистрация мотоцикла">Регистрация мотоцикла</item>
  17:          <item value="Регистрация прицепа">Регистрация прицепа</item>
  18:          <item value="Техосмотр">Техосмотр</item>
  19:          <item value="Замена гос номера">Замена гос номера</item>
  20:          <item value="Передача по наследству">Передача по наследству</item>
  21:        </enum>
  22:      </field>
  23:      <field id="100" type="text">
  24:        <name>Фамилия Имя Отчество</name>
  25:      </field>
  26:      <field id="101" type="integer" minlength="10" maxlength="18">
  27:        <name>ОКАТО подразделения ГИБДД</name>
  28:        <comment>Введите код ОКАТО подразделения ГИБДД по месту прописки</comment>
  29:      </field>
  30:      <field id="106" type="masked">
  31:        <name>ИНН подразделения ГИБДД</name>
  32:        <mask>**********</mask>
  33:        <comment>Введите ИНН подразделения ГИБДД по месту прописки</comment>
  34:      </field>
  35:      <field id="102" type="text">
  36:        <name>Адрес плательщика</name>
  37:      </field>
  38:      <field id="103" type="masked">
  39:        <name>Номер телефона</name>
  40:        <mask>8 (***) ***-**-**</mask>
  41:        <comment>[b]Внимание![/b] Номер телефона вводится без "[b]8[/b]".</comment>
  42:      </field>
  43:    </fields>
  44:    <processor offline="1">
  45:      <check amount-value="10">
  46:        <url>http://proc.starpay.ru:10036/cgi-bin/gb/gb_pay_check.cgi</url>
  47:        <request-property name="NUMBER" field-id="100" />
  48:        <request-property name="ACCOUNT">
  49:          <fixed-text id="31||" />
  50:          <field-ref id="101" />
  51:        </request-property>
  52:        <request-property name="PURPOSE" field-id="104" />
  53:        <request-property name="ADDRESS" field-id="102" />
  54:        <request-property name="AGREE">
  55:          <fixed-text id="1" />
  56:        </request-property>
  57:        <request-property name="INN" field-id="106" />
  58:      </check>
  59:      <payment>
  60:        <url>http://proc.starpay.ru:10036/cgi-bin/gb/gb_pay.cgi</url>
  61:        <request-property name="NUMBER" field-id="100" />
  62:        <request-property name="ACCOUNT">
  63:          <fixed-text id="31||" />
  64:          <field-ref id="101" />
  65:        </request-property>
  66:        <request-property name="PURPOSE" field-id="104" />
  67:        <request-property name="ADDRESS" field-id="102" />
  68:        <request-property name="AGREE">
  69:          <fixed-text id="1" />
  70:        </request-property>
  71:        <request-property name="INN" field-id="106" />
  72:      </payment>
  73:      <status>
  74:        <url>http://proc.starpay.ru:10036/cgi-bin/gb/gb_pay_status.cgi</url>
  75:      </status>
  76:    </processor>
  77:    <commission>
  78:      <part value="50" />
  79:      <part min="1001" value="5%" />
  80:    </commission>
  81:    <image>ohr_gibdd.gif</image>
  82:  </operator>

Этих параметров может быть произвольное число и они бывают разных типов Enum, Int, Masked, Text - кроме того, в этом файлике определен специальный порядок упаковки результата в итоговое поле, которое будет отправлено на оплату через Cyberplat.

Распарсить нерегулярный XML в регулярную структуру - это нетривиальная задача, но в этом топике, посвященном MVC - я на этом останавливаться не буду. Для дальнейшего повестования об MVC существенно лишь то, что мой класс порождает коллекцию Fields As New Collections.Generic.List(Of OneCyberField), в которой есть все нужные поля, чтобы скормить эту модель шаблону странички.



Cоздаем шаблон странички (View) как и в предыдущих случаях:



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



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


   1:  @ModelType Collections.Generic.List(Of Terminal_MVC.OneCyberField)
   2:  @Code
   3:      Layout = "~/Views/Shared/M1.vbhtml"
   4:  End Code
   5:   
   6:  <center><h3>Введите параметры платежа</h3></center>
   7:   
   8:  <script type="text/javascript" language="javascript">
   9:  function save1(hiddenfield,storevalue)
  10:  {
  11:      var x = document.getElementById(hiddenfield);
  12:      x.value = storevalue;
  13:  }
  14:  </script>
  15:   
  16:  @Using Html.BeginForm("SetPaymentFields", "Payment")
  17:      
  18:      @<table>
  19:          @For Each One In Model
  20:      
  21:          Select Case One.FieldType
  22:              Case Terminal_MVC.CyberFieldsType._Enum_
  23:                  
  24:              @<tr>
  25:                  <td><b>@CType(One.FieldDefinition, Terminal_MVC.CyberEnum).Name</b>
  26:                  </td>
  27:                  <td>
  28:                      <input type="hidden"  id="@CType(One.FieldDefinition, Terminal_MVC.CyberEnum).ID" name="@CType(One.FieldDefinition, Terminal_MVC.CyberEnum).ID" />
  29:                      <select  onchange='save1("@CType(One.FieldDefinition, Terminal_MVC.CyberEnum).ID",this.options[selectedIndex].value);'>
  30:                          @For Each OneListItem In CType(One.FieldDefinition, Terminal_MVC.CyberEnum).EnumFields
  31:                              @<option id="@OneListItem.Value" value="@OneListItem.Value">@OneListItem.Text</option>
  32:                          Next
  33:                      </select>
  34:                  </td>
  35:              </tr>
  36:      
  37:              Case Terminal_MVC.CyberFieldsType._Int_
  38:                  
  39:              @<tr>
  40:                  <td><b>>@CType(One.FieldDefinition, Terminal_MVC.CyberInt).Name</b>
  41:                  </td>
  42:                  <td>@Html.TextBox(CType(One.FieldDefinition, Terminal_MVC.CyberInt).ID)
  43:                  </td>
  44:              </tr>
  45:              @<tr>
  46:                  <td colspan="2">@CType(One.FieldDefinition, Terminal_MVC.CyberInt).Comment ( @CType(One.FieldDefinition, Terminal_MVC.CyberInt).MinLength - @CType(One.FieldDefinition, Terminal_MVC.CyberInt).MaxLength  знаков )
  47:                  </td>
  48:              </tr>
  49:             
  50:                  
  51:              Case Terminal_MVC.CyberFieldsType._Masked_
  52:                  
  53:   
  54:              @<tr>
  55:                  <td><b>@CType(One.FieldDefinition, Terminal_MVC.CyberMasked).Name</b>
  56:                  </td>
  57:                  <td>@Html.TextBox(CType(One.FieldDefinition, Terminal_MVC.CyberMasked).ID)
  58:                  </td>
  59:              </tr>
  60:              @<tr>
  61:                  <td colspan="2">@CType(One.FieldDefinition, Terminal_MVC.CyberMasked).Comment ( @CType(One.FieldDefinition, Terminal_MVC.CyberMasked).Mask )
  62:                  </td>
  63:              </tr>
  64:   
  65:                  
  66:              Case Terminal_MVC.CyberFieldsType._Text_
  67:                  
  68:              @<tr>
  69:                  <td><b>@CType(One.FieldDefinition, Terminal_MVC.CyberText).Name</b>
  70:                  </td>
  71:                  <td>@Html.TextBox(CType(One.FieldDefinition, Terminal_MVC.CyberText).ID)
  72:                  </td>
  73:              </tr>
  74:              @<tr>
  75:                  <td colspan="2">@CType(One.FieldDefinition, Terminal_MVC.CyberText).Comment
  76:                  </td>
  77:              </tr>
  78:                 
  79:                  
  80:          End Select
  81:      
  82:              @<tr>
  83:                  <td colspan="2" style="height: 5px;">
  84:                  </td>
  85:              </tr>
  86:              @<tr>
  87:                  <td colspan="2" style="background-color: Gray; height: 1px;">
  88:                  </td>
  89:              </tr>
  90:              @<tr>
  91:                  <td colspan="2" style="height: 10px;">
  92:                  </td>
  93:              </tr>
  94:      Next
  95:      </table>
  96:      
  97:      @<input type="submit" value="OK" />
  98:      
  99:  End Using

Проверяю - как видите все работает, параметры постбека приходят нормально. Дальше можно переходить к следующей форме.



Думаю, принцип построения последующих форм понятен. Набор хелперов настолько ограничен, что даже простой Select/Option на форме выше мне пришлось сделать самому. Это напоминает не просто программирование в Нотепаде, это похоже на программирование на ассемблере. Почему умер простой ASP когда появился ASP.NET? Он имел существенно меньшую скорость разработки, чем ASP.NET - и как на простом ASP можно было зарабатывать деньги, когда заказчика интересует результат, а не название технологической платформы? А MVC это в чистом виде простой ASP, только с добавление обьектного экстремизма и дополнительных чемоданов знаний (типа LINQ), которые ничем не помогают, но которые надо таскать за собой. Хм, не знаю насколько это жизнеспособно при работе в режиме фрилансера, когда деньги приносит только скорость работы.

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

Мне просто хотелось сделать (в том числе для себя) некоторое сравнение подходов ASP.NET и MVC, а заодно сделать step-by-step инструкцию для тех, кто стоит на еще более ранней стадии знакомства с MVC и обдумать - есть ли смысл переходить с ASP.NET на MVC? А может проще перейти с ASP.NET на Flex? Для себя (по состоянию на конец 2010 года) я этого решения еще не принял. Вроде бы в MVC технологических возможностей побольше, сайт конвертируется в PHP, но скорость разработки снижается раз в десять наверное. А самые элементарные вещи, типа разворота данных в табличке вместо строк по столбцам (которые выполнялись в Datalist ASP.NET ровно в один клик мышкой) теперь становятся проблемой. Кроме того, для версток в MVC теперь Visual Studio недостаточно - нужны другие инструменты.

Думаю, описание остальных форм моего первого сайта на MVC уже ничего не добавят к заявленным целям этой странички и есть смысл на этих трех первых формах остановиться. Модели в этих первых трех формах у меня получились все разные - просто класс с одним полем, стандартная коллекция и SQL/LINQ-коллекция. Это будет полезный и общедоступный сквозной пример. В заключение я скажу пару слов о валидации (которую я прикрутил к этому моему сайту чуть позже) - таким образом на одной страничке будет сосредоточено описание не только трех моделей, но и трех видов постбеков странички к серверу - GET,POST и асинхронного.

Итак, в ASP.NET у нас было семь стандартных валидаторов, среди которых Custom Validator c серверной функцией валидации. Ставился он беспредельно просто - одним единственным drag-and-drop'ом - после чего указывалось в атрибуте OnServerValidate имя серверной функции валидации - и все! Функция валидации тупо писалась прямо в теле странички как ни в чем не бывало. Всю остальную черновую работу брала на себя среда ASP.NET (кстати в этом же контроле можно было указывать в ClientValidationFunction и клиентскую функцию валидации).


Для этой формы мне потребовался такой же точно валидатор. Понятно что для клиента такие расчеты как проверка корректности телефона по маске, ИНН или ОКПО будет нереально тяжеловесна (одних запросов в базу сколько тут нужно). Поэтому мне нужна была именно стандартная серверная валидация (подобная классической ASP.NET валидации) - которая бы отправляла асинхронный постбек к серверу и возвращала бы страничке результат валидации.

В MVC мне удалось решить эту задачку на JQUERY (есть и альтернативный путь - микрософтовскими AJAX-скриптами). Это решение я предлагаю вашему вниманию (и надеюсь вы можете на этом примере асазнать пропасть между классическим ASP.NET и ASP.NET MVC):


На форме я указал вот такие строки:

<input type="text" id="@CType(One.FieldDefinition, Terminal_MVC.CyberInt).ID" onchange="validate1(this.id,this.value);"  />

В терминах обьектного экстремизма эту же мысль для первой формы можно выразить вот так (а для третьей формы я даже затрудняюсь сформулировать это выражение в виде анонимной функции расширения):

@Html.TextBoxFor((Function(model) model.BarCode), New With {.onchange = "validate1(this.id,this.value);"})

После этого мне пришлось отладить вызов JQUERY и прием JSON с сервера:


   1:  <script type="text/javascript" language="javascript">
   2:  function validate1 (id,value) {
   3:      var x = document.getElementById(id);
   4:      $.getJSON("/Payment/Validate",
   5:      { id: x.id, value: x.value },
   6:      ret1);
   7:  }
   8:   
   9:  function ret1(json,success) {
  10:      var x = document.getElementById(json.id);
  11:      x.style.color = json.color;
  12:  }
  13:  </script>

Естественно сама JQUERY присутствует в HEAD мастер-странички (Layout-странички в новой индусской терминологии):

<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>

После этого мне пришлось в контроллере принять JSON, собранный с формы JQUERY-функций getJSON, сформулировать ответ контроллера в виде объекта JsonResult - и лишь после полной отладки этого враппера, я смог в теле функции Validate собственно приступить к смысловому алгоритму валидации (к тому самому алгоритму, который я начинал писать на ASP.NET через полсекунды после драг-анд-дропа кастом-валидатора на форму) :


   1:          ' GET: /Payment/Validate
   2:          Function Validate(ByVal ID As String, ByVal Value As String) As JsonResult
   3:              Dim Result As New JsonResult
   4:              '
   5:              'тут собственно алгоритм валидации, возвращающий "" или "red" в случае неверного вводимого символа
   6:              '
   7:              Result.Data = New With {.id = ID, .color = "red"}
   8:              Result.JsonRequestBehavior = Mvc.JsonRequestBehavior.AllowGet
   9:              Return Result
  10:          End Function

Еще несколько поучительных примеров использования jQuery я описал на страничках Как с помощью jQuery сделать флеш-ролик резиновым, AJAX подсказка/автозаполнение на jQuery, Как сделать простейший Web-handler - формирующий XML или JSON.


В заключение я бы хотел добавить сравнительный анализ MVC и ASP.NET из книги знаменитого микрософтовского пропагандиста Гайдара Магданурова (с которым я во многом согласен):


Преимущества WebForms Недостатки WebForms Преимущества MVC Недостатки MVC
Набор требований для выбора между технологиями MVC и ASP.NET:

WebFormsMVC
  
-+Полный контроль над HTML-разметкой
++/-Поддержка визуального редактора страниц
+/-+Возможность автоматического тестирования логики приложения
-+Возможность замены движка генерации разметки
++/-Высокая скорость разработки
+/-+Простота поддержки при необходимости частого внесения изменения
+/-+Небольшая команда разработчкиков
+-Использование готовых элементов управления
+/-+Простота поисковой оптимизации готового решения
++/-Приложение делается только для локальной сети
+-Необходимость быстрее разработать прототип для проверки идеи
+-Участие разработчиков, знакомых только с Windows-технологиями
+/-+Богатый скриптовый функционал на стороне клиента
+-Вэб-приложение является интерфейсом для базы данных
-+Приложение может быть сконвертировано в PHP


После написания моего первого сайта на MVC, мое мнение об MVC пожалуй не изменилось. Как-то на MVC конечно программировать возможно, но хочется закончить эту страничку с введением в технологию MVC ровно так же, как закончил страничку о Visual Studio 2010:

Адское сырье нам подсунули опять из MS под видом "пиридавой технологии". Где так необходимое и востребованное развитие ASP.NET? Где новые комплекты контролов? Возможности работать с фреймами, возможности нескольких тегов FORMS, полноценнй интрументарий изготовления контролов, которые можно было бы ложить в библиотеки и использовать во многих проектах? Нормальные инструменты вместо EVENTTARGET, позволяющие видеть еще в Page_Load от кого идет постбек. Думаю, каждый практикующий ASP.NET программист перечислил бы десятки позиций, которых ему не хватает для комфортной работы в ASP.NET. Если бы эти инструменты были бы реализованы, то технология ASP.NET заняла долю не 0,4% рынка web-приложений, а стала бы полностью конкурентной.

А меньше всего в развитии ASP.NET требовался ASP.NET URLRewriter, тем более он существует во множестве автономных OpenSource пакетов, свободно догружаемых в ASP.NET (и многие их использовали начиная с ASP.NET 1.1). A еще меньше требовалось полное исключение контролов - технология ASP.NET прекрасно позволяла работать и без них - просто по литералам (у меня множество страниц сделано совершенно без контролов). Ожидаемого и востребованного развития ASP.NET не произошло - вместо этого с огромной помпой подсовывают какое адское сырье.

От всего этого сырья даже до отсталого PHP - ровно как до Луны раком. Только когда в этой "пиридавой тихналогии" появятся ДЕСЯТКИ CMS хотя бы такого качества как JOOMLA и DRUPAL (вот первый попавшийся список из 237 PHP OpenSource CMS) - и когда в каждой из этих CMS появятся по несколько тысяч готовых бесплатных модулей (хотя бы такого качества и функционала как vluemart - где содержится полный готовый электронный магазин) - вот с этого момента (а не раньше) это чудо-пиридавая технология от билла-де-билла хоть как-то сможет вступить в конкуренцию с "отсталыми" технологиями - по цене среды девелопмента, по цене хостинга, по количеству багов в IDE, по количество плагинов в CMS, по цене операционной системы для хостинга сайта, по скорости освоения технологической платформы, по кроссплатформенности технологии, по удобству и бесплатности доступа к дополнительным модулям CMS, по функциональности этих модулей и тд и тд..

Жаль в общем-то, что MS всех кинула и прекратила развитие весьма-весьма неплохой технологии ASP.NET. Но раз уж вы попали на мою страничку об ASP.NET MVC - то я рекомендую вам почитать как делается верстка в ASP.NET MVC. Ну а тем, кому такое чтиво не асилить - можете почитать что-нибудь попроще - Подарки от Microsoft для ASP.NET программистов.



Comments ( )
Link to this page: //www.vb-net.com/MVC3_step_by_step/index.htm
< THANKS ME>