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

На цій сторінці я розповім як робиться періодична публікація дилерского прайсу, коли головна компания викладає свій прайс на 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 сборками.
-
1: exec sp_configure 'clr enabled', 1;
-
2: ALTER DATABASE [OMSK] SET TRUSTWORTHY ON
Як правило, потрібно ще корректно встановити власника бази - і це, мабуть, все.
-
3: exec sp_changedbowner 'sa'
Але для сборці SQL-CLR з правами External Access потрібно набагато більше.
- 4: По-перше, у самому проєкті потрібно встановити право сбоці на External Access:
- 5: Також треба підписати сборку, щоб сборка мала StrongName:
- 6: Ще треба пам'ятати про сумісність версій SQL и NET - у моєму випадку потребувалося встановити фрейморк сборці NET 3.5, хоча за замовчуванням я роблю зараз проєкти на NET 4.0
По-друге, потрібно декілька інших налаштувань e самому SQL-сервері - треба дозволити сборці виконуватися під логіном адміністратора комп'ютера та дозволити SQL-CLR сборкам виконуватися з правами External Access на рівні окремої бази SQL-серверу:
7: ALTER AUTHORIZATION ON DATABASE::[OMSK] TO [SQL2008\Administrator]
8: GRANT EXTERNAL ACCESS ASSEMBLY TO [OMSK]
Як бачите, налаштувань для запуску 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:
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% більше ніж у головної компанії) та опублікувати його на своєму сайті - навіть така простенька задачка потребує чимало коду і навиків программування.

<SITEMAP> <MVC> <ASP> <NET> <DATA> <KIOSK> <FLEX> <SQL> <NOTES> <LINUX> <MONO> <FREEWARE> <DOCS> <ENG> <CHAT ME> <ABOUT ME> < THANKS ME> |