(MVC) MVC (2011 год)

Как сделать простейший Web-handler - формирующий XML или JSON.

web performance Хандлеры, формирующие XML - являются основой обмена данными. Это реальная альтернатива SOAP/WSDL-сервисам, ASMX-сервисам и прочим более тяжеловесным решениям.

Помимо легковесности и простоты - XML, формируемые хандлерами, достаточно легко шифруются. Я постоянно использую шированные на прикладном уровне XML, когда в этом возникает необходимость (например для защиты от копирования платных программ). У меня даже поднята своя собственная инфраструктура выдачи ключей шифровния и их энролмента (http://activator.vb-net.com/activate.ashx. Причем при таком механизме шифрования, полностью обходится стандартная платная инфраструктура сертификатов, прикрученная к интернету непонятно кем и непонятно для чего (но позволяющая выкачивать из пользователей интернета миллионы долларов). То есть компании покупают у Thawte ключи шифрования, которыми заверяют подлинность своих собственных открытых клоючей шифрования - а потом (в конечном итоге) перекладываются свои расходы на рядовых пользователей интернета. При шифровании XML, формируемыми в хандлерах эта вся инфраструктура платных заверений открытых ключей не нужна совершенно, а защищенность трафика получается ровно такая же, как в самых-самых крученых решениях.

Впрочем, еще более адская разводкой в области безопасности интернета, чем даже стандартная инфраструктура сертификатов - является Криптография по ГОСТ.


Не касаясь преимуществ шифрования трафика - подобными простейшими XML, сформированными хандлерами, реализуются в интернете десятки стандартных задач - начиная от построения SiteMap для целей SEO и обменом данных с Flex-приложениями, кончая формированием RSS-каналов сайтов и Шлюзов к платежным системам интернет-денег.

Хандлеры, формирующий XML используются при работе шлюзов с 1С, а полученный XML можно напрямую укладывать в базу, корректировать его там встроенными XML-функциями SQL, парсить на уровне SQL, парсить на уровне кода, а также собирать не просто строками (как будет показано ниже), но и более сложными специализированными классами. Подобный хандлер можно сделать на классах XLINQ, а не на простых строках.

Но я покажу здесь самый найпростейший из возможных вариантов - вариант в сто волшебных кликов мышкой.


Я начну с создания WebApplication на NET 4.0 - чтобы показать все варианты, потом удалю Web-приложение и сделаю просто сайт с хандлером. Просто сайт в беспроектной архитерктуре имеет ряд преимуществ перед веб-приложением. Основное преимущество - код можно править в Visual Studio во время просмотра странички и просто нажимать рефреш в браузере - и откорректированный код сразу же оказывается в браузере. В отличие от этого - изменение в код веб-приложения невозможны и для ничтожнейшей поправки надо делать стоп в Visual Studio и запускать приложение на выполнение снова и снова. Зато в web-приложении возможны какие-то фишки, которые невозможны в сайте.

Но общая рекомендация такова - можете обходится беспроектной архитектурой - обходитесь. Ну а мы для начала начнем с хандлера в составе проекта.



Здесь первый ключевой момент - в проект wevApllication надо обеспечить, чтобы парсер URL не преобразовывал URL при реквестах к хандлеру, это делается внесением в global.asax правила - {resource}.ashx/{patchinfo} - подробнее о преобраованиях URL читайте тут ASP.NET URL rewriting.



Теперь собственно добавляем шаблон хандлера.



Назначение этого хандлера - сформировать XML со списком файлов отображения для моего фотослайдера на FLEX. Пока создадим папочку для изображений (которую потом на хостинге смапируем с помощью виртуальных директорий IIS на реальную папку с изображениями).

Забросим в эту папку несколько изображений для отладки и поставим сразу же безопасность на эту папку - разрешено читать всем.



Теперь пора проверится - все ли работает в создаваемом нами проекте. Введем пару строк кода и проверимся:



Да все отлично. Теперь можно создавать реальный код. Но для начала я покажу структуру рисунков, которые я собираюсь показывать этим хандлером (и как я эти рисунки обычно создаю).

Все рисунки на этом моем сайте я создаю с помощью XNVIEW - а эта пакетный формирователь аватарок в этой проге создает уменьшенный рисунок-аватарку с суффиксом "_1":



Следовательно, нам потребуется сортировка файлов. А реализция сортировки требует реализации интерфейса IComparer, иначе непонятно по какому критерию мортировать файлы - по дате, размеру, времени создания, расширению или иначе. А нам нужна сортировака по имени.



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


   1:  Public Class CompareFileByName
   2:      Implements IComparer
   3:   
   4:      Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
   5:          If x.Name = y.Name Then
   6:              Return 0
   7:          ElseIf x.Name > y.Name Then
   8:              Return 1
   9:          ElseIf x.Name < y.Name Then
  10:              Return -1
  11:          End If
  12:   
  13:      End Function
  14:  End Class

Теперь вводим первый простейший вариант кода (который вы видите на скрине), и проверяем его:



Чудесно, все работает. Почистим проект от ненужных папок и скопируем его на хостинг. Я копирую его на один из своих собственных серверов.



Теперь пропишем доменное имя (хост-хеадер) для нашего будущего хандлера.



Теперь создадим новый web-сайт в IIS и внимание (!) сделаем в нем виртуальную директорию с нашим именем папки ImageSlider, использованным при отладке, но которая будет смотреть уже на реальные рисунки.



Теперь укажем, что это сайт на NET 4.0, а не на NET 2.0 и дефолтный документ этого узла.



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

В принципе, это можно было бы сделать отдельной функций бейсика, которая позволяет обходить все директории, да еще и позволяет указать несколько шаблонов файлов сразу (jpg и gif) - My.Computer.FileSystem.GetFiles(FolderName, Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories, {"*.gif", "*.jpg"}), но я для большей гибкости реализую эту функцию сам своим кодом - в той канве в которой я уже начал.

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



Теперь я просто перенесу существуюший код в обход одной директории, и добавлю в хандлер параметр и проверю, при котором алгоритм будет обходить все директории.



Опубликуем код и проверим его в реальной обстановке. Но перед этим я решил добавить код в Subversion. О системе SVN вы можете почитать на отдельной страничке моего сайта - Избавляемся от Team Foundation Server - ставим Subversion.



И еще я одну опарацию попутно произведу - перейду из проектной в беспроетную архитектуру. Если в проектной архитектуре у меня было свой свойства проекта (левый скрин), мастер публикации и Global.asax, то сейчас я выброшу все файлы проектной архитектуры и global.asax - свойства сайта на правом скрине будут теперь выглядеть гораздо скромнее, но зато теперь код можно будет менять без перезапуска проекта.



Собственно этот вариант мы бы получили, если бы изначально создавали сайт вот таким путем.



Итак, публикуем сайт.



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



Но и добавить фильтрацию только графических файлов несложно - это пару строк кода, которые будут отбирать только интересующие нас типы файлов.


Итак, остановимся на этом варианте кода - это и будет простейший вариант необходимого фотосладеру кода.


   1:  <%@ WebHandler Language="VB" CodeBehind="GetImageName.ashx.vb" Class="GetImageName" %>
   2:   
   3:  Imports System.Web
   4:  Imports System.Web.Services
   5:   
   6:  Public Class GetImageName
   7:      Implements System.Web.IHttpHandler
   8:   
   9:      Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
  10:          Try
  11:              Dim SliderFotos As String = HttpContext.Current.Server.MapPath("ImageSlider")
  12:              '
  13:              context.Response.ContentType = "text/xml"
  14:              context.Response.Write("<?xml version='1.0' encoding='UTF-8'?>")
  15:              context.Response.Write("<BestFotoNumbers>" & vbCrLf)
  16:              '
  17:              If context.Request.Params("all") Is Nothing Then
  18:                  ProcessOneFolder(context, SliderFotos)
  19:              Else
  20:                  Dim Folders As New IO.DirectoryInfo(SliderFotos)
  21:                  For Each OneFolder As IO.DirectoryInfo In Folders.GetDirectories()
  22:                      ProcessOneFolder(context, OneFolder.FullName)
  23:                  Next
  24:              End If
  25:              '
  26:              context.Response.Write("</BestFotoNumbers>" & vbCrLf)
  27:          Catch ex As Exception
  28:              context.Response.ContentType = "text/plain"
  29:              context.Response.Write(ex.Message)
  30:          End Try
  31:   
  32:      End Sub
  33:   
  34:      Sub ProcessOneFolder(ByVal context As HttpContext, ByVal FolderName As String)
  35:          Dim FileList As New IO.DirectoryInfo(FolderName)
  36:          Dim Files1() As IO.FileInfo = FileList.GetFiles("*.gif")
  37:          Dim Files2() As IO.FileInfo = FileList.GetFiles("*.jpg")
  38:          Dim Files3() As IO.FileInfo = FileList.GetFiles("*.png")
  39:          Dim Files(Files1.Length + Files2.Length + Files3.Length-1) As IO.FileInfo
  40:          Array.Copy(Files1, Files, Files1.Length)
  41:          Array.Copy(Files2, Files, Files2.Length)
  42:          Array.Copy(Files3, Files, Files3.Length)
  43:          Array.Sort(Files, New CompareFileByName)
  44:          For i As Integer = 0 To Files.Count / 2 - 1
  45:              If Files.Count >= i * 2 + 1 Then
  46:                  If IO.Path.GetFileNameWithoutExtension(Files(i * 2 + 1).Name).Contains(IO.Path.GetFileNameWithoutExtension(Files(i * 2).Name)) Then
  47:                      context.Response.Write("<BestFoto>")
  48:                      context.Response.Write("<Big>"  &  FileList.Name &  "/" & Files(i * 2).Name & "</Big>" & vbCrLf)
  49:                      context.Response.Write("<Small>" &  FileList.Name &  "/" &  Files(i * 2 + 1).Name & "</Small>" & vbCrLf)
  50:                      context.Response.Write("</BestFoto>" & vbCrLf)
  51:                  End If
  52:              End If
  53:          Next
  54:      End Sub
  55:   
  56:      ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
  57:          Get
  58:              Return False
  59:          End Get
  60:      End Property
  61:  End Class
  62:   
  63:  Public Class CompareFileByName
  64:      Implements IComparer
  65:   
  66:      Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
  67:          If x.Name = y.Name Then
  68:              Return 0
  69:          ElseIf x.Name > y.Name Then
  70:              Return 1
  71:          ElseIf x.Name < y.Name Then
  72:              Return -1
  73:          End If
  74:   
  75:      End Function
  76:  End Class

Итак, если вы вместе со мной проделали весь путь этой странички от начала до конца - вы не только получили хандлер для моего OpenSource фото-слайдера на Flex за сто кликов мышкой, но и научились настраивать IIS, мапить виртуальные директории IIS на реальные папки с данными, получили начальные понятия о сортировке, работе с массивами, работе с папками и файлами, проектной и беспроектной архитектурой Visual Studio 2010, о контроле версий с помощью Subversion, о методологии пошаговой реализации кода и поняли насколько принципиально важным компонентом в web является понятие handler.




Теперь я покажу step-by-step чуток другой мой хандлер, тоже являющийся важным компонентом моего Simple Flash Banner Rotator with BLUR Effect. Надеюсь, что такое JSON - знают все. Это тот же самый XML - только количество символов и тегов-паразитов сведено к минимуму. Ибо строковые обработки - циклически проходы по памяти и сравнение байтов памяти с шаблонами - это самые тормозные операции из всех, которые можно придумать. Поэтому и надо максимально убрать ненужные скобочки и теги - если смысл данных ясен из контекста их расположения.

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

Поскольку JSON (в отличие от XML) браузером напрямую не отображается - я также сделаю страничку http://pencil.vb-net.com/ для отображения JSON. Эту страничку я сделаю на jQuery и плагине к нему http://www.datatables.net/. Для разнообразия, я выведу в этом примере данные не оберткой вокруг SQL-процедуры, а чистым LINQ. Им же и пересортирую данные в случайном порядке.

Итак, создаем чистый web-проект и добавляем в него хандлер:



Теперь создадим буфер в памяти компьютера для чтения данных из SQL-сервера и разметим его путем перетаскивания таблички из SQL-сервера:



Теперь вводим код хандлера:



Не уверен, правда, что LINQ-способ сортировки быстрее, чем сортировка прямо внутри SQL - но пусть для этого примера будет так:


   1:  <%@ WebHandler Language="VB" Class="GetSiteTopic" %>
   2:   
   3:  Imports System
   4:  Imports System.Web
   5:   
   6:  Public Class GetSiteTopic : Implements IHttpHandler
   7:      
   8:      Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
   9:          Try
  10:              Dim DB1 As New TopicDataContext
  11:              Dim AllRecord As System.Linq.IQueryable(Of Entrance) = From X In DB1.Entrance Select X
  12:              Dim AllRecords = AllRecord.ToList
  13:              Dim RND As New System.Random(Now.Millisecond + Now.Second + Now.Minute + Now.Hour * Now.Day)
  14:              Dim RandomRecords = AllRecords.OrderBy(Function(r) RND.Next()).ToList
  15:              Dim Result As New StringBuilder("[")
  16:              For i As Integer = RandomRecords.Count - 1 To 0 Step -1
  17:                  Result.Append("{""")
  18:                  Result.Append("TYPE"":""")
  19:                  Result.Append(RandomRecords(i).Type)
  20:                  Result.Append(""",""")
  21:                  Result.Append("URL"":""")
  22:                  Result.Append(RandomRecords(i).URL)
  23:                  Result.Append(""",""")
  24:                  Result.Append("TXT"":""")
  25:                  Result.Append(RandomRecords(i).TXT.Replace("""", ""))
  26:                  Result.Append("""},")
  27:                  Result.AppendLine()
  28:              Next
  29:              Result.Remove(Result.Length - 3, 3)
  30:              Result.Append("]")
  31:              context.Response.ContentType = "application/json"
  32:              context.Response.Charset = "utf-8"
  33:              'context.Response.Write("[""a"",""b"",""c""]")
  34:              context.Response.Write(Result.ToString)
  35:          Catch ex As Exception
  36:              context.Response.ContentType = "text/plain"
  37:              context.Response.Write(ex.Message)
  38:          End Try
  39:   
  40:      End Sub
  41:   
  42:      Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
  43:          Get
  44:              Return False
  45:          End Get
  46:      End Property

Проверить корректность сформированного хандлером JSON можно на этом сайте http://www.jsonlint.com/, а увидеть JSON глазками можно в отладчике FireFox:



Но хотелось бы увидеть ответ хандлера непосредственно на HTML-страничке. Для этого сделаем небольшую тестовую страничку на JQUERY. Результат сформированный этой тестовой страничкой вы можете увидеть на http://pencil.vb-net.com/.


   1:  <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:  <html xmlns="http://www.w3.org/1999/xhtml">
   5:  <head runat="server">
   6:   
   7:      <script src="jquery-1.4.4.min.js" type="text/javascript" language="javascript"></script>
   8:      <script src="jquery.dataTables.min.js" type="text/javascript" language="javascript"></script>
   9:      <script type="text/javascript" language="javascript">
  10:   
  11:          $(document).ready(function () {
  12:              $.getJSON("GetSiteTopic.ashx", {}, ret1);
  13:   
  14:          });
  15:   
  16:          function ret1(json, success) {
  17:              $('#waiting').hide();
  18:   
  19:              $('#resultcontent').dataTable({
  20:                  "aLengthMenu": [[10, -1, ], [10, "All"]]
  21:              });
  22:   
  23:              for (var i = 0; i < json.length - 1; i++) {
  24:                  $('#resultcontent').dataTable().fnAddData
  25:                  ([
  26:                      json[i].TYPE,
  27:                      json[i].URL,
  28:                      json[i].TXT
  29:                      ]);
  30:              }
  31:          }
  32:      </script>
  33:  </head>
  34:  <body>
  35:      <img id="waiting" src="wait.gif" />
  36:      <table id="resultcontent" style="width: 100%">
  37:          <thead>
  38:              <tr>
  39:                  <th>
  40:                  </th>
  41:                  <th>
  42:                  </th>
  43:                  <th>
  44:                  </th>
  45:              </tr>
  46:          </thead>
  47:          <tbody>
  48:              <tr>
  49:                  <td>
  50:                  </td>
  51:                  <td>
  52:                  </td>
  53:                  <td>
  54:                  </td>
  55:              </tr>
  56:          </tbody>
  57:      </table>
  58:  </body>
  59:  </html>

Вы можете построить свой аналогичный хандлер по этому же принципу. Этот хандлер является серверной основой моего Simple Flash Banner Rotator with BLUR Effect . Так же как предыдущий хандлер был серверной основой моего OpenSource фото-слайдера на Flex.



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/WebHandler/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>