(MVC) MVC (2015)

Періодична публікація дилерського прайсу.

На цій сторінці я розповім як робиться періодична публікація дилерского прайсу, коли головна компания викладає свій прайс на FTP, а ділер у своєму регіоні націнює прайс і публікує його на своему сайті.

Найбільш важливим для корректного вирішення цієї задачі є час публікації, тобто якщо прайс головної компанії публікується наприклад у 10 годин утра, то через 15-ть хвилин прайс повинен бути опублікований на сайті дилерської компанії.Це робить неможливим отакі прості рішення, коли в Global.asaх робиться додатковий поток для виконання періодичних задач:

   1:      Sub Application_Start()
   2:          AreaRegistration.RegisterAllAreas()
   3:   
   4:          RegisterGlobalFilters(GlobalFilters.Filters)
   5:          RegisterRoutes(RouteTable.Routes)
   6:   
   7:          Application("EmexAutoLogin") = GlobalFunction.GetAutoLogin()
   8:          Application("EmexAviaLogin") = GlobalFunction.GetAviaLogin()
   9:          '
  10:          Dim RefreshStateThread As System.Threading.Thread = New System.Threading.Thread(AddressOf RefreshStateSub)
  11:          RefreshStateThread.Name = "RefreshState (Started " & Now.ToString & ")"
  12:          Application("RefreshStateThread") = RefreshStateThread
  13:          ApplicationPrm.GetGlobalParameters("RefreshStateInterval", 1)
  14:          Application("RefreshStateInterval") = System.Configuration.ConfigurationManager.AppSettings("RefreshStateInterval")
  15:          If System.Configuration.ConfigurationManager.AppSettings("RefreshStateThread") Then RefreshStateThread.Start()
  16:          '
  17:   
  18:   
  19:      End Sub
  20:   
  21:      Sub RefreshStateSub()
  22:          Dim X As New Emex
  23:          Dim DB1 As New OmskDataContext
  24:          While True
  25:              'диагностика фонового процесса
  26:              Dim RefreshStateThread As System.Threading.Thread = CType(Application("RefreshStateThread"), System.Threading.Thread)
  27:              DB1.TraceAdd("RefreshStateThread", RefreshStateThread.Name & " started at " & Now.ToString)
  28:              X.GetNothingGlobalIDRows()
  29:              X.RefreshState()
  30:              X.RefreshEmexAuto()
  31:              'задержка
  32:              System.Threading.Thread.Sleep(CInt(Application("RefreshStateInterval")) * 3600000)
  33:          End While
  34:      End Sub


   1:  <?xml version="1.0" encoding="utf-8"?>
   ...  
  20:      <!-- интервал чтения состояния заказов - 1час -->
  21:      <add key="RefreshStateInterval" value="1"/>
  22:      <add key="RefreshStateThread" value="True"/>




Взагалі, такі прості рішення працюють добре, але не тоді, коли треба чітко витримати час виконання завдання. Для таких часо-залежних задач єдиний надійний механізм - це задача агента SQL-сервера. Ну, власне, я вже багато разів описував подібні задачі у своєму блозі - Выполнение периодических задач в ASP.NET - у кожному реальному інтернет-проєкті працюють десятки таких задач.





Інше обмеження цієї задачки в тому, ща зробити публікацію прайсу на сторонній FTP за допомогою мікрософтовських класов System.Net.FtpWebRequest неможливо, бо ці класи добре зчитують дані з FTP, але аплоад вони не роблять. Тобто треба зробити ще додаткову работу по інтеграції у проєкт сторонньої бібліотеці публікації на FTP. Про цей баг, мабуть, ніхто ніде відкрито не пише, але я трасував багато разів FTP Upload, що робиться мікрософтовскою бібліотекою з будь-якими FTP-серверами - і усі без винятку дають помилку при роботі мікросовської бібліотеці.

На перших двох скрінах протокол, як працює браузер та комерційний FTP, а на останніх трьох скрінах - як працює мікрософтовська бібліотека. У який би каталог не робився перехід, будь-який FTP-сервер дає помилку, а самому зробити послідовність команд роботи з FTP у мікрософтовської бібліотеці неможливо.





Тобто, якщо не прикручувати к проєкту сторонню бібліотеку FTP Upload, то прайс можливо публікувати тільки на своєму сайті. Ці два обмеження (надійний таймер та публікація на своєму сайті) і визначають шлях до вирішення цієї задачки.

Отже, ця задачка починається з утворення періодичної задачі агенту SQL-серверу:





Але як викликати з SQL-сервера код сайту? Це описано у мені на сайті десятки разів - SQL-Client_for_remote_XML-WebService - клиент meteonova.ru - це і є мій головний шлях вирішення таких задач вже багато років.

Для данного проєкту я зробив Lite-версію цієї SQL-CLR сборці GetPageLite, бо реквест буде до публічного URL, розташованого на моєму сайті.



   1:  Imports System
   2:  Imports System.Data
   3:  Imports System.Data.SqlClient
   4:  Imports System.Data.SqlTypes
   5:  Imports Microsoft.SqlServer.Server
   6:   
   7:  select dbo.GetPage('http://shel-auto.ru/RMSPrice.ashx?AU=2ae9363bf9e2eed25184dbcdf29f369d')
   8:  Partial Public Class UserDefinedFunctions
   9:      <Microsoft.SqlServer.Server.SqlFunction()> _
  10:      Public Shared Function GetPageLite(ByVal URL As String) As SqlString
  11:          Try
  12:              'запрос по HTTP
  13:              Dim PageRequest As System.Net.HttpWebRequest = CType(System.Net.WebRequest.Create(URL), System.Net.HttpWebRequest)
  14:              'Отправлен запрос
  15:              Dim PageResponse As System.Net.HttpWebResponse = PageRequest.GetResponse
  16:              'Получен ответ
  17:              Dim Reader As New System.IO.StreamReader(PageResponse.GetResponseStream(), System.Text.Encoding.Default, True)
  18:              Dim HTML As String = Reader.ReadToEnd
  19:              Reader.Close()
  20:              'Загружено в память
  21:              Return New SqlString(HTML)
  22:          Catch ex As Exception
  23:              Return New SqlString("Error: " & ex.Message)
  24:          End Try
  25:   
  26:      End Function
  27:  End Class


Сама по собі сборка SQL-CLR у данному випадку з себе не репрезентує з себе що небудь будь яке цікаве, взагалі у данному випадку можна було і резпонз не вичитувати. Але ця SQL-CLR - сборка повинна мати право External Access - а його потрібно налаштувати у SQL-сервері.

Взагалі для простих зборок потрібно лише два права - дозволити їм виконуватися на рівні SQL та на рівні конкретної бази встановити відношення довіру між головним модулем SQL-серверу та окремими динамічно завантаженими модулями, тобто SQL-CLR сборками.

Як правило, потрібно ще корректно встановити власника бази - і це, мабуть, все.



Але для сборці SQL-CLR з правами External Access потрібно набагато більше.

По-друге, потрібно декілька інших налаштувань e самому SQL-сервері - треба дозволити сборці виконуватися під логіном адміністратора комп'ютера та дозволити SQL-CLR сборкам виконуватися з правами External Access на рівні окремої бази SQL-серверу:

Як бачите, налаштувань для запуску SQL-CLR сборок (особливо з правами External Access) чимало.



Для того, щоб убезпечити сайт, та FTP-сервер від DDOS-атак, потрібно якось долучити аутентифікацію, тобто виконувати усі операції по створенню прайсу тільки у тому разі, якщо вхід на заданий URL виконується з таким параметром, який дозволяє зрозуміти, що виклик цього URL зробив не якийсь хакер, а цей виклик зробив легальний кліент. Це можна було б зробити просто сховавши пароль у MD5-хеш, але для того, щоб хеш був при кожному виклику різний, треба додати якийсь параметр (про який знає і сайт і клієнт, що викликає URL на сайті) - і лише після цього усе це загортати у MD5-хеш.

Тут потрібно зрозуміти, що у самому SQL-Сервері вже мається функция MD5, але особливості низькорівневого кодування стрічок роблять неможливим загорнути число та пароль у MD5 вбудованою функцією SQL-сервера, а потім загорнути те ж саме за допомогою NET-кода у сайті і порівняти MD5-хеш. Тобто немає іншого шляху, окрім того, щоб для загортання пароля і числа у MD5-хеш зробити ще одну сборку.

На щастя, це проста стрічна функція, що не потребує прав External Access, тобто після разгортання у SQL-сервері сборки GetPage для разгортання сборки CreateMD5 взагалі ничого не потрібно робити - тільки клацнуть один раз по кнопци DEPLOY у студіі.



   1:  Imports System
   2:  Imports System.Data
   3:  Imports System.Data.SqlClient
   4:  Imports System.Data.SqlTypes
   5:  Imports Microsoft.SqlServer.Server
   6:   
   7:  Partial Public Class UserDefinedFunctions
   8:      <Microsoft.SqlServer.Server.SqlFunction()> _
   9:      Public Shared Function CreateMD5AU(Prm As Integer, AuKey As String) As SqlString
  10:          Dim Str0 As String = Prm.ToString
  11:          '
  12:          Dim Str1 As String = Str0 & AuKey
  13:          Dim MD5 = System.Security.Cryptography.MD5.Create()
  14:          Dim Buf1 As Byte() = (New System.Text.UnicodeEncoding).GetBytes(Str1)
  15:          Dim MD5Hash As Byte() = MD5.ComputeHash(Buf1)
  16:          Dim StrOut As New System.Text.StringBuilder
  17:          For j As Integer = 0 To MD5Hash.Length - 1
  18:              StrOut.AppendFormat("{0:X2}", MD5Hash(j))
  19:          Next
  20:          Return New SqlString(StrOut.ToString.Trim.ToLower)
  21:      End Function
  22:  End Class


Тобто, якщо обидві зборки добре розгорнуті у SQL-сервері, то у ієрархії об'єктів SQL-серверу вони виглядають так:





У якості специфічного числа, що постійно змінює MD5-хеш і додается к потаємному паролю я вибрав номер запису протоколу утворення нового прайсу. Тобто я зробив отаку найпростішу табличку:





Але до неї має доступ і сайт і SQL-CLR. І обидва ці компоненти дуже просто можуть побачити останню стрічку у цієї табличці.

   1:  ALTER procedure [dbo].[GetLastRMSBatchNum]
   2:  as
   3:  SELECT isnull(MAX(i),0) as MaxNum
   4:    FROM [Omsk].[dbo].[RMSBatch]


І якщо саме цей номер (у вигляді MD5-хеша надходить до сайта) - це означає, що виклик до сайту з вимогою зробити новий прайс зроблено саме з SQL-сервера, з його планувальника завдань. А інакше виклик хандлеру сайта (що виробляє ділерский прайс) робить хакер з метою DDOS-атаки, тобто він домагається щоб головна компанія заборонила ділеру забирати прайс з свого FTP через надмірне завантаження FTP постійними реквестами.

Тобто, тепер ви можете зрозуміти ту саму процедуру, яку викликає Агент SQL-серверу по планувальнику завдань.



   1:  ALTER procedure [dbo].[CreateNewRMSPrice]
   2:  as
   3:  declare @I as int, @MD5 as nvarchar(32)
   4:  select @I=MAX(i) from dbo.RMSBatch
   5:  select @MD5=dbo.CreateMD5AU(@I,'Password')
   6:  select dbo.GetPageLite('http://shel-auto.ru/RMSPrice.ashx?AU='+ @MD5)


Тобто це все, що потрібно для виклику хандлеру сайту по суворому таймеру часу. Який легко налаштовується по часу і типу рекурсії. Далі я буду описувати вже код сайту, який у потрібний час отримує виклик типу:

http://shel-auto.ru/RMSPrice.ashx?AU=2ae9363bf9e2eed25184dbcdf29f369d
і виробляє по отримання цього виклику ділерский прайс.

Тут є ще невеличка крапочка - якщо SQL-сервер розташовано на тому ж самому хостінгу, що і WEB-сервер, то треба не забути додати стрічку у C:\Windows\System32\drivers\etc\hosts - без цього im'я сайту не буде відповідати чинному IP-адресу сайта.



Отже, тепер подивимося на код хандлеру RMSPrice.ashx.



   1:  Imports System.Web
   2:  Imports System.Web.Services
   3:   
   4:  Public Class RMSPrice
   5:      Implements System.Web.IHttpHandler, IRequiresSessionState
   6:   
   7:      Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
   8:   
   9:          Dim db5 As New OmskDataContext
  10:          Dim LastNum As Integer = db5.GetLastRMSBatchNum(0).MaxNum
  11:          If AU.IsAU(LastNum, HttpContext.Current.Request.Params("AU")) Then
  12:              '
  13:              Dim db6 As New OmskDataContext
  14:              Dim R As New RMSBatch
  15:              R.CrDate = Now.ToString
  16:              db6.RMSBatches.InsertOnSubmit(R)
  17:              db6.SubmitChanges()
  18:              '
  19:              Dim db2 As New OmskDataContext
  20:              db2.TraceAdd("RMSBarch-Start", Now.ToString)
  21:              '
  22:              Dim db1 As New OmskDataContext
  23:              Dim Users = (From X In db1.AspnetUsers Select X Where X.IsAdmin).ToList
  24:              If Users.Count > 0 Then
  25:                  '
  26:                  Dim Lerr1 As String = ""
  27:                  Try
  28:                      'создание екземпляра контроллера и вызов пакета в нем
  29:                      Dim X As New OMSK1.Omsk.AdminController
  30:                      X.RMSPriceBODY()
  31:                      X.RMSUnzipBODY()
  32:                      X.RMSCalcBODY()
  33:                      X.RMSZipBODY()
  34:                      '
  35:                      Dim db3 As New OmskDataContext
  36:                      db3.TraceAdd("RMSBarch-End", Now.ToString)
  37:   
  38:                      Dim db7 As New OmskDataContext
  39:                      Dim LastRMSRecord = (From Q In db7.RMSBatches Order By Q.i Descending Take 1).ToList
  40:                      LastRMSRecord(0).TXT = "OK - " & Now.ToString
  41:                      db7.SubmitChanges()
  42:   
  43:                  Catch ex As Exception
  44:                      Lerr1 = ex.Message
  45:                      Dim db4 As New OmskDataContext
  46:                      db4.TraceAdd("RMSBarch-Err", ex.Message)
  47:   
  48:                      Dim db8 As New OmskDataContext
  49:                      Dim LastRMSRecord = (From Q In db8.RMSBatches Order By Q.i Descending Take 1).ToList
  50:                      LastRMSRecord(0).TXT = Now.ToString & ex.Message
  51:                      db8.SubmitChanges()
  52:                  End Try
  53:   
  54:                  context.Response.ContentType = "text/plain"
  55:                  If Lerr1 = "" Then
  56:                      context.Response.Write("OK")
  57:                  Else
  58:                      context.Response.Write(Lerr1)
  59:                  End If
  60:              End If
  61:          End If
  62:      End Sub
  63:   
  64:      ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
  65:          Get
  66:              Return False
  67:          End Get
  68:      End Property
  69:   
  70:  End Class


Взагалі, це не обов'язково робити саме хандрером, але я все роблю на хандлерах, бо тут не потрібні усі кроки обробки реквесту, хандлер напрямки виконується IIS при отриманні його URL web-сервером.

У самому хандлері у стрічках 9-11 викликається саме той код, що ми поклали у середину SQL-серверу у вигляді SQL-CLR сборці CreateMD5 (код цього классу буде нище), далі хандлер веде різноманітну статистику, у тому числі у стрічках 13-17 додає нові стрічки у журнал своєї роботи, і таким чином модифікує табличку, яка використовується для аутентифікації. Таким чином MD5-код аутентифікації кожний раз новий. І його утворює з таблички RMSBatch сборка при виклику хандлеру, а хандлер бачить теє ту табличку, також утворює такий самий MD5, порівнює їх, і якщо вони рівні - то робить свою роботу, тобто утворює дилерській прайс.

Взагалі можна було в зробити окремий класс, що виробляє дилерській прайс, но я роблю ще хитріше, тобто беру готовий класс контроллера і роблю його екземпляр і напрямки викликаю методи цього контроллера. Зверніть увагу, що утворення прайсу розбито на окремі кроки - це дозволить зробити форми з окремими кнопками на кожний крок.

Ну і ще подивимося на класс аутентифікації, який я зробив у вигляді модуля Бейсіка.



   1:  Public Module AU
   2:      Public Function IsAU(Prm As Integer, AU As String) As Boolean
   3:          If AU Is Nothing Then Return False
   4:          If AU = "" Then Return False
   5:          If AU.Length <> 32 Then Return False
   6:          IsAU = False
   7:          Dim Str0 As String = Prm.ToString
   8:          '
   9:          Dim Str1 As String = Str0 & System.Configuration.ConfigurationManager.AppSettings("AuKey")
  10:          Dim MD5 = System.Security.Cryptography.MD5.Create()
  11:          Dim Buf1 As Byte() = (New System.Text.UnicodeEncoding).GetBytes(Str1)
  12:          Dim MD5Hash As Byte() = MD5.ComputeHash(Buf1)
  13:          Dim StrOut As New System.Text.StringBuilder
  14:          For j As Integer = 0 To MD5Hash.Length - 1
  15:              StrOut.AppendFormat("{0:X2}", MD5Hash(j))
  16:          Next
  17:          If StrOut.ToString.Trim.ToLower = AU.Trim.ToLower Then
  18:              Return True
  19:          End If
  20:      End Function
  21:   
  22:      Public Function CreateAU(Prm As Integer) As String
  23:          Dim Str0 As String = Prm.ToString
  24:          '
  25:          Dim Str1 As String = Str0 & System.Configuration.ConfigurationManager.AppSettings("AuKey")
  26:          Dim MD5 = System.Security.Cryptography.MD5.Create()
  27:          Dim Buf1 As Byte() = (New System.Text.UnicodeEncoding).GetBytes(Str1)
  28:          Dim MD5Hash As Byte() = MD5.ComputeHash(Buf1)
  29:          Dim StrOut As New System.Text.StringBuilder
  30:          For j As Integer = 0 To MD5Hash.Length - 1
  31:              StrOut.AppendFormat("{0:X2}", MD5Hash(j))
  32:          Next
  33:          Return StrOut.ToString.Trim.ToLower
  34:      End Function
  35:   
  36:      Public Function CreateAuPrm() As String
  37:          Dim db5 As New OmskDataContext
  38:          Dim LastNum As Integer = db5.GetLastRMSBatchNum(0).MaxNum
  39:          Return CreateAU(LastNum)
  40:      End Function
  41:   
  42:  End Module


Якихось особливостей тут немає, мені було лениво аналізувати вхідні параметри на Reflection, задача того не потребує (хоча взагалі це було би більш правильно) - як бачите, все зроблено тут топором.



І ось, нарешті мі дошли до кода контроллера. Але щоб зрозуміти основні операції, що читають прайс с FTP-серверу головної фірми, націнюють його, та викладають на своєму сайті - ми спочатку подивимося на код ще одного компонента мого сайта, який зберігає параметри, які задають режими роботи виробника дилерського прайсу.





Форма з параметрами, що ви бачите вище, у смисловій частині виглядає ось так:



 ....
  56:                              <% Using Html.BeginForm()%>
  57:                              <table border="0" cellpadding="0" cellspacing="0" width="100%">
 ....
  68:                                   <tr>
  69:                                      <td>
  70:                                          Наценка %
  71:                                      </td>
  72:                                      <td>
  73:                                          <%: Html.ValidationMessage(Application("RMS-FTP-OverPrice"))%>
  74:                                      </td>
  75:                                      <td>
  76:                                          <%: Html.EditorFor(Function(model) Application("RMS-FTP-OverPrice"))%>
  77:                                      </td>
  78:                                  </tr>
  79:                                  <tr>
  80:                                      <td>
  81:                                          PriceColumn
  82:                                      </td>
  83:                                      <td>
  84:                                          <%: Html.ValidationMessage(Application("PriceColumn"))%>
  85:                                      </td>
  86:                                      <td>
  87:                                          <%: Html.EditorFor(Function(model) Application("PriceColumn"))%>
  88:                                      </td>
  89:                                  </tr>
 ....
 289:                                  <tr>
 290:                                      <td>
 291:                                          <input type="submit" class="postbutton" value="Сохранить" />
 292:                                      </td>
 293:                                      <td>
 294:                                          <%: Html.ValidationSummary(True) %>
 295:                                      </td>
 296:                                      <td>
 297:                                      </td>
 298:                                  </tr>
 299:                                  <tr>
 300:                                      <td>
 301:                                          &nbsp;
 302:                                      </td>
 303:                                      <td>
 304:                                      </td>
 305:                                      <td>
 306:                                      </td>
 307:                                  </tr>
 308:                              </table>
 309:                              <% End Using%>
 ....


Тобто сотні параметрів сайту зберігаються у об'єкті Application. Але як це я роблю звичайно? У мене є многократно перевірена альтернатива зберігання параметрів у web.config - яка, на відміну від стандартних можливостей зберігання параметрів у web.config:



   1:  <configuration>
   2:    <configSections>   
   3:      <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
   4:        <section name="OMSK1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
   5:        <section name="Omsk1.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
   6:      </sectionGroup>
   7:    </configSections>
   8:    <connectionStrings>
   9:      <add name="OmskConnectionString" connectionString="Data Source=AAA.BBB.CCC.DDD;Initial Catalog=Omsk;User ID=XXXXXX;Password=YYYYYY"
  10:        providerName="System.Data.SqlClient" />
  11:    </connectionStrings>
  12:    <appSettings>
  13:      <!-- ключ аутентификации -->
  14:      <add key="AUKey" value="MainPassword"/>
  15:      <!-- интервал чтения состояния заказов - 1час -->
  16:      <add key="RefreshStateInterval" value="1"/>
  17:      <add key="RefreshStateThread" value="True"/>


дозволяє самому юзеру у будь-який час змінювати параметри роботи сайту. Наприклад націнку ділерского прайсу адмін сайта може змінювати сам скільки завгодно разів.

Ось як я це роблю звичайно. По перше у базі є табличка для зберігання усіх параметрів сайту:





І є дві SQL-процедури для читання та запису параметрів у цю табличку:



   1:  ALTER procedure [dbo].[SetGlobalParm]
   2:  (
   3:  @Name nvarchar(50),
   4:  @Value  nvarchar(max) = null
   5:  )
   6:  as
   7:  Insert dbo.GlobalParms(Date,ParmName,ParmValue) values(GETDATE(),@Name, @Value)


   1:  ALTER procedure [dbo].[GetGlobalParm]
   2:  (
   3:  @Name nvarchar(50)
   4:  )
   5:  as
   6:  select top (1) i,Date,ParmValue from dbo.GlobalParms where ParmName=@Name order by i desc

Як ви бачите, значення параметрів ніколи не знищуються, лише додаются нові значення, що дуже допомагає у пошуках помилок.

А на самому сайті є отакі врапери цих SQL-процедур:



   1:  Public Class ApplicationPrm
   2:   
   3:   
   4:      Public Shared Function GetGlobalParameters(Prm As String, DefaultPrm As String) As String
   5:          Dim db1 As New OmskDataContext
   6:          Try
   7:              Dim OneRecord = (From X In db1.GlobalParms Select X Where X.ParmName = Prm Order By X.i Descending).First
   8:              If OneRecord IsNot Nothing Then
   9:                  HttpContext.Current.Application(Prm) = OneRecord.ParmValue
  10:              End If
  11:          Catch ex As System.InvalidOperationException
  12:              HttpContext.Current.Application(Prm) = DefaultPrm
  13:          End Try
  14:          Return HttpContext.Current.Application(Prm)
  15:      End Function
  16:   
  17:   
  18:      Public Shared Function SetGlobalParameters(Prm As String, Value As Integer, Optional DefaultValue As Integer = 0) As Integer
  19:          Dim db1 As New OmskDataContext
  20:          Try
  21:              db1.SetGlobalParm(Prm, Value)
  22:              HttpContext.Current.Application(Prm) = CInt(Value)
  23:          Catch ex As System.InvalidOperationException
  24:              HttpContext.Current.Application(Prm) = CInt(DefaultValue)
  25:          End Try
  26:          Return HttpContext.Current.Application(Prm)
  27:      End Function
  28:   
  29:      Public Shared Function SetGlobalParameters(Prm As String, Value As Decimal, Optional DefaultValue As Decimal = 0) As Decimal
  30:          Dim db1 As New OmskDataContext
  31:          Try
  32:              db1.SetGlobalParm(Prm, Value)
  33:              HttpContext.Current.Application(Prm) = Decimal.Parse(Value, System.Globalization.CultureInfo.InvariantCulture)
  34:          Catch ex As System.InvalidOperationException
  35:              HttpContext.Current.Application(Prm) = CDec(DefaultValue)
  36:          End Try
  37:          Return HttpContext.Current.Application(Prm)
  38:      End Function
  39:   
  40:      Public Shared Function SetGlobalParameters(Prm As String, Value As String, Optional DefaultValue As String = "") As String
  41:          Dim db1 As New OmskDataContext
  42:          Try
  43:              db1.SetGlobalParm(Prm, Value)
  44:              HttpContext.Current.Application(Prm) = Value
  45:          Catch ex As System.InvalidOperationException
  46:              HttpContext.Current.Application(Prm) = DefaultValue
  47:          End Try
  48:          Return HttpContext.Current.Application(Prm)
  49:      End Function
  50:   
  51:  End Class


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



   1:      Sub Application_Start()
   2:          AreaRegistration.RegisterAllAreas()
   3:   
   4:          RegisterGlobalFilters(GlobalFilters.Filters)
   5:          RegisterRoutes(RouteTable.Routes)
   6:   
 ....
  18:          ApplicationPrm.GetGlobalParameters("OverCostPercentLocal", 15)
  19:          ApplicationPrm.GetGlobalParameters("KursDirham", 10)
  20:          ApplicationPrm.GetGlobalParameters("KursEuro", 40)
  21:          ApplicationPrm.GetGlobalParameters("Delivery", 10)
  22:          ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-URL", "ftp.rmsauto.ru")
  23:          ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-Login", "XXXXXX")
  24:          ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-Pass", "YYYYYY")
  25:          ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-FileName", "Срок1_1/_All.zip")
  26:          ApplicationPrm.GetGlobalParameters("RMS-FTP-OverPrice", CDec(10.0))
  27:          ApplicationPrm.GetGlobalParameters("ZipCommandLine", """C:\Program Files\7-Zip\7z.exe""")
  28:          ApplicationPrm.GetGlobalParameters("ZipExpand", " e ""@ArchiveFullFileName"" -w@OutDir -o@OutDir -y")
  29:          ApplicationPrm.GetGlobalParameters("ZipCompress", " a ""@ArchiveFullFileName"" ""@PriceFullFileName"" -w@OutDir -o@OutDir -y")
  30:          ApplicationPrm.GetGlobalParameters("PriceColumn", CInt(5))
  31:          ApplicationPrm.GetGlobalParameters("OutColumn", CInt(6))
  32:          ApplicationPrm.GetGlobalParameters("DecimalFormat", "N2")
  33:          ApplicationPrm.GetGlobalParameters("OutFileName", "Shel.txt")
  34:          ApplicationPrm.GetGlobalParameters("InFileName", "_All.txt")
  35:          ApplicationPrm.GetGlobalParameters("InputPriceEncode", "windows-1251")
  36:          ApplicationPrm.GetGlobalParameters("OutPriceEncode", "UTF-8")
 ....


А по-друге завантажуються свіженькими значеннями у GET-контроллері:





І ось нарешті код тих самих головних фрагментів смислової обробці прайсу, які викликаються у Хедлері RMSPrice.ashx у стрічках 30-33. Як я вже казав віще, їх можна було б винести і у окремий клас, але я розмістив цей код безпосередньо у контролері.

Вичитування файла з FTP:



1579:          Function RMSPriceBODY() As String
1580:              Dim Lerr1 As String = "", Lerr2 As String = ""
1581:              Try
1582:                  Dim SourceRMSFTPURL As String = ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-URL", "ftp.rmsauto.ru")
1583:                  Dim SourceRMSFTPLogin As String = ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-Login", "YYYYYYY")
1584:                  Dim SourceRMSFTPPass As String = ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-Pass", "XXXXXXX")
1585:                  Dim SourceRMSFTPFileName As String = ApplicationPrm.GetGlobalParameters("Source-RMS-FTP-FileName", "Срок1_1/_All.zip")
1586:                  Dim TempDirectory As String = Server.MapPath("/")
1587:                  Dim SourceFileName As String = Guid.NewGuid.ToString & ".zip"
1588:                  Dim FullFileName As String = IO.Path.Combine(TempDirectory, SourceFileName)
1589:                  Dim SourceURL As New Uri("ftp://" & SourceRMSFTPURL & "/" & SourceRMSFTPFileName)
1590:                  '
1591:                  Dim SourceFTPRequest As System.Net.FtpWebRequest = CType(Net.FtpWebRequest.Create(SourceURL), System.Net.FtpWebRequest)
1592:                  Dim SourceFTPCredintal As New System.Net.NetworkCredential(SourceRMSFTPLogin, SourceRMSFTPPass)
1593:                  SourceFTPRequest.KeepAlive = False
1594:                  SourceFTPRequest.UseBinary = True
1595:                  SourceFTPRequest.UsePassive = True
1596:                  SourceFTPRequest.Credentials = SourceFTPCredintal
1597:                  SourceFTPRequest.Method = System.Net.WebRequestMethods.Ftp.DownloadFile
1598:                  '
1599:                  Dim SourceFTPResponse As System.Net.FtpWebResponse = CType(SourceFTPRequest.GetResponse, System.Net.FtpWebResponse)
1600:                  Dim SourceFTPStream As System.IO.Stream = SourceFTPResponse.GetResponseStream
1601:                  Dim SourceIOFile As System.IO.FileStream = New System.IO.FileStream(FullFileName, IO.FileMode.CreateNew)
1602:                  Dim SourceByteCount As String = 0
1603:                  Dim SourceBuffer(4095) As Byte
1604:                  Do
1605:                      SourceByteCount = SourceFTPStream.Read(SourceBuffer, 0, SourceBuffer.Length)
1606:                      SourceIOFile.Write(SourceBuffer, 0, SourceByteCount)
1607:                  Loop Until SourceByteCount = 0
1608:                  '
1609:                  Lerr2 = SourceFTPResponse.BannerMessage & vbCrLf & SourceFTPResponse.WelcomeMessage & vbCrLf & SourceFTPResponse.StatusDescription & vbCrLf & SourceFTPResponse.ExitMessage & vbCrLf & SourceIOFile.Position.ToString & " bytes receive."
1610:                  SourceFTPResponse.Close()
1611:                  SourceIOFile.Flush()
1612:                  SourceIOFile.Close()
1613:                  SourceFTPResponse.Close()
1614:                  '
1615:                  Dim db2 As New OmskDataContext
1616:                  db2.TraceAdd("RMSPriceStart", Now.ToString & Lerr2.Replace(vbCrLf, ""))
1617:              Catch ex As Exception
1618:                  Lerr1 = ex.Message
1619:                  Dim db3 As New OmskDataContext
1620:                  db3.TraceAdd("RMSPriceStart-Err", ex.Message)
1621:              End Try
1622:   
1623:              Return Lerr1 & vbCrLf & Lerr2
1624:          End Function


Раззіповка:



1643:          Function RMSUnzipBODY() As String
1644:              Dim Lerr1 As String = "", Lerr2 As String = ""
1645:              Try
1646:                  Dim TempDirectory As String = Server.MapPath("/")
1647:                  Dim Dir As New IO.DirectoryInfo(TempDirectory)
1648:                  Dim ZipFileList As IO.FileInfo() = Dir.GetFiles("*.zip")
1649:                  Dim LastZip As System.IO.FileInfo
1650:                  For Each One As System.IO.FileInfo In ZipFileList
1651:                      If LastZip Is Nothing Then
1652:                          LastZip = One
1653:                      Else
1654:                          If One.LastWriteTime > LastZip.LastWriteTime Then
1655:                              LastZip = One
1656:                          End If
1657:                      End If
1658:                  Next
1659:                  Dim ZipCommandLine As String = ApplicationPrm.GetGlobalParameters("ZipCommandLine", """C:\Program Files\7-Zip\7z.exe""")
1660:                  Dim ZipArguments As String = ApplicationPrm.GetGlobalParameters("ZipExpand", " e ""@ArchiveFullFileName"" -w@OutDir -o@OutDir -y")
1661:                  Dim ZipProcess As New Process
1662:                  ZipProcess.StartInfo.UseShellExecute = False
1663:                  ZipProcess.StartInfo.RedirectStandardOutput = True
1664:                  ZipProcess.StartInfo.FileName = ZipCommandLine
1665:                  ZipProcess.StartInfo.Arguments = ZipArguments.Replace("@ArchiveFullFileName", LastZip.FullName).Replace("@OutDir", TempDirectory)
1666:                  ZipProcess.Start()
1667:                  Do While Not ZipProcess.StandardOutput.EndOfStream
1668:                      Lerr2 &= ZipProcess.StandardOutput.ReadLine & vbCrLf
1669:                  Loop
1670:                  ZipProcess.WaitForExit()
1671:                  ZipProcess.Dispose()
1672:                  '
1673:                  Dim db2 As New OmskDataContext
1674:                  db2.TraceAdd("RMSUnzipStart", Now.ToString & Lerr2.Replace(vbCrLf, ""))
1675:              Catch ex As Exception
1676:                  Lerr1 = ex.Message
1677:                  Dim db3 As New OmskDataContext
1678:                  db3.TraceAdd("RMSUnzipStart-Err", ex.Message)
1679:              End Try
1680:              Return Lerr1 & vbCrLf & Lerr2
1681:          End Function


Націнка прайсу:



1700:          Function RMSCalcBODY() As String
1701:              Dim Lerr1 As String = "", Lerr2 As String = ""
1702:              Try
1703:                  Dim PriceColumn As Integer = ApplicationPrm.GetGlobalParameters("PriceColumn", CInt(5))
1704:                  Dim OutColumn As Integer = ApplicationPrm.GetGlobalParameters("OutColumn", CInt(6))
1705:                  Dim RMSFTPOverPrice As String = ApplicationPrm.GetGlobalParameters("RMS-FTP-OverPrice", CDec(10.0))
1706:                  Dim DecimalFormat As String = ApplicationPrm.GetGlobalParameters("DecimalFormat", "N2")
1707:                  Dim OutFileName As String = ApplicationPrm.GetGlobalParameters("OutFileName", "Shel.txt")
1708:                  Dim InFileName As String = ApplicationPrm.GetGlobalParameters("InFileName", "_All.txt")
1709:                  Dim InputPriceEncode As String = ApplicationPrm.GetGlobalParameters("InputPriceEncode", "windows-1251")
1710:                  Dim OutPriceEncode As String = ApplicationPrm.GetGlobalParameters("OutPriceEncode", "UTF-8")
1711:   
1712:                  Dim TempDirectory As String = Server.MapPath("/")
1713:                  Dim NewFileName As String = IO.Path.Combine(TempDirectory, OutFileName)
1714:                  If System.IO.File.Exists(NewFileName) Then
1715:                      System.IO.File.Delete(NewFileName)
1716:                  End If
1717:                  Dim NewFile = IO.File.CreateText(NewFileName)
1718:                  Dim OldFileName As String = IO.Path.Combine(TempDirectory, InFileName)
1719:                  Dim LineOfOldFileName As Integer = 1, LineOfNewFileName As Integer = 1
1720:                  Dim OldPrice As Decimal, NewPrice As Decimal
1721:                  If System.IO.File.Exists(OldFileName) Then
1722:                      Dim RDR As New System.IO.StreamReader(OldFileName, System.Text.Encoding.GetEncoding(InputPriceEncode))
1723:                      While Not RDR.EndOfStream
1724:                          Dim Str1 As String = RDR.ReadLine
1725:                          Dim Arr1() As String = Str1.Split(";")
1726:                          Try
1727:                              OldPrice = CDec(Arr1(PriceColumn))
1728:                              NewPrice = OldPrice * (1 + RMSFTPOverPrice / 100)
1729:                              LineOfNewFileName += 1
1730:                          Catch ex As Exception
1731:                              Lerr2 &= "Err in line " & LineOfOldFileName & vbCrLf
1732:                          End Try
1733:                          LineOfOldFileName += 1
1734:                          Dim Str2 As New StringBuilder("")
1735:                          Dim Str3 As String = ""
1736:                          For i As Integer = 0 To OutColumn
1737:                              If i = PriceColumn Then
1738:                                  Str3 &= NewPrice.ToString(DecimalFormat) & ";"
1739:                              Else
1740:                                  Str3 &= Arr1(i).ToString & ";"
1741:                              End If
1742:                          Next
1743:                          Str2.Append(Str3)
1744:                          NewFile.WriteLine(Str2.ToString)
1745:                      End While
1746:                      RDR.Close()
1747:                      NewFile.Flush()
1748:                      NewFile.Close()
1749:                      Lerr2 &= LineOfOldFileName & " line read," & LineOfOldFileName & " line write."
1750:                  Else
1751:                      Lerr1 = "File not found."
1752:                  End If
1753:                  Dim db2 As New OmskDataContext
1754:                  db2.TraceAdd("RMSCalcStart", Now.ToString & Lerr2.Replace(vbCrLf, ""))
1755:              Catch ex As Exception
1756:                  Lerr1 = ex.Message
1757:                  Dim db3 As New OmskDataContext
1758:                  db3.TraceAdd("RMSCalcStart-Err", ex.Message)
1759:              End Try
1760:              Return Lerr1 & vbCrLf & Lerr2
1761:          End Function


Запаковка:



1779:          Function RMSZipBODY() As String
1780:              Dim Lerr1 As String = "", Lerr2 As String = ""
1781:              Try
1782:                  Dim TXTFileName As String = ApplicationPrm.GetGlobalParameters("OutFileName", "Shel.txt")
1783:                  Dim ZipFileName As String = TXTFileName.Replace(".txt", ".zip")
1784:                  Dim TempDirectory As String = Server.MapPath("/")
1785:                  Dim FullTXTFileName As String = IO.Path.Combine(TempDirectory, TXTFileName)
1786:                  Dim FullZipFileName As String = IO.Path.Combine(TempDirectory, ZipFileName)
1787:                  Dim ZipCommandLine As String = ApplicationPrm.GetGlobalParameters("ZipCommandLine", """C:\Program Files\7-Zip\7z.exe""")
1788:                  Dim ZipCompress As String = ApplicationPrm.GetGlobalParameters("ZipCompress", " a ""@ArchiveFullFileName"" ""@PriceFullFileName"" -w@OutDir -o@OutDir -y")
1789:                  Dim ZipProcess As New Process
1790:                  ZipProcess.StartInfo.UseShellExecute = False
1791:                  ZipProcess.StartInfo.RedirectStandardOutput = True
1792:                  ZipProcess.StartInfo.FileName = ZipCommandLine
1793:                  ZipProcess.StartInfo.Arguments = ZipCompress.Replace("@ArchiveFullFileName", FullZipFileName).Replace("@PriceFullFileName", FullTXTFileName).Replace("@OutDir", TempDirectory)
1794:                  ZipProcess.Start()
1795:                  Do While Not ZipProcess.StandardOutput.EndOfStream
1796:                      Lerr2 &= ZipProcess.StandardOutput.ReadLine & vbCrLf
1797:                  Loop
1798:                  ZipProcess.WaitForExit()
1799:                  ZipProcess.Dispose()
1800:                  Dim db2 As New OmskDataContext
1801:                  db2.TraceAdd("RMSZipStart", Now.ToString & Lerr2.Replace(vbCrLf, ""))
1802:              Catch ex As Exception
1803:                  Lerr1 = ex.Message
1804:                  Dim db3 As New OmskDataContext
1805:                  db3.TraceAdd("RMSZipStart-Err", ex.Message)
1806:              End Try
1807:              Return Lerr1 & vbCrLf & Lerr2
1808:          End Function


Але виконати усе за раз, хандлером у пакеті по суворому таймеру - це лише частина задачки. Треба щоб менеджер мав можливість виконати сам усю операцію по окремим крокам і побачити очима результат кожного кроку трансформації прайсу. Тобто у мене виконання по окремим крокам виглядає ось так:





Тепер подивимося, як це зроблено. По-перше є форма RMSPrice.aspx (зрозуміло, що я даю лише одну маленьку її смислов частинку):

   1:  <%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/M1.Master" Inherits="System.Web.Mvc.ViewPage" %>
   2:  <%@ Import Namespace="OMSK1" %>
   3:   
   4:  <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
   5:   
   6:   
   7:      <%-- The following line works around an ASP.NET compiler warning --%>
   8:      <%: "" %>
   9:   
  10:      <% Html.RenderPartial("AdminPanel")%>
  11:   
  12:      <br />
  13:   
  14:      <script language="javascript" type="text/javascript">
  15:          $('body').on('click', 'a.linkdisabled', function (event) {
  16:              event.preventDefault();
  17:          });
  18:      </script>
  19:   
  20:   
  21:      <script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
  22:      <script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>"
  23:          type="text/javascript"></script>
  24:      <center>
  25:          <span class="pagetitle">Кабинет администратора (Наценить и переложить прайс на другой FTP) </span></center>
  26:   
  27:   
  28:      <br />
  29:      <% If ViewData("Access").RMSPriceStart = 1 Then%>
  30:      <%: Html.ActionLink("Скачать с FTP", "RMSPriceStart", Nothing, New With {.class = "postbutton", .style = "color:white;text-decoration: none;"})%>
  31:      <%Else%>
  32:      <%: Html.ActionLink("Скачать с FTP", "RMSPriceStart", Nothing, New With {.class = "postbutton linkdisabled", .style = "color:white;text-decoration: none;"})%>
  33:      <% End If%>
  34:      <br /><pre><%: ViewData("Err-RMSPriceStart")%></pre>
  35:      <br />
  36:      
  37:      
  38:      <% If ViewData("Access").RMSUnzipStart = 1 Then%>
  39:      <%: Html.ActionLink("Распаковать", "RMSUnzipStart", Nothing, New With {.class = "postbutton", .style = "color:white;text-decoration: none;"})%>
  40:      <%Else%>
  41:      <%: Html.ActionLink("Распаковать", "RMSUnzipStart", Nothing, New With {.class = "postbutton linkdisabled", .style = "color:white;text-decoration: none;"})%>
  42:      <% End If%>
  43:      <br /><pre><%: ViewData("Err-RMSUnzipStart")%></pre>
  44:      <br />
  45:      
  46:      
  47:      <% If ViewData("Access").RMSCalcStart = 1 Then%>
  48:      <%: Html.ActionLink("Наценить", "RMSCalcStart", Nothing, New With {.class = "postbutton", .style = "color:white;text-decoration: none;"})%>
  49:      <%Else%>
  50:      <%: Html.ActionLink("Наценить", "RMSCalcStart", Nothing, New With {.class = "postbutton linkdisabled", .style = "color:white;text-decoration: none;"})%>
  51:      <% End If%>
  52:      <br /><pre><%: ViewData("Err-RMSCalcStart")%></pre>
  53:      <br />
  54:   
  55:   
  56:      <% If ViewData("Access").RMSZipStart = 1 Then%>
  57:      <%: Html.ActionLink("Запаковать", "RMSZipStart", Nothing, New With {.class = "postbutton", .style = "color:white;text-decoration: none;"})%>
  58:      <%Else%>
  59:      <%: Html.ActionLink("Запаковать", "RMSZipStart", Nothing, New With {.class = "postbutton linkdisabled", .style = "color:white;text-decoration: none;"})%>
  60:      <% End If%>
  61:      <br /><pre><%: ViewData("Err-RMSZipStart")%></pre>
  62:      <br />
  63:   
  64:      <% If ViewData("Access").RMSClearStart = 1 Then%>
  65:      <%: Html.ActionLink("Почистить кеш", "RMSClearStart", Nothing, New With {.class = "postbutton", .style = "color:white;text-decoration: none;"})%>
  66:      <%Else%>
  67:      <%: Html.ActionLink("Почистить кеш", "RMSClearStart", Nothing, New With {.class = "postbutton linkdisabled", .style = "color:white;text-decoration: none;"})%>
  68:      <% End If%>
  69:      <br /><pre><%: ViewData("Err-RMSClearStart")%></pre>
  70:   
  71:   
  72:      <br /><br /><a href="../<%: Application("OutFileName").ToString.Replace(".txt",".zip")%>">Новый прайс</a><br /><br />
  73:  </asp:Content>


По-друге є код контроллера, який обробляє клікі по лінкам:



 ....
1545:          ' GET: /RMSPrice
1546:          Function RMSPrice() As ActionResult
1547:              Dim db1 As New OmskDataContext
1548:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1549:              If Users IsNot Nothing Then
1550:                  If Users.Count > 0 Then
1551:                      'сюда зашел админ
1552:                      '
1553:                      ViewData("Access") = New With {.RMSPriceStart = 1, .RMSUnzipStart = 0, .RMSCalcStart = 0, .RMSZipStart = 0, .RMSClearStart = 0}
1554:                      Dim db2 As New OmskDataContext
1555:                      db2.TraceAdd("RMSPriceGET", Now.ToString)
1556:                      Return View()
1557:                  End If
1558:              End If
1559:              Return RedirectToAction("Index", "Home")
1560:          End Function
1561:   
1562:          ' GET: /RMSPriceStart
1563:          Function RMSPriceStart() As ActionResult
1564:              Dim db1 As New OmskDataContext
1565:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1566:              If Users IsNot Nothing Then
1567:                  If Users.Count > 0 Then
1568:                      'сюда зашел админ
1569:                      '
1570:                      ViewData("Err-RMSPriceStart") = RMSPriceBODY()
1571:                      ViewData("Access") = New With {.RMSPriceStart = 0, .RMSUnzipStart = 1, .RMSCalcStart = 0, .RMSZipStart = 0, .RMSClearStart = 0}
1572:                      Return View("RMSPrice")
1573:                  End If
1574:              End If
1575:              Return RedirectToAction("Index", "Home")
1576:          End Function
 ....
1627:          ' GET: /RMSUnzipStart
1628:          Function RMSUnzipStart() As ActionResult
1629:              Dim db1 As New OmskDataContext
1630:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1631:              If Users IsNot Nothing Then
1632:                  If Users.Count > 0 Then
1633:                      'сюда зашел админ
1634:                      '
1635:                      ViewData("Err-RMSUnzipStart") = RMSUnzipBODY()
1636:                      ViewData("Access") = New With {.RMSPriceStart = 0, .RMSUnzipStart = 0, .RMSCalcStart = 1, .RMSZipStart = 0, .RMSClearStart = 0}
1637:                      Return View("RMSPrice")
1638:                  End If
1639:              End If
1640:              Return RedirectToAction("Index", "Home")
1641:          End Function
 ....
1684:          ' GET: /RMSCalcStart
1685:          Function RMSCalcStart() As ActionResult
1686:              Dim db1 As New OmskDataContext
1687:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1688:              If Users IsNot Nothing Then
1689:                  If Users.Count > 0 Then
1690:                      'сюда зашел админ
1691:                      '
1692:                      ViewData("Err-RMSCalcStart") = RMSCalcBODY()
1693:                      ViewData("Access") = New With {.RMSPriceStart = 0, .RMSUnzipStart = 0, .RMSCalcStart = 0, .RMSZipStart = 1, .RMSClearStart = 0}
1694:                      Return View("RMSPrice")
1695:                  End If
1696:              End If
1697:              Return RedirectToAction("Index", "Home")
1698:          End Function
 ....
1763:          ' GET: /RMSZipStart
1764:          Function RMSZipStart() As ActionResult
1765:              Dim db1 As New OmskDataContext
1766:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1767:              If Users IsNot Nothing Then
1768:                  If Users.Count > 0 Then
1769:                      'сюда зашел админ
1770:                      '
1771:                      ViewData("Err-RMSZipStart") = RMSZipBODY()
1772:                      ViewData("Access") = New With {.RMSPriceStart = 0, .RMSUnzipStart = 0, .RMSCalcStart = 0, .RMSZipStart = 0, .RMSClearStart = 1}
1773:                      Return View("RMSPrice")
1774:                  End If
1775:              End If
1776:              Return RedirectToAction("Index", "Home")
1777:          End Function
 ....
1868:          ' GET: /RMSClearStart
1869:          Function RMSClearStart() As ActionResult
1870:              Dim db1 As New OmskDataContext
1871:              Dim Users = (From X In db1.AspnetUsers Select X Where X.Login = HttpContext.User.Identity.Name And X.IsAdmin).ToList
1872:              If Users IsNot Nothing Then
1873:                  If Users.Count > 0 Then
1874:                      'сюда зашел админ
1875:                      '
1876:                      Dim Lerr1 As String = "", Lerr2 As String = ""
1877:                      Try
1878:                          Dim TempDirectory As String = Server.MapPath("/")
1879:                          Dim Dir As New IO.DirectoryInfo(TempDirectory)
1880:                          Dim ZipFileList As IO.FileInfo() = Dir.GetFiles("*.zip")
1881:                          For Each One As System.IO.FileInfo In ZipFileList
1882:                              Lerr2 &= "del " & One.FullName & vbCrLf
1883:                              System.IO.File.Delete(One.FullName)
1884:                          Next
1885:                          Dim db2 As New OmskDataContext
1886:                          db2.TraceAdd("RMSClearStart", Now.ToString & Lerr2.Replace(vbCrLf, ""))
1887:                      Catch ex As Exception
1888:                          Lerr1 = ex.Message
1889:                          Dim db3 As New OmskDataContext
1890:                          db3.TraceAdd("RMSZipStart-Err", ex.Message)
1891:                      End Try
1892:   
1893:                      ViewData("Err-RMSClearStart") = Lerr1 & vbCrLf & Lerr2
1894:                      ViewData("Access") = New With {.RMSPriceStart = 1, .RMSUnzipStart = 0, .RMSCalcStart = 0, .RMSZipStart = 0, .RMSClearStart = 0}
1895:                      Return View("RMSPrice")
1896:                  End If
1897:              End If
1898:              Return RedirectToAction("Index", "Home")
1899:          End Function
1900:   
1901:          Public Shadows ReadOnly Property HttpContext() As HttpContextBase
1902:              Get
1903:                  Dim context As New HttpContextWrapper(System.Web.HttpContext.Current)
1904:                  Return DirectCast(context, HttpContextBase)
1905:              End Get
1906:          End Property
1907:   
1908:          Public Shadows ReadOnly Property Server() As HttpServerUtilityBase
1909:              Get
1910:                  Dim context As New HttpContextWrapper(System.Web.HttpContext.Current)
1911:                  Dim _Server = Me.HttpContext.Server
1912:                  Return DirectCast(_Server, HttpServerUtilityBase)
1913:              End Get
1914:          End Property
 ....
4826:      End Class
4827:  End Namespace


Як ви бачите, код контроллера дуже простий, він перевіряє аутентифікацію і веде ось такий лог усіх операцій.





Найбільш цікавим фрагментом цього коду є стрічки 1901-1914, у яких заново перепризначується контекст виконання контроллеру. Тобто якщо у хендлері створити екземпляр контроллеру, то об'єкт SERVER буде пустий (VB-Nothing, С#-NULL) i Server.MapPath("/") закінчиться з помилкою, тобто контроллер так працювати не буде. Є дві альтернативи для вирішення цієї проблеми - перша, передати контекст як параметр з хандлеру. Другу альтернативу ви бачите вище - самому знову утворити контекст виконання контроллеру. Зверніть увагу на рідкісний кваліфикатор Shadows, він використаний тут тому, що у базовому классі System.Web.Mvc.Controller указано що контекст перепризначити неможливо, тобто мікрософт там не указав квалификатор Overloads Overridable.


Насправді у цієї задачці коду набагато більше, є і розсилка прайсу милом, доступ до дилерського прайсу не може отримати кто завгодно, тобто є віртуальна директорія IIS з базовою аутентифікацію:





і багато ще чого є, не описанного на цієї сторінці. Як бачите, здавалося би зовсім простенька задачка - зробити дилерський прайс (на 10% більше ніж у головної компанії) та опублікувати його на своєму сайті - навіть така простенька задачка потребує чимало коду і навиків программування.



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/RMSPrice/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>