Долгоиграющие странички с прогресс-баром.
На начало 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: <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-технологии (и на клиенте и на сервере) в каждом конкретном случае и является мерой профессонализма программиста.
|