(MVC) MVC (2012 год)

Долгоиграющие странички с прогресс-баром.

На начало 2012-го года мой сайт flyseason.ru содержит уже 380 отдельных файлов с классами и 130 страничек (web-форм). В весь проект flyseason.ru состоит не только из front-end сайта, а из многих других отдельных проектов - веб-сервисы, толстый слой данный с сотнями SQL-процедур, отдельные NET-компоненты и библиотеки, отдельные приложение на AIR. Продолжая свою традицию - я опубликую некоторую небольшую часть лишь одной из 130 форм сайта - чтобы в интернете присутствовали живые фрагменты кода реальных проектов. Эта страничка может представлять некоторый интерес начинающим программистам - в ней они могут увидеть как строить долгоиграющие формы с прогрессбаром и формы с состоянием элементов управления (когда некоторые одни кнопки доступны в одном состоянии, а другие - в другом).

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

Поэтому, чтобы сформировать запросы для прогресс-бара - на форму нужно для начала положить таймер. В принципе, когда ASP.NET еще не существовало - я писал все эти таймеры сам, котодом JavaScript. Вот например фрагмент с таймером одного моего старинного приложения для РЖД - в данном случае это тест, в котором надо ответить за фиксованное время. Здесь прогресс выполнен в виде времени, оставшегося на ответ - по истечении времени кнопка ответа нажимается автоматически.


   1:  <script language="javascript" type="text/javascript">
   2:  function timer_process()
   3:  {
   4:      var timer = document.getElementById('Timer1');
   5:      if(timer!= null && timer.value>0)
   6:      {
   7:          var lbl_timer = document.getElementById('lbl_timer');
   8:          timer.value = timer.value-1;
   9:          var m = timer.value/60;
  10:          var s = timer.value%60;
  11:          lbl_timer.innerText = 'Для ответа осталось ' + Math.floor(m) + ' минут ' + s + ' секунд';
  12:          
  13:          window.setTimeout("timer_process()", 1000);
  14:      }
  15:      else if(timer!= null && timer.value<=0)
  16:      {
  17:          document.getElementById('Button1').click();
  18:      }
  19:  }
  20:  </script>

Но в этом коде нет периодической отправки данных на сервер, здесь просто вы видите голый принцип работы таймеров (setTimeout). После появления jQuery и ASP.NET AJAX самому ковыряться с язваскиптом уже нет ни малейшей необходимости. В ASP.NET достаточно перетянуть мышкой (drag-and-drop) с панели инструментов на форму ScriptManager (контейнер, содержащий скрипты MS AJAX) и элемент TIMER.

Асинхронно срабатывающее на сервере событие (без полного постбека, посылаемое ScriptManager'ом - обвязкой вокруг HttpXmlRequest) должно обновить все что нужно во всех UpdatePanel - прогрессбар, состояния различных кнопок (доступна/недоступна), какие-то сообщения, которые сгенерированы софтом во время долгоиграющих операций.

Это голый принцип работы этой формы - а ниже вы видите живую форму из реального проекта. На картинке вы видите, как таймер асинхронно дергает сервер. У меня тут несколько UpdatePanel'ей, чтобы таймер не постбечил страничку на сервер, достаточно его событие TICK указать в одной из панелей.



Небольшой значащий фрагмент кода странички выглядит вот так (ScriptManager лежит на MasterPage):


   1:  <%@ Page Title="" Language="VB" MasterPageFile="~/MA.master" AutoEventWireup="false"  CodeFile="AdminParsers.aspx.vb" Inherits="AdminParsers" %>
...
  56:              <asp:UpdatePanel ID="ButtonUpdatePanel" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
  57:                  <ContentTemplate>
  58:                      <table border="0" cellpadding="3" cellspacing="3">
  59:                          <tr>
  60:                              <td>
  61:                                  <b>Интурист:</b>
  62:                              </td>
  63:                              <td>
  64:                                  <asp:LinkButton ID="LoadXMLLinkButton1" runat="server">скачать XML</asp:LinkButton>
  65:                              </td>
  66:                              <td>
  67:                                  <asp:LinkButton ID="ParseXMLLinkButton1" runat="server">обработать XML</asp:LinkButton>
  68:                              </td>
  69:                              <td>
  70:                                  <asp:LinkButton ID="ClearCacheLinkButton1" runat="server">очистить кэш</asp:LinkButton>
  71:                              </td>
  72:                              <td colspan="2">
  73:                                  &nbsp;<asp:HyperLink ID="IntouristLink" runat="server" NavigateUrl="http://tourhot.ntk-intourist.ru/xml/MAIN_RESOURCES/flights_list.xml"
  74:                                      Target="_blank" ForeColor="#660066">http://tourhot.ntk-intourist.ru/xml/MAIN_RESOURCES/flights_list.xml</asp:HyperLink>
  75:                              </td>
  76:                          </tr>
  77:                          <tr>
  78:                              <td>
  79:                                  <b>Анекc:</b>
  80:                              </td>
  81:                              <td>
  82:                                  <asp:LinkButton ID="AnexLoadURLList" runat="server">скачать URL</asp:LinkButton>
  83:                              </td>
  84:                              <td>
  85:                                  <asp:LinkButton ID="AnexLoadXMLLinkButton1" runat="server">скачать XML </asp:LinkButton>
  86:                              </td>
  87:                              <td>
  88:                                  <asp:LinkButton ID="AnexParseXMLLinkButton1" runat="server">обработать XML</asp:LinkButton>
  89:                              </td>
  90:                              <td>
  91:                                  <asp:LinkButton ID="AnexClearCacheLinkButton1" runat="server">очистить кэш</asp:LinkButton>
  92:                              </td>
  93:                              <td>
  94:                                  <asp:HyperLink ID="AnexLink" runat="server" NavigateUrl="http://online3.anextour.ru/xml.php"
  95:                                      Target="_blank" ForeColor="#660066">http://online3.anextour.ru/xml.php</asp:HyperLink>
  96:                              </td>
  97:                          </tr>
  98:                      </table>
  99:                  </ContentTemplate>
 100:                  <Triggers>
 101:                       <asp:AsyncPostBackTrigger ControlID="AnexClearCacheLinkButton1" EventName="Click" />
 102:                       <asp:AsyncPostBackTrigger ControlID="AnexLoadURLList" EventName="Click" />
 103:                       <asp:AsyncPostBackTrigger ControlID="AnexLoadXMLLinkButton1" EventName="Click" />
 104:                       <asp:AsyncPostBackTrigger ControlID="AnexParseXMLLinkButton1" EventName="Click" />
 105:                       <asp:AsyncPostBackTrigger ControlID="ClearCacheLinkButton1" EventName="Click" />
 106:                       <asp:AsyncPostBackTrigger ControlID="LoadXMLLinkButton1" EventName="Click" />
 107:                       <asp:AsyncPostBackTrigger ControlID="ParseXMLLinkButton1" EventName="Click" />
 108:                  </Triggers>
 109:              </asp:UpdatePanel>
 110:          </div>
 111:          <div class="txt34">
 112:              <br />
 113:              <asp:UpdatePanel ID="CacheUpdatePanel" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
 114:                  <ContentTemplate>
 115:                      <asp:Label ID="CacheIntouristState" runat="server"></asp:Label><br />
 116:                      <asp:Label ID="CacheAnexState" runat="server"></asp:Label>
 117:                  </ContentTemplate>
 118:                <Triggers>
 119:                       <asp:AsyncPostBackTrigger ControlID="UpdateTimer" eventname="Tick" />
 120:                  </Triggers>
 121:              </asp:UpdatePanel>
 122:              <br />
 123:              <asp:UpdatePanel ID="WaitUpdatePanel" runat="server" UpdateMode="Conditional" 
 124:                  ChildrenAsTriggers="False">
 125:                  <ContentTemplate>
 126:                      <asp:Image ID="WaitImage1" runat="server" ImageUrl="~/Images/wait.gif" />
 127:                      <asp:LinkButton ID="StopLinkButton" runat="server">Stop</asp:LinkButton>
 128:                  </ContentTemplate>
 129:                  <Triggers>
 130:                       <asp:AsyncPostBackTrigger ControlID="StopLinkButton" eventname="Click" />
 131:                  </Triggers>
 132:              </asp:UpdatePanel>
 133:              <br />
 134:              <asp:UpdatePanel ID="ErrUpdatePanel" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
 135:                  <ContentTemplate>
 136:                      <asp:Label ID="lErr1" runat="server" ForeColor="Red"></asp:Label>
 137:                  </ContentTemplate>
 138:              </asp:UpdatePanel>
 139:   
 140:              <asp:Timer ID="UpdateTimer" runat="server" interval="1000" 
 141:                  ontick="UpdateTimer_Tick" Enabled="True" />
 142:          </div>
 143:          <br />
 144:          <br />
 145:          <br />
 146:          <br />
 147:      </div>
 148:      <asp:SqlDataSource ID="Truncate" runat="server" ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>"
 149:          SelectCommand="truncate table TestIntourist" SelectCommandType="Text"></asp:SqlDataSource>
 150:      <asp:SqlDataSource ID="AnexTruncate" runat="server" ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>"
 151:          SelectCommand="truncate table AnexList" SelectCommandType="Text"></asp:SqlDataSource>
 152:      <asp:SqlDataSource ID="GetCount" runat="server" ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>"
 153:          SelectCommand="select count(*) as count from TestIntourist with (nolock)" SelectCommandType="Text">
 154:      </asp:SqlDataSource>
 155:      <asp:SqlDataSource ID="GetAnexCount" runat="server" ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>"
 156:          SelectCommand="select count(*) as count from AnexList with (nolock)" SelectCommandType="Text">
 157:      </asp:SqlDataSource>
 158:  </asp:Content>

Фишка конкретно этой моей странички заключается в том, что здесь ПрогрессБар открывается ИНОГДА - в некоторых состояниях странички (при некоторых долгоиграющих операциях). При краткосрочных операциях его просто нет. Ниже вы можете видеть как я тыкнул СТОП в момент скачки 668 файла и процесс скачки и парсинга (занимающий четыре часа) завершился и прогресс-бар исчез со старнички (и стали доступными другие кнопки) странички.



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


   1:  Partial Class AdminParsers
   2:      Inherits System.Web.UI.Page
   3:   
   4:      Dim AnexTempPath As String = System.Configuration.ConfigurationManager.AppSettings("TEMP")
   5:   
   6:      'состояние кнопочной панели
   7:      Public ButtonUpdatePanelState As ButtonState
   8:      Public Enum ButtonState
   9:          Ready = 1               'начальное - доступны только LoadXMLLinkButton1 для интуриста и AnexLoadURLList для анекса
  10:          ReadyInt = 2            'доступны LoadXMLLinkButton1 и AnexParseXMLLinkButton1, AnexClearCacheLinkButton1
  11:          ReadyAnex = 3           'достпуны AnexLoadURLList и ParseXMLLinkButton1, ClearCacheLinkButton1
  12:          StartIntLoadXML = 4     'начата скачка XML с интуриста - все недоступно
  13:          ErrtIntLoadXML = 5      'не закончилась скачка - доступна только чистка ClearCacheLinkButton1
  14:          EndtIntLoadXML = 6      'закончена скачка XML с интуриста - доступны ClearCacheLinkButton1 и ParseXMLLinkButton1
  15:          StartIntParseXML = 7    'начат парсинг - все недоступно
  16:          ErrIntParseXML = 8      'не закончился парсинг - доступна только чистка ClearCacheLinkButton1
  17:          StartIntClearCache = 9  'все недоступно
  18:          StartAnexLoadURL = 10   'началась скачка URL c анекса - все недоступно
  19:          ErrAnexLoadURL = 11     'доступно только AnexClearCacheLinkButton1
  20:          EndAnexLoadURL = 12     'завершилась скачка списка URL - доступны AnexLoadXMLLinkButton1 и AnexClearCacheLinkButton1
  21:          StartAnexClearCache = 13 'начало чистки кеша - все недоступно
  22:          StartAnexLoadXML = 14   'начало загрузок XML - все недоступно
  23:          ErrAnexLoadXML = 15     'ошибка загрузки всех XML- доступны - AnexClearCacheLinkButton1 и AnexParseXMLLinkButton1
  24:          StartAnexParseXML = 16  'начало парсинга - все недоступно
  25:          ErrAnexParseXML = 17    'ошибка парсинга - доступна чистка кеша AnexClearCacheLinkButton1
  26:          ErrorState = 18         'непонятное состояние
  27:      End Enum
  28:   
  29:      'асинхронные запросы по таймеру
  30:      Protected Sub UpdateTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles UpdateTimer.Tick
  31:          If Session("ButtonUpdatePanelState") Is Nothing Then
  32:              Session("ButtonUpdatePanelState") = ButtonState.Ready
  33:          End If
  34:          If Session("lErr1") Is Nothing Then
  35:              Session("lErr1") = ""
  36:          End If
  37:          lErr1.Text = Session("lErr1")
  38:          'Установка состояний кнопок
  39:          Select Case DirectCast(Session("ButtonUpdatePanelState"), ButtonState)
  40:              Case ButtonState.Ready                'начальное - доступны только LoadXMLLinkButton1 для интуриста и AnexLoadURLList для анекса
  41:                  Session("StopLinkButtonPress") = False
  42:                  LoadXMLLinkButton1.Enabled = True
  43:                  ParseXMLLinkButton1.Enabled = False
  44:                  ClearCacheLinkButton1.Enabled = False
  45:                  AnexLoadURLList.Enabled = True
  46:                  AnexLoadXMLLinkButton1.Enabled = False
  47:                  AnexParseXMLLinkButton1.Enabled = False
  48:                  AnexClearCacheLinkButton1.Enabled = False
  49:                  ShowIntourState()
  50:                  ShowAnexState()
  51:                  StopLinkButton.Visible = False
  52:                  WaitImage1.Visible = False
...
 147:              Case ButtonState.StartIntParseXML     'начат парсинг - все недоступно
 148:                  LoadXMLLinkButton1.Enabled = False
 149:                  ParseXMLLinkButton1.Enabled = False
 150:                  ClearCacheLinkButton1.Enabled = False
 151:                  AnexLoadURLList.Enabled = False
 152:                  AnexLoadXMLLinkButton1.Enabled = False
 153:                  AnexParseXMLLinkButton1.Enabled = False
 154:                  AnexClearCacheLinkButton1.Enabled = False
 155:                  ShowIntourState()
 156:                  ShowAnexState()
 157:                  StopLinkButton.Visible = True
 158:                  WaitImage1.Visible = True
 159:                  'четыре апдейт-панели формы, состояние которых обновляется
 160:                  WaitUpdatePanel.Update()
 161:                  CacheUpdatePanel.Update()
 162:                  ButtonUpdatePanel.Update()
 163:                  ErrUpdatePanel.Update()
...
 357:          End Select
 358:      End Sub
 359:   
 360:      Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 361:          If Not IsPostBack Then
 362:              If Session("ButtonUpdatePanelState") Is Nothing Then
 363:                  Dim AnexCount As Integer = ShowAnexState()
 364:                  Dim IntourCount As Integer = ShowIntourState()
 365:                  If AnexCount = 0 And IntourCount = 0 Then
 366:                      Session("ButtonUpdatePanelState") = ButtonState.Ready
 367:                  ElseIf AnexCount > 0 And IntourCount = 0 Then
 368:                      Session("ButtonUpdatePanelState") = ButtonState.ReadyInt
 369:                  ElseIf AnexCount = 0 And IntourCount > 0 Then
 370:                      Session("ButtonUpdatePanelState") = ButtonState.ReadyAnex
 371:                  End If
 372:              End If
 373:   
 374:              If Session("lErr1") Is Nothing Then Session("lErr1") = ""
 375:              UpdateTimer_Tick(Nothing, Nothing)
 376:          End If
 377:      End Sub
 378:   
 379:  #Region "Интурист"
 380:   
 381:      Dim WithEvents Intour As Intourist
 382:      'событие парсера - отпарсены сто строчек
 383:      Protected Sub Intour_Pass(ByVal RowNumber As Integer) Handles Intour.Pass
 384:          Session("lErr1") &= "Process " & RowNumber & " row" & "<br>"
 385:          UpdateTimer_Tick(Nothing, Nothing)
 386:      End Sub
 387:   
 388:      'асинхронный вызов очистки интуриста
 389:      Protected Sub ClearCacheLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ClearCacheLinkButton1.Click
 390:          Session("ButtonUpdatePanelState") = ButtonState.StartIntClearCache
 391:          UpdateTimer_Tick(Nothing, Nothing)
 392:          '
 393:          Try
 394:              Truncate.Select(New DataSourceSelectArguments)
 395:              '
 396:              CacheIntouristState.Text = "Кэш Интуриста пустой."
 397:              '
 398:              Session("lErr1") = "Cache clear" & "<br>"
 399:              '
 400:              Session("ButtonUpdatePanelState") = ButtonState.Ready
 401:              UpdateTimer_Tick(Nothing, Nothing)
 402:              UpdateTimer.Enabled = False
 403:   
 404:          Catch ex As Exception
 405:              Session("lErr1") &= ex.Message & "<br>"
 406:              '
 407:              Session("ButtonUpdatePanelState") = ButtonState.ErrorState
 408:              UpdateTimer_Tick(Nothing, Nothing)
 409:              UpdateTimer.Enabled = False
 410:          End Try
 411:   
 412:      End Sub
 413:   
 414:      'асинхронный вызов загрузки XML с интуриста
 415:      Protected Sub LoadXMLLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles LoadXMLLinkButton1.Click
 416:          UpdateTimer.Enabled = True 'запуск таймера
 417:          Session("ButtonUpdatePanelState") = ButtonState.StartIntLoadXML
 418:          UpdateTimer_Tick(Nothing, Nothing)
 419:          '
 420:          Dim RowNumber As Integer
 421:          Try
 422:              'сначала чистим таблу с кешем
 423:              Truncate.Select(New DataSourceSelectArguments)
 424:   
 425:              'теперь скачиваем в кеш все что отдает интурист
 426:              Dim XML As New System.Xml.XmlDocument
 427:              Dim XMLstring As String = CommonParser.GetXMLContents(IntouristLink.Text)
 428:              '
 429:              Session("lErr1") &= "Downloading " & XMLstring.Length & " chars" & "<br>"
 430:              '
 431:              XML.LoadXml(XMLstring)
 432:              Dim XMLitems = XML.SelectNodes("/FLIGHTS_LIST/ITEM_FLIGHT")
 433:              '
 434:              Session("lErr1") &= "Parse " & XMLitems.Count & " XMLnodes" & "<br>"
 435:              '
 436:              Intour = New Intourist
 437:              Intour.LoadXML(RowNumber, XMLitems)
 438:              '
 439:              Session("lErr1") &= "Save to cache" & "<br>"
 440:              '
 441:              Session("ButtonUpdatePanelState") = ButtonState.EndtIntLoadXML
 442:              UpdateTimer_Tick(Nothing, Nothing)
 443:   
 444:          Catch ex As Exception
 445:              Session("lErr1") &= "RowNumber=" & RowNumber & ". " & ex.Message & "<br>"
 446:              '
 447:              Session("ButtonUpdatePanelState") = ButtonState.ErrtIntLoadXML
 448:              UpdateTimer_Tick(Nothing, Nothing)
 449:          End Try
 450:   
 451:      End Sub
 452:   
 453:      'асинхронный вызов парсинга XML интуриста
 454:      Protected Sub ParseXMLLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ParseXMLLinkButton1.Click
 455:          Session("ButtonUpdatePanelState") = ButtonState.StartIntParseXML
 456:          UpdateTimer_Tick(Nothing, Nothing)
 457:          Dim RowNumber As Integer
 458:          Try
 459:              Intour = New Intourist
 460:              Intour.ParseXML(RowNumber)
 461:              '
 462:              Session("lErr1") &= "Process " & RowNumber & " row" & "<br>"
 463:              '
 464:              'после нормального завершения чистим кеш
 465:              Truncate.Select(New DataSourceSelectArguments)
 466:              '
 467:              Session("lErr1") &= "Cache clear" & "<br>"
 468:              '
 469:              Session("ButtonUpdatePanelState") = ButtonState.Ready
 470:              UpdateTimer_Tick(Nothing, Nothing)
 471:              UpdateTimer.Enabled = False 'цикл работы закончен - останавливаем таймер
 472:   
 473:          Catch ex As Exception
 474:              Session("lErr1") &= "RowNumber=" & RowNumber & ". " & ex.Message & "<br>"
 475:              '
 476:              Session("ButtonUpdatePanelState") = ButtonState.ErrIntParseXML
 477:              UpdateTimer_Tick(Nothing, Nothing)
 478:          End Try
 479:      End Sub
 480:   
 481:   
 482:  #End Region
 483:   
 484:  #Region "Anex"
 485:   
 486:   
 487:      Dim WithEvents AnexParser As Anex
 488:      Protected Sub Anex_Pass(ByVal RowNumber As Integer) Handles AnexParser.Pass
 489:          Session("lErr1") &= "Process " & RowNumber & " row" & "<br>"
 490:          UpdateTimer_Tick(Nothing, Nothing)
 491:      End Sub
 492:   
 493:      'асинхронный вызов загрузки списка URL с анекса
 494:      Protected Sub AnexLoadURLList_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AnexLoadURLList.Click
 495:          UpdateTimer.Enabled = True 'запуск таймера
 496:          Session("ButtonUpdatePanelState") = ButtonState.StartAnexLoadURL
 497:          UpdateTimer_Tick(Nothing, Nothing)
 498:          '
 499:          Dim URLCount As Integer
 500:          Try
 501:              Session("lErr1") &= "Start downloading... " & "<br>"
 502:   
 503:              'сначала чистим таблу с кешем
 504:              AnexTruncate.Select(New DataSourceSelectArguments)
 505:   
 506:              'теперь скачиваем и сохраняем в базу список файлов
 507:              AnexParser = New Anex
 508:              URLCount = AnexParser.LoadURL(AnexLink.Text)
 509:              '
 510:              Session("lErr1") &= "Download " & UrlCount.ToString & " URL" & "<br>"
 511:              '
 512:              Session("ButtonUpdatePanelState") = ButtonState.EndAnexLoadURL
 513:              UpdateTimer_Tick(Nothing, Nothing)
 514:              '
 515:          Catch ex As Exception
 516:              Session("lErr1") &= "URLCount=" & URLCount & ". " & ex.Message & "<br>"
 517:              '
 518:              Session("ButtonUpdatePanelState") = ButtonState.ErrAnexLoadURL
 519:              UpdateTimer_Tick(Nothing, Nothing)
 520:          End Try
 521:      End Sub
 522:   
 523:   
 524:      'асинхронный вызов загрузки множества XML с анекса
 525:      Protected Sub AnexLoadXMLLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AnexLoadXMLLinkButton1.Click
 526:          Session("ButtonUpdatePanelState") = ButtonState.StartAnexLoadXML
 527:          UpdateTimer_Tick(Nothing, Nothing)
 528:          '
 529:          Dim UrlCount As Integer = 0
 530:          Try
 531:              Session("lErr1") &= "Start downloading... " & "<br>"
 532:              '
 533:              AnexParser = New Anex
 534:              AnexParser.LoadXML(UrlCount)
 535:              '
 536:              Session("lErr1") &= "Download " & UrlCount.ToString & " XML files" & "<br>"
 537:              '
 538:              Session("ButtonUpdatePanelState") = ButtonState.EndtIntLoadXML
 539:              UpdateTimer_Tick(Nothing, Nothing)
 540:   
 541:          Catch ex As Exception
 542:              Session("lErr1") &= "UrlCount=" & UrlCount & ". " & ex.Message & "<br>"
 543:              '
 544:              Session("ButtonUpdatePanelState") = ButtonState.ErrAnexLoadXML
 545:              UpdateTimer_Tick(Nothing, Nothing)
 546:          End Try
 547:      End Sub
 548:   
 549:   
 550:      'асинхронный вызов парсинга XML анекса
 551:      Protected Sub AnexParseXMLLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AnexParseXMLLinkButton1.Click
 552:          Session("ButtonUpdatePanelState") = ButtonState.StartAnexParseXML
 553:          UpdateTimer_Tick(Nothing, Nothing)
 554:          Dim RowNumber As Integer
 555:          Try
 556:              '
 557:              AnexParser = New Anex
 558:              AnexParser.ParseXML(RowNumber)
 559:              '
 560:              'после нормального завершения чистим кеш
 561:              AnexTruncate.Select(New DataSourceSelectArguments)
 562:              Session("lErr1") &= "Cache clear" & "<br>"
 563:              '
 564:              Dim DirInfo As IO.DirectoryInfo = My.Computer.FileSystem.GetDirectoryInfo(AnexTempPath)
 565:              For Each OneFile As IO.FileInfo In DirInfo.GetFiles
 566:                  OneFile.Delete()
 567:                  RowNumber += 1
 568:                  If (RowNumber Mod 100) = 0 Then
 569:                      Session("lErr1") &= "Process " & RowNumber & " files" & "<br>"
 570:                  End If
 571:              Next
 572:              Session("lErr1") &= "FileSystem clear" & "<br>"
 573:              '
 574:              Session("ButtonUpdatePanelState") = ButtonState.Ready
 575:              UpdateTimer_Tick(Nothing, Nothing)
 576:              UpdateTimer.Enabled = False 'цикл работы закончен - останавливаем таймер
 577:          Catch ex As Exception
 578:              Session("lErr1") &= "UrlCount=" & RowNumber & ". " & ex.Message & "<br>"
 579:              Session("ButtonUpdatePanelState") = ButtonState.ErrAnexParseXML
 580:              UpdateTimer_Tick(Nothing, Nothing)
 581:          End Try
 582:   
 583:      End Sub
 584:   
 585:      'асинхронный вызов очистки анекса
 586:      Protected Sub AnexClearCacheLinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AnexClearCacheLinkButton1.Click
 587:          Dim RowNumber As Integer
 588:          Session("ButtonUpdatePanelState") = ButtonState.StartAnexClearCache
 589:          UpdateTimer_Tick(Nothing, Nothing)
 590:          Try
 591:              '
 592:              AnexTruncate.Select(New DataSourceSelectArguments)
 593:              Session("lErr1") = "Cache clear" & "<br>"
 594:              '
 595:              Dim DirInfo As IO.DirectoryInfo = My.Computer.FileSystem.GetDirectoryInfo(AnexTempPath)
 596:              For Each OneFile As IO.FileInfo In DirInfo.GetFiles
 597:                  OneFile.Delete()
 598:                  RowNumber += 1
 599:                  If (RowNumber Mod 100) = 0 Then
 600:                      Session("lErr1") &= "Process " & RowNumber & " files" & "<br>"
 601:                  End If
 602:              Next
 603:              Session("lErr1") &= "FileSystem clear" & "<br>"
 604:              '
 605:              Session("ButtonUpdatePanelState") = ButtonState.Ready
 606:              UpdateTimer.Enabled = False 'цикл работы закончен - останавливаем таймер
 607:              UpdateTimer_Tick(Nothing, Nothing)
 608:          Catch ex As Exception
 609:              Session("lErr1") &= ex.Message & "<br>"
 610:              Session("ButtonUpdatePanelState") = ButtonState.ErrorState
 611:              UpdateTimer_Tick(Nothing, Nothing)
 612:          End Try
 613:   
 614:      End Sub
 615:   
 616:   
 617:  #End Region
 618:   
 619:   
 620:      Function ShowAnexState() As Integer
 621:          Dim DirInfo As IO.DirectoryInfo = My.Computer.FileSystem.GetDirectoryInfo(AnexTempPath)
 622:          Dim DV2 As Data.DataView = GetAnexCount.Select(New DataSourceSelectArguments)
 623:          If DV2 IsNot Nothing Then
 624:              If DV2.Count > 0 Then
 625:                  CacheAnexState.Text = "Кэш Анекса содержит " & DV2(0)("count") & " записей (" & DirInfo.GetFiles.Length & ") файлов."
 626:                  CacheUpdatePanel.Update()
 627:                  Return DV2(0)("count")
 628:              Else
 629:                  CacheAnexState.Text = "Кэш Анекса пустой. (" & DirInfo.GetFiles.Length & ") файлов."
 630:                  CacheUpdatePanel.Update()
 631:                  Return 0
 632:              End If
 633:          Else
 634:              CacheAnexState.Text = "Кэш Анекса пустой. (" & DirInfo.GetFiles.Length & ") файлов."
 635:              CacheUpdatePanel.Update()
 636:              Return 0
 637:          End If
 638:   
 639:      End Function
 640:   
 641:      Function ShowIntourState() As Integer
 642:          Dim DV1 As Data.DataView = GetCount.Select(New DataSourceSelectArguments)
 643:          If DV1 IsNot Nothing Then
 644:              If DV1(0)("count") > 0 Then
 645:                  CacheIntouristState.Text = "Кэш Интуриста содержит " & DV1(0)("count") & " записей."
 646:                  CacheUpdatePanel.Update()
 647:                  Return DV1(0)("count")
 648:              Else
 649:                  CacheIntouristState.Text = "Кэш Интуриста пустой."
 650:                  CacheUpdatePanel.Update()
 651:                  Return 0
 652:              End If
 653:          Else
 654:              CacheIntouristState.Text = "Кэш Интуриста пустой."
 655:              CacheUpdatePanel.Update()
 656:              Return 0
 657:          End If
 658:      End Function
 659:   
 660:   
 661:      Protected Sub StopLinkButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles StopLinkButton.Click
 662:          Session("StopLinkButtonPress") = True
 663:      End Sub
 664:  End Class

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


   1:  Imports Microsoft.VisualBasic
   2:   
   3:   
   4:  'формат одной строки данных интуриста
   5:  '<?xml version="1.0" encoding="windows-1251" standalone="yes"?>
   6:  '<FLIGHTS_LIST SCAN_DATETIME="01.10.2011 11:43:42" FROM_DATE="01.10.2011" TILL_DATE="31.03.2012">
   7:  '<ITEM_FLIGHT 
   8:  'ASH_CODE="73505" 
   9:  'FLIGHT_CODE="3067" 
  10:  'DATE_OF="01.10.2011" 
  11:  'DIR_STATUS="-1" 
  12:  'BACK_STATUS="-1" 
  13:  'DIR_DESC="НЕТ РЕЙСА" 
  14:  'BACK_DESC="НЕТ РЕЙСА" 
  15:  'AIR_COMP_CODE="79" 
  16:  'AIR_COMP="TULPAR" 
  17:  'FL_N_DIR="TUL 2241" 
  18:  'FL_N_BACK="TUL 2242" 
  19:  'AIR_BUS_CODE="16" 
  20:  'AIR_BUS="YAK-42" 
  21:  'CLASS_SYMB="" 
  22:  'COUNTRYFR_C="139" 
  23:  'COUNTRYFR="BELGOROD" 
  24:  'CITYFR_C="1040" 
  25:  'CITYFR="Belgorod" 
  26:  'COUNTRYTO_C="1" 
  27:  'COUNTRYTO="ANTALYA" 
  28:  'CITYTO_C="2" 
  29:  'CITYTO="Antalya City" 
  30:  'TIME_DEP="15:00:00" 
  31:  'TIME_ARR="16:30:00" 
  32:  'DA="0" 
  33:  'B_TIME_DEP="7:30:00" 
  34:  'B_TIME_ARR="11:00:00" 
  35:  'STATEFR_ID="3" 
  36:  'STATEFR="RUSSIA " 
  37:  'STATETO_ID="6" 
  38:  'STATETO="TURKEY"/>
  39:   
  40:   
  41:  Public Class Intourist
  42:   
  43:      Public Event Pass(ByVal RowNumber As Integer)
  44:   
...
  92:      Public Sub ParseXML(ByRef RowNumber As Integer)
  93:          Dim db1 As New FlySeasonDataContext
  94:          Dim db2 As New FlySeasonDataContext
  95:          Dim Cache = (From X In db1.TestIntourist Select X).ToList
  96:   
  97:          '
  98:          For Each One In Cache
  99:              RowNumber += 1
 100:              '
 101:              Dim FromCountry As String = "Россия"
 102:              Dim FromCity As String = "Москва"
 103:              '
 104:              For i As Integer = 0 To (FromAirportList.Length / 2) - 1
 105:                  If One.COUNTRYFR.ToString.Trim = "MOSCOW" Then
 106:                      GoTo OkFrom
 107:                  End If
 108:              Next
 109:              Continue For 'этот город вылета мы не обрабатываем
 110:              '
 111:  OkFrom:     Dim FromAirport As String
 112:              For i As Integer = 0 To (FromAirportList.Length / 2) - 1
 113:                  If FromAirportList(i, 0).ToString.Trim.ToLower = One.CITYFR.ToString.Trim.ToLower Then
 114:                      FromAirport = FromAirportList(i, 1)
 115:                      Exit For
 116:                  End If
 117:              Next
 118:              '
 119:              Dim ToCountry As String
 120:              For i As Integer = 0 To (ToCountryList.Length / 2) - 1
 121:                  If ToCountryList(i, 0).ToString.Trim.ToLower = One.STATETO.ToString.Trim.ToLower Then
 122:                      ToCountry = ToCountryList(i, 1)
 123:                      GoTo OkCountry
 124:                  End If
 125:              Next
 126:              Continue For 'эту страну мы не обрабатываем
 127:              '
 128:  OkCountry:
 129:              Dim ToCity As String
 130:              Dim AirPortIndex As Integer
 131:              For i As Integer = 0 To (ToCityList.Length / 2) - 1
 132:                  If ToCityList(i, 0).ToString.Trim.ToLower = One.CITYTO.ToString.Trim.ToLower Then
 133:                      ToCity = ToCityList(i, 1)
 134:                      AirPortIndex = i
 135:                      GoTo OkCity
 136:                  End If
 137:              Next
 138:              Continue For 'этот город мы не обрабатываем
 139:  OkCity:
 140:              Dim ToAirport As String = ToAirPortList(AirPortIndex)
 141:              '
 142:              Dim FromDateArr As String() = One.DATE_OF.Split(".")
 143:              Dim FromDate As New DateTime(CInt(FromDateArr(2)), CInt(FromDateArr(1)), CInt(FromDateArr(0)))
 144:              '
 145:              Dim FromTime As TimeSpan
 146:              Try
 147:                  FromTime = TimeSpan.Parse(One.TIME_DEP)
 148:              Catch ex As Exception
 149:                  FromTime = TimeSpan.Parse("00:00")
 150:              End Try
 151:   
 152:              Dim Flyime As TimeSpan
 153:              Try
 154:                  Flyime = TimeSpan.Parse(One.TIME_ARR)
 155:              Catch ex As Exception
 156:                  Flyime = TimeSpan.Parse("00:00")
 157:              End Try
 158:   
 159:              Dim RetFromTime As TimeSpan
 160:              Try
 161:                  RetFromTime = TimeSpan.Parse(One.B_TIME_DEP)
 162:              Catch ex As Exception
 163:                  RetFromTime = TimeSpan.Parse("00:00")
 164:              End Try
 165:   
 166:              Dim RetFlyime As TimeSpan
 167:              Try
 168:                  RetFlyime = TimeSpan.Parse(One.B_TIME_ARR)
 169:              Catch ex As Exception
 170:                  RetFlyime = TimeSpan.Parse("00:00")
 171:              End Try
 172:              '
 173:              Dim AviaCompanyCode As String
 174:              If One.FL_N_DIR.Length > 2 Then
 175:                  Dim Arr1 As String() = One.FL_N_DIR.ToString.Split(" ")
 176:                  If Arr1.Length > 1 Then
 177:                      'пробел есть
 178:                      AviaCompanyCode = Arr1(0)
 179:                  End If
 180:              Else
 181:                  AviaCompanyCode = "NO"
 182:              End If
 183:              '
 184:              Dim RetAviaCompanyCode As String
 185:              If One.FL_N_BACK.Length > 2 Then
 186:                  Dim Arr2 As String() = One.FL_N_BACK.ToString.Split(" ")
 187:                  If Arr2.Length > 1 Then
 188:                      'пробел есть
 189:                      RetAviaCompanyCode = Arr2(0)
 190:                  End If
 191:              Else
 192:                  RetAviaCompanyCode = "NO"
 193:              End If
 194:              '
 195:              Dim FlyNumberPos1 As Integer = 0
 196:              FlyNumberPos1 = One.FL_N_DIR.IndexOf(" ")
 197:              Dim FlyNumber As String
 198:              If FlyNumberPos1 > 0 Then
 199:                  FlyNumber = One.FL_N_DIR.Substring(FlyNumberPos1, One.FL_N_DIR.Length - FlyNumberPos1).Trim()
 200:              Else
 201:                  FlyNumber = One.FL_N_DIR
 202:              End If
 203:              '
 204:              Dim RetFlyNumberPos1 As Integer = 0
 205:              RetFlyNumberPos1 = One.FL_N_DIR.IndexOf(" ")
 206:              Dim RetFlyNumber As String
 207:              If RetFlyNumberPos1 > 0 Then
 208:                  RetFlyNumber = One.FL_N_BACK.Substring(RetFlyNumberPos1, One.FL_N_BACK.Length - RetFlyNumberPos1).Trim
 209:              Else
 210:                  RetFlyNumber = One.FL_N_BACK
 211:              End If
 212:   
 213:              '
 214:              Dim FlyClass As String = "Economy"
 215:              Dim Supplier As String = "INTOURIST XML"
 216:              If One.FL_N_DIR.Contains("(бизнес)") Then
 217:                  FlyClass = "Business"
 218:                  Supplier = "INTOURIST XML Business"
 219:              End If
 220:   
 221:              Dim RetFlyClass As String = "Economy"
 222:              Dim RetSupplier As String = "INTOURIST XML"
 223:              If One.FL_N_BACK.Contains("(бизнес)") Then
 224:                  RetFlyClass = "Business"
 225:                  RetSupplier = "INTOURIST XML Business"
 226:              End If
 227:   
 228:              '
 229:              Dim HowMany As String
 230:              Dim RetHowMany As String
 231:              '
 232:              Dim AirTransfer As String
 233:              Dim RetAirTransfer As String
 234:              '
 235:              Dim AirTransferComment As String = "-"
 236:              Dim RetAirTransferComment As String = "-"
 237:              '
 238:              'прямой билет
 239:              '
 240:              Select Case CInt(One.DIR_STATUS)
 241:                  Case -1  'НЕТ РЕЙСА
 242:                      GoTo NoWriteDir
 243:                  Case 0   'НЕТ МЕСТ
 244:                      HowMany = "Нет"
 245:                      AirTransfer = "Нет"
 246:                  Case 1   'МАЛО МЕСТ
 247:                      HowMany = "Есть"
 248:                      AirTransfer = "Есть"
 249:                      AirTransferComment = (One.DIR_DESC)
 250:                  Case 2   'ЕСТЬ МЕСТА
 251:                      HowMany = "Есть"
 252:                      AirTransfer = "Нет"
 253:                  Case 3   'МАЛО МЕСТ (X)
 254:                      HowMany = "Есть"
 255:                      AirTransfer = "Есть"
 256:                      AirTransferComment = (One.DIR_DESC)
 257:              End Select
 258:              Dim RetCode1 = db2.LoadOneTicket(Nothing, FromCountry, FromCity, FromAirport, ToCountry, ToCity, ToAirport, FromDate, FromTime, Flyime, AviaCompanyCode, FlyNumber, FlyClass, 0, HowMany, AirTransfer, AirTransferComment, Supplier)
 259:  NoWriteDir:
 260:              '
 261:              'обратный билет
 262:              '
 263:              Select Case CInt(One.BACK_STATUS)
 264:                  Case -1  'НЕТ РЕЙСА
 265:                      GoTo NoWriteBack
 266:                  Case 0   'НЕТ МЕСТ
 267:                      RetHowMany = "Нет"
 268:                      RetAirTransfer = "Нет"
 269:                  Case 1   'МАЛО МЕСТ
 270:                      RetHowMany = "Есть"
 271:                      RetAirTransfer = "Есть"
 272:                      RetAirTransferComment = One.BACK_DESC
 273:                  Case 2   'ЕСТЬ МЕСТА
 274:                      RetHowMany = "Есть"
 275:                      RetAirTransfer = "Нет"
 276:                  Case 3   'МАЛО МЕСТ (X)
 277:                      RetHowMany = "Есть"
 278:                      RetAirTransfer = "Есть"
 279:                      RetAirTransferComment = One.BACK_DESC
 280:              End Select
 281:              Dim RetCode2 = db2.LoadOneTicket(Nothing, ToCountry, ToCity, ToAirport, FromCountry, FromCity, FromAirport, FromDate, RetFromTime, RetFlyime, RetAviaCompanyCode, RetFlyNumber, RetFlyClass, 0, RetHowMany, RetAirTransfer, RetAirTransferComment, RetSupplier)
 282:  NoWriteBack:
 283:              '
 284:              If CBool(HttpContext.Current.Session("StopLinkButtonPress")) Then
 285:                  Exit Sub
 286:              End If
 287:              If (RowNumber Mod 100) = 0 Then
 288:                  RaiseEvent Pass(RowNumber)
 289:              End If
 290:          Next
 291:   
 292:      End Sub
 293:   
 294:   
 295:      'массивы фильтрации из XML полезной информации
 296:      Dim FromAirportList As Array = {{"DME", "DME"}, _
 297:                                  {"Sheremetevo-2", "SVO-2"}, _
 298:                                  {"Sheremetevo-2", "SVO-2"}, _
 299:                                  {"Sheremetevo-1", "SVO-1"}, _
 300:                                  {"Sheremetevo-C", "SVO-C"}, _
 301:                                  {"Sheremetevo-D", "SVO-D"}, _
 302:                                  {"Sheremetevo-F", "SVO-F"}, _
 303:                                  {"Vnukovo", "VKO"}}
 304:      Dim ToCountryList As Array = {{"BULGARIA", "Болгария"}, _
 305:                                  {"TURKEY", "Турция"}, _
 306:                                  {"ITALY", "Италия"}, _
 307:                                  {"THAILAND", "Таиланд"}, _
 308:                                  {"INDIA", "Индия"}, _
 309:                                  {"UAE", "ОАЭ"}, _
 310:                                  {"EGYPT", "Египет"}}
 311:      Dim ToCityList As Array = {{"Varna", "Варна"}, _
 312:                                  {"Burgac", "Бургас"}, _
 313:                                  {"Hurghada", "Хургада"}, _
 314:                                  {"Sharm-El-Sheikh", "Шарм Эль Шейх"}, _
 315:                                  {"Goa", "Гоа"}, _
 316:                                  {"Dabolim", "Гоа"}, _
 317:                                  {"Rimini", "Римини"}, _
 318:                                  {"Bangkok", "Бангкок"}, _
 319:                                  {"Phuket", "Пхукет"}, _
 320:                                  {"Antalya City", "Анталия"}, _
 321:                                  {"Istanbul", "Стамбул"}, _
 322:                                  {"Bodrum", "Бодрум"}, _
 323:                                  {"Izmir", "Измир"}, _
 324:                                  {"Dubai", "Дубай"}, _
 325:                                  {"Dalaman", "Даламан"}}
 326:      Dim ToAirPortList As Array = {"VAR", _
 327:                                  "BOJ", _
 328:                                  "HRG", _
 329:                                  "SSH", _
 330:                                  "DBL", _
 331:                                  "DBL", _
 332:                                  "RMN", _
 333:                                  "BKK", _
 334:                                  "PHT", _
 335:                                  "AYT", _
 336:                                  "IST", _
 337:                                  "BJV", _
 338:                                  "ADB", _
 339:                                  "DXB", _
 340:                                  "DLM"}
...

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


В заключение я хотел бы заметить, что я тут применил лишь одну из многих-многих технологий MS AJAX. Альтернативная (и самая старая) технология ASP.NET AJAX заключается в изготовлении web-сервисов, интергрированных непосредственно на страничку - когда класс ASP.NET странички помечается атрибутом ScriptService, а асинхронный метод класса (работающий как web-служба ASMX) помечается атрибутом WebMethod - вот так. Также в ASP.NET 4.0 есть и множество альтернативных более современных методов. Можно также сделать полноценный WCF-сервис (например так - Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX. Данные, создаваемые на сервере - хоть интергрированными на страничку методами, хоть выделенными ASMX сервисами, хоть WCF сервисами - можно принять на страничке множеством способов (Заполнение связанных списков на MS AJAX и jQuery) - хоть просто JavaScript-ом, хоть микрософтовским ScriptManager с декларативным синтаксисом описаний ссылок на сервисы сервера, хоть с помощью jQuery (см например мою страничку - AJAX подсказка/автозаполнение на jQuery). А можно вообще просто сформировать простой XML или JSON безо всяких web-сервисов (например так Как сделать простейший Web-handler - формирующий XML или JSON) и точно так же принять его хоть MS AJAX (ScriptManager'ом), хоть ЯзваСкриптом, хоть jQuery. Сравнения формирования на сервере данных простыми XML/JSON относительно формирования web-сервисами вы можете посмотреть на моей страничке - SOAP/WSDL vs XML data exchange.

Правильность выбора того или иной AJAX-технологии (и на клиенте и на сервере) в каждом конкретном случае и является мерой профессонализма программиста.



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