(NET) NET (2015)

Складська прога на WCF-сервісах зі сканером.

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

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


По перше - самий найзвичайний компонент - це сторінка адмінки на ASP.NET MVC - у який працює менеджер (сторінку кліента я описувати не буду).



На цієї сторінці менеджер ставить дозвіл на видачу деталі. Тобто кожна деталь проходить у процесі обробці декілька фаз, які відображаються у статусі деталі - у цьому моменті нам буде цікаво, що спочатку деталь набуває статус "Відправлено до Омська", а лише потім кладовщик має можливість оприбутковувати деталь на склад, потім менеджер ставить дозвіл на видачу детали, а лише потім кладовщик видає деталь клієнту. Тобто прога працює тільки зі статусами 15-23-24-25 у таблиці нище. Це важливо, бо статути деталі не можуть змінюватися будь-як, а тільки поступово - алгоритм поступових переходів статуту деталі реалізується у коді, який ви зможете побачити нище.



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



Get-Post контроллер тут також досить звичайний, нема навіть що про нього розповісти:



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

Найбільш цікаве питання, як зробити аутентификацию звернення до сервісу. Я роблю аутентифікацію по добре відпрацьованому шляху, який у мене описан тут:

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

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

У межах таких обмежень (не треба криптувати тіло звернення і заздалегідь прошиті імена параметрів )- це буде найпростіший відомий мені механізм аутентифікації:

Але спочатку подивимося на дата-контракт:


   1:  <ServiceContract()>
   2:  Public Interface IService1
   3:   
   4:      <OperationContract()>
   5:      Function Test(ByVal value As Integer) As String
   6:   
   7:      <OperationContract()>
   8:      Function TestAU(ByVal value As Integer, AU As String) As String
   9:   
  10:      <OperationContract()>
  11:      Function GetDetail(ByVal GetParm As GetParmType, AU As String) As System.Collections.Generic.List(Of RetType)
  12:   
  13:      <OperationContract()>
  14:      Function SetState(ByVal SetParm As SetParmType, AU As String) As Integer
  15:   
  16:  End Interface
  17:   
  18:  ' Use a data contract as illustrated in the sample below to add composite types to service operations.
  19:   
  20:  <DataContract()>
  21:  Public Class SetParmType
  22:   
  23:      <DataMember()>
  24:      Public Property NewState() As Integer
  25:   
  26:      <DataMember()>
  27:      Public Property BacketID() As Guid
  28:   
  29:  End Class
  30:   
  31:   
  32:  <DataContract()>
  33:  Public Class GetParmType
  34:   
  35:      <DataMember()>
  36:      Public Property PartNumber() As String
  37:   
  38:  End Class
  39:   
  40:  <DataContract()>
  41:  Public Class RetType
  42:   
  43:      <DataMember()>
  44:      Public Property BacketID() As Guid
  45:   
  46:      <DataMember()>
  47:      Public Property Logo() As String
  48:   
  49:      <DataMember()>
  50:      Public Property EmNumber() As Integer
  51:   
  52:      <DataMember()>
  53:      Public Property DetailName() As String
  54:   
  55:      <DataMember()>
  56:      Public Property State() As Integer
  57:   
  58:      <DataMember()>
  59:      Public Property CrDate() As DateTime
  60:   
  61:      <DataMember()>
  62:      Public Property UserID() As Guid
  63:   
  64:      <DataMember()>
  65:      Public Property UserLogin() As String
  66:   
  67:      <DataMember()>
  68:      Public Property UserName1() As String
  69:   
  70:      <DataMember()>
  71:      Public Property UserName2() As String
  72:   
  73:      <DataMember()>
  74:      Public Property Kol() As Integer
  75:   
  76:      <DataMember()>
  77:      Public Property RealKol() As Integer
  78:   
  79:      <DataMember()>
  80:      Public Property InSklad() As Integer
  81:   
  82:      <DataMember()>
  83:      Public Property OutSklad() As Integer
  84:   
  85:  End Class

Як ви бачите, тут є два метода GetDetail і SetState, обидва з них мають параметр звернення (комбіноване поле) і поле аутентіфакції, що не дозволить кому завгодно отримувати інформацію через сервіс з бази.

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

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


   1:  <Activation.AspNetCompatibilityRequirements(RequirementsMode:=Activation.AspNetCompatibilityRequirementsMode.Allowed)> _
   2:  Public Class Service1
   3:      Implements IService1
   4:   
   5:   
   6:      Public Sub New()
   7:      End Sub
   8:   
   9:      Public Function Test(ByVal value As Integer) As String Implements IService1.Test
  10:          Return String.Format("You entered: {0}", value)
  11:      End Function
  12:   
  13:   
  14:      Public Function TestAU(value As Integer, AU As String) As String Implements IService1.TestAU
  15:          If IsAU(value, AU) Then Return String.Format("You entered: {0}", value)
  16:      End Function
  17:   
  18:      Public Function GetDetail(GetParm As GetParmType, AU As String) As System.Collections.Generic.List(Of RetType) Implements IService1.GetDetail
  19:          If IsAU(GetParm, AU) Then
  20:              Dim Db1 As New OmskDataContext
  21:              Dim Rows = (Db1.GetEmexPrice(GetParm.PartNumber)).ToList
  22:              If Rows.Count > 0 Then
  23:                  Dim X As New System.Collections.Generic.List(Of RetType)
  24:                  For i As Integer = 0 To Rows.Count - 1
  25:                      X.Add(New RetType With {.BacketID = Rows(i).BacketID, .Logo = Rows(i).Logo, .EmNumber = Rows(i).EmexNum, .DetailName = Rows(i).DetailName, .State = Rows(i).State, .CrDate = Rows(i).CrDate, .UserID = Rows(i).ToUser, .UserLogin = Rows(i).Login, .UserName1 = Rows(i).Name1, .UserName2 = Rows(i).Name2, .Kol = Rows(i).Kol})
  26:                      If Rows(i).RealKol Is Nothing Then
  27:                          X.Last.RealKol = 0
  28:                      Else
  29:                          X.Last.RealKol = Rows(i).RealKol
  30:                      End If
  31:                      If Rows(i).InSklad Is Nothing Then
  32:                          X.Last.InSklad = 0
  33:                      Else
  34:                          X.Last.InSklad = Rows(i).InSklad
  35:                      End If
  36:                      If Rows(i).OutSklad Is Nothing Then
  37:                          X.Last.OutSklad = 0
  38:                      Else
  39:                          X.Last.OutSklad = Rows(i).OutSklad
  40:                      End If
  41:                  Next
  42:                  Return X
  43:              End If
  44:          End If
  45:      End Function
  46:   
  47:      Public Function SetState(SetParm As SetParmType, AU As String) As Integer Implements IService1.SetState
  48:          If IsAU(SetParm, AU) Then
  49:              Dim Db1 As New OmskDataContext
  50:              If SetParm.NewState = 23 Then
  51:                  'Поступило ОМСК - кладовщик делает приход
  52:                  Db1.SetEmexInSclad(SetParm.BacketID)
  53:              ElseIf SetParm.NewState = 25 Then
  54:                  'Выдано - кладовщик делает выдачу
  55:                  Db1.SetEmexOutSclad(SetParm.BacketID)
  56:              End If
  57:          End If
  58:      End Function
  59:   
  60:   
  61:      Function IsAU(Prm As Object, AU As String) As Boolean
  62:          If AU Is Nothing Then Return False
  63:          If AU = "" Then Return False
  64:          If AU.Length <> 32 Then Return False
  65:          IsAU = False
  66:          Dim Str0 As String
  67:          If Prm.GetType.Name = GetType(System.Int32).Name Then
  68:              Str0 = Prm.ToString
  69:          ElseIf Prm.GetType.Name = GetType(GetParmType).Name Then
  70:              Str0 = (Prm.PartNumber).ToString
  71:          ElseIf Prm.GetType.Name = GetType(SetParmType).Name Then
  72:              Str0 = (Prm.BacketID).ToString & "~" & (Prm.NewState).ToString
  73:          Else
  74:              Return False
  75:          End If
  76:          Dim Str1 As String = Str0 & System.Configuration.ConfigurationManager.AppSettings("AuKey")
  77:          Dim MD5 = System.Security.Cryptography.MD5.Create()
  78:          Dim Buf1 As Byte() = (New System.Text.UnicodeEncoding).GetBytes(Str1)
  79:          Dim MD5Hash As Byte() = MD5.ComputeHash(Buf1)
  80:          Dim StrOut As New System.Text.StringBuilder
  81:          For j As Integer = 0 To MD5Hash.Length - 1
  82:              StrOut.AppendFormat("{0:X2}", MD5Hash(j))
  83:          Next
  84:          If StrOut.ToString.Trim.ToLower = AU.Trim.ToLower Then
  85:              Return True
  86:          End If
  87:   
  88:      End Function
  89:  End Class

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

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



Ці процедури маніпулюють лише декількома останніми полями у таблиці Basket, що описує товар на складі:



Процедури ці теж майже вироджені, майже пусті, майже прості Селекти та Апдейти і виглядають ось так:


   1:  ALTER procedure [dbo].[GetEmexPrice]
   2:  @SearchString as nvarchar(250)
   3:  as
   4:  With BasketRows as 
   5:  (
   6:  SELECT B.id as BacketID,
   7:         LEFT(B.Keys, CHARINDEX('~',B.Keys)-1) AS Logo,
   8:         (select top 1 Replace(s,'~','') from dbo.SplitString((select top 1 Keys from [Basket] as B
   9:         where B.Archive = 0 and B.AdmArchive=0 and upper(B.Keys) like '%'+'~|~'+upper(Replace(@SearchString,'-',''))+'~|~'+'%'), '|') 
  10:         where zeroBasedOccurance=2) as DetailName,
  11:         B.EmexNum, 
  12:         B.[State],
  13:         B.CrDate,
  14:         B.ToUser,
  15:         AspnetUsers.Login,
  16:         AspnetUsers.Name1,
  17:         AspnetUsers.Name2,
  18:         Kol,
  19:         RealKol,
  20:         InSklad,
  21:         OutSklad
  22:         --,B.Keys
  23:    FROM [Omsk].[dbo].[Basket] as B
  24:    join AspnetUsers on B.ToUser=AspnetUsers.id
  25:    where   B.AdmArchive=0 and (B.Keys) like '%'+'~|~'+upper(Replace(@SearchString,'-',''))+'~|~'+'%'
  26:    and (State=15 or State=23 or State=24 or State=25)
  27:  )
  28:  Select * from BasketRows
   1:  ALTER procedure [dbo].[SetEmexInSclad]
   2:  @BaskedID as uniqueidentifier
   3:  as
   4:   
   5:   
   6:  update dbo.Basket
   7:  set InSklad=ISNULL(InSklad,0) +1, State=23, SetManualState=1
   8:  where id=@BaskedID
   9:   
  10:  Declare @First datetime
  11:   
  12:  select @First=InDateStart from dbo.Basket
  13:  where id=@BaskedID
  14:   
  15:  If (@First is NULL) 
  16:   
  17:  Update dbo.Basket
  18:  Set InDateStart=GETDATE(), InDateEnd=GETDATE()
  19:  where id=@BaskedID
  20:   
  21:  ELSE
  22:   
  23:  Update dbo.Basket
  24:  Set InDateEnd=GETDATE()
  25:  where id=@BaskedID
   1:  ALTER procedure [dbo].[SetEmexOutSclad]
   2:  @BaskedID as uniqueidentifier
   3:  as
   4:   
   5:   
   6:  update dbo.Basket
   7:  set OutSklad=ISNULL(OutSklad,0) +1,  State=25, SetManualState=1
   8:  where id=@BaskedID
   9:   
  10:  Declare @First datetime
  11:   
  12:  select @First=OutDateStart from dbo.Basket
  13:  where id=@BaskedID
  14:   
  15:  If (@First is NULL) 
  16:   
  17:  Update dbo.Basket
  18:  Set OutDateStart=GETDATE(), OutDateEnd=GETDATE()
  19:  where id=@BaskedID
  20:   
  21:  ELSE
  22:   
  23:  Update dbo.Basket
  24:  Set OutDateEnd=GETDATE()
  25:  where id=@BaskedID

Щоб завершити опис цього B2B-Сервіса, залишаеться показати тільки його дійсній конфіг:


   1:  <?xml version="1.0"?>
   2:  <configuration>
   3:    <connectionStrings>
   4:      <add name="OmskConnectionString" connectionString="Data Source=xxx.xxx.xxx.xxx;Initial Catalog=Omsk;User ID=Omsk;Password=yyyyyyyyy"
   5:        providerName="System.Data.SqlClient" />
   6:    </connectionStrings>
   7:    <appSettings>
   8:      <add key="AUKey" value="MyMainPassword"/>
   9:    </appSettings>
  10:      <system.web>
  11:      <customErrors mode="Off"/>
  12:      <compilation debug="true" strict="false" explicit="true" targetFramework="4.0" />
  13:    </system.web>
  14:    <system.serviceModel>
  15:      <services>
  16:        <service behaviorConfiguration="debug" name="ShelService1.Service1">
  17:          <endpoint address="http://scaner.zzzzzzzzzz.ru/Service1.svc" binding="basicHttpBinding"
  18:            bindingConfiguration="NewBinding0" contract="ShelService1.IService1" />
  19:        </service>
  20:      </services>
  21:      <bindings>
  22:        <basicHttpBinding>
  23:          <binding name="NewBinding0">
  24:            <security>
  25:              <transport clientCredentialType="None" />
  26:            </security>
  27:          </binding>
  28:        </basicHttpBinding>
  29:      </bindings>
  30:      <behaviors>
  31:        <serviceBehaviors>
  32:          <behavior name="debug">
  33:            <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
  34:            <serviceMetadata httpGetEnabled="true"/>
  35:            <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
  36:            <serviceDebug includeExceptionDetailInFaults="true"/>
  37:          </behavior>
  38:       </serviceBehaviors>
  39:      </behaviors>
  40:      <serviceHostingEnvironment multipleSiteBindingsEnabled="false" aspNetCompatibilityEnabled="true" />
  41:    </system.serviceModel>
  42:    <system.webServer>
  43:      <modules runAllManagedModulesForAllRequests="true"/>
  44:    </system.webServer>
  45:    
  46:  </configuration>

На цьому з цим простеньким сервісом усе. Це найпростіший B2B-сервіс, що я зробив за останні роки, тому мені так приємно його описувати.

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

Спочатку подивимося на форму. У верхнє поле сканер зчитує штріх-код. Тут мені знову пощастило, сканер у кладовщика виявився PROTON IMS-3190 USB - а він просто працює як клавіатура, тобто нічого робити не треба, проініціалізував його об штрих-код англійської мови, поставив мишку на поле - і він зчитує штрих-код і записує його у потрібне місце.



Ну назву полей форми я приводити не буду, бо назви зрозуміли з контексту:



Модуль АU містить той самий код, що і в сервісі, з крихітною різницею звернення до конфігу з паролем у web-forms порівняно з windows-forms.

У класс MyGrid я зібрав обробку заповнення мережі з даними на головній формі. Цей класс приймає посилання на результат праці сервісу ScanerService.RetType і посилання на Windows-forms мережу DataGridView і записує дані, отримані від сервіса у необхідні клітини мережі, при чому - головне - робить це не напрямки, а через специфічній метод цієї мережі.
Grid.Invoke(New MethodInvoker(AddressOf DataFiller))
що дозволяє зробити цю операцію у іншому потоці, ніж поток, що працює зі вводом з клавіатури.


   1:  Public Class MyGrid
   2:   
   3:      Property _Grid As DataGridView
   4:      Property _Data As ScanerService.RetType()
   5:      Property _RowCount As Integer
   6:      Property _Msg As String
   7:      Property _PartNumber As String
   8:   
   9:      Public Sub New(Grid As DataGridView)
  10:          _Grid = Grid
  11:      End Sub
  12:   
  13:      Public Sub Fill(Data As ScanerService.RetType(), PartNumber As String)
  14:          SyncLock Me
  15:              _Data = Data
  16:              _PartNumber = PartNumber
  17:              'маршалинг из потока BackgroundWorker в поток формы
  18:              _Grid.Invoke(New MethodInvoker(AddressOf DataFiller))
  19:          End SyncLock
  20:      End Sub
  21:   
  22:      Sub AddRows(Count As Integer)
  23:          SyncLock Me
  24:              _RowCount = Count
  25:              _Grid.Invoke(New MethodInvoker(AddressOf RowAdder))
  26:          End SyncLock
  27:      End Sub
  28:   
  29:      Sub WriteError(Msg As String)
  30:          SyncLock Me
  31:              _Msg = Msg
  32:              _Grid.Invoke(New MethodInvoker(AddressOf MsgWriter))
  33:          End SyncLock
  34:      End Sub
  35:   
  36:      Sub ClearGrid()
  37:          SyncLock Me
  38:              _Grid.Invoke(New MethodInvoker(AddressOf Clearer))
  39:          End SyncLock
  40:      End Sub
  41:   
  42:      Sub DataFiller()
  43:          For i As Integer = 0 To _Data.Count - 1
  44:              _Grid.Rows.Add()
  45:              _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
  46:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = _Data(i).Logo
  47:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Data(i).DetailName
  48:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = _Data(i).CrDate
  49:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = _Data(i).EmNumber
  50:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = _Data(i).BacketID
  51:              '
  52:              _Grid.Rows.Add()
  53:              _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
  54:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = _PartNumber
  55:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Data(i).UserName1()
  56:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = _Data(i).UserName2()
  57:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = _Data(i).UserLogin()
  58:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = _Data(i).UserID()
  59:              '
  60:              _Grid.Rows.Add()
  61:              _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
  62:              Dim St1 As New State1
  63:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = St1.GetState(_Data(i).State)
  64:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = "Заказывали: " & _Data(i).Kol
  65:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = "Обещали: " & _Data(i).RealKol
  66:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = "На складе: " & _Data(i).InSklad
  67:              _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = "Выдано: " & _Data(i).OutSklad
  68:   
  69:          Next
  70:      End Sub
  71:   
  72:   
  73:      Sub Clearer()
  74:          _Grid.Rows.Clear()
  75:      End Sub
  76:   
  77:      Sub RowAdder()
  78:          _Grid.Rows.Add(_RowCount)
  79:      End Sub
  80:   
  81:      Sub MsgWriter()
  82:          _Grid.Rows.Add()
  83:          _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
  84:          _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Msg
  85:      End Sub
  86:   
  87:  End Class

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


   1:  Public Class State1
   2:      Dim StateList = New System.Collections.Generic.List(Of Object)
   3:   
   4:      Public Sub New()
   5:          StateList.Add(New With {.Selected = False, .Value = "15", .Text = "Отправлено ОМСК"})
   6:          StateList.Add(New With {.Selected = False, .Value = "23", .Text = "Поступило ОМСК"})
   7:          StateList.Add(New With {.Selected = False, .Value = "24", .Text = "Готово к выдаче"})
   8:          StateList.Add(New With {.Selected = False, .Value = "25", .Text = "Выдано со склада"})
   9:      End Sub
  10:   
  11:      Public Function GetState(Code As Integer) As String
  12:          For j As Integer = 0 To StateList.Count - 1
  13:              If StateList(j).Value = Code Then
  14:                  Return StateList(j).Text
  15:                  Exit Function
  16:              End If
  17:          Next
  18:      End Function
  19:  End Class

І ось нарешті головний код форми, який я навмисно добре закоментував. Як ви бачите, я зробив звернення до сервісу у іншому потоці, ніж поток форми.


   1:  Public Class Form1
   2:   
   3:  #Region "Инициализация проги кладовщика и связь с сайтом, на котором работает менежжер"
   4:   
   5:      Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
   6:          System.Windows.Forms.Application.DoEvents()
   7:          BackgroundWorker1.WorkerSupportsCancellation = True
   8:          BackgroundWorker1.WorkerReportsProgress = True
   9:      End Sub
  10:   
  11:      Private Sub PictureBox1_Click(sender As System.Object, e As System.EventArgs) Handles PictureBox1.Click
  12:          Dim sInfo As ProcessStartInfo = New ProcessStartInfo("http://XXXXXXX.ru/")
  13:          Process.Start(sInfo)
  14:      End Sub
  15:   
  16:  #End Region
  17:   
  18:  #Region "Особый фон формы"
  19:   
  20:      Private Sub Form1_Resize_Activate(sender As Object, e As System.EventArgs) Handles Me.Resize, Me.Activated
  21:          Me.Invalidate()
  22:      End Sub
  23:   
  24:      Protected Overrides Sub OnPaintBackground(e As System.Windows.Forms.PaintEventArgs)
  25:          Try
  26:              If Me.ClientRectangle.Height <> 0 And Me.ClientRectangle.Width <> 0 Then
  27:                  Using Brush As Drawing2D.LinearGradientBrush = New Drawing2D.LinearGradientBrush(Me.ClientRectangle, _
  28:                                                                               Color.LightGray, _
  29:                                                                               Color.Gray, _
  30:                                                                               90.0F)
  31:                      e.Graphics.FillRectangle(Brush, Me.ClientRectangle)
  32:                  End Using
  33:              End If
  34:   
  35:          Catch ex As Exception
  36:              'MsgBox(ex.Message)
  37:          End Try
  38:      End Sub
  39:   
  40:  #End Region
  41:   
  42:   
  43:  #Region "Обращение к BackgroundWorker, в потоке которого происходит обращение к web-сервису"
  44:   
  45:      Private Sub TextBoxScanerCode_TextChanged(sender As Object, e As System.EventArgs) Handles TextBoxScanerCode.TextChanged
  46:          If TextBoxScanerCode.Text = "" Then
  47:              ButtonRequest.Enabled = False
  48:          Else
  49:              ButtonRequest.Enabled = True
  50:          End If
  51:      End Sub
  52:   
  53:      Private Sub ButtonRequest_Click(sender As System.Object, e As System.EventArgs) Handles ButtonRequest.Click
  54:          If Not BackgroundWorker1.IsBusy = True Then
  55:              ButtonIN.Enabled = False
  56:              ButtonOUT.Enabled = False
  57:              TextBoxIN.Enabled = False
  58:              TextBoxOUT.Enabled = False
  59:              BackgroundWorker1.RunWorkerAsync()
  60:          End If
  61:      End Sub
  62:   
  63:  #End Region
  64:   
  65:  #Region "Обращение к web-сервису"
  66:   
  67:      Dim Ret As ScanerService.RetType()
  68:      Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
  69:          'тут все уже в потоке BackgroundWorker
  70:          Dim Gr1 As New MyGrid(DataGridView1)
  71:          If CheckBox1.Checked Then
  72:              Gr1.ClearGrid()
  73:          End If
  74:          Dim X As New ScanerService.Service1Client
  75:          Dim Prm As New ScanerService.GetParmType
  76:          Prm.PartNumber = TextBoxScanerCode.Text
  77:          Ret = X.GetDetail(Prm, AU.CreateAU(Prm))
  78:          '
  79:          If BackgroundWorker1.CancellationPending Then
  80:              e.Cancel = True
  81:          End If
  82:      End Sub
  83:   
  84:   
  85:      Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
  86:          'тут все уже в потоке BackgroundWorker
  87:          Dim Grid1 As New MyGrid(DataGridView1)
  88:          If e.Cancelled = True Then
  89:              Grid1.WriteError("Canceled!")
  90:          ElseIf e.Error IsNot Nothing Then
  91:              DataGridView1.Rows.Add(1)
  92:              Grid1.WriteError(e.Error.Message)
  93:          Else
  94:              If Ret IsNot Nothing Then
  95:                  If Ret.Count > 0 Then
  96:                      Grid1.Fill(Ret, TextBoxScanerCode.Text)
  97:                      ButtonIN.Enabled = True
  98:                      ButtonOUT.Enabled = True
  99:                      TextBoxIN.Enabled = True
 100:                      TextBoxOUT.Enabled = True
 101:                  End If
 102:              End If
 103:          End If
 104:      End Sub
 105:   
 106:  #End Region
 107:   
 108:  #Region "Логика приема на склад товара - отгрузки со склада"
 109:   
 110:      Private Sub DataGridView1_SelectionChanged(sender As Object, e As System.EventArgs) Handles DataGridView1.SelectionChanged
 111:          'выделить можно только титульную строчку описания товара (один товар описан в трех строчках)
 112:          If DataGridView1.SelectedRows.Count > 0 Then
 113:              If DataGridView1.SelectedRows(0).Index = 0 Or DataGridView1.SelectedRows(0).Index Mod 3 = 0 Then
 114:              Else
 115:                  Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
 116:                  DataGridView1.SelectedRows(0).Selected = False
 117:                  If CurrentSelectedRow Mod 3 = 1 Then
 118:                      DataGridView1.Rows(CurrentSelectedRow - 1).Selected = True
 119:                  ElseIf CurrentSelectedRow Mod 3 = 2 Then
 120:                      DataGridView1.Rows(CurrentSelectedRow - 2).Selected = True
 121:                  End If
 122:              End If
 123:          End If
 124:      End Sub
 125:   
 126:   
 127:      Private Sub ButtonIN_Click(sender As Object, e As System.EventArgs) Handles ButtonIN.Click
 128:          Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
 129:          'сервис выдает список товаров только в нужном состоянии
 130:          Dim BasketID As Guid = DataGridView1.Rows(CurrentSelectedRow).Cells(4).Value
 131:          Dim X As New ScanerService.Service1Client
 132:          Dim Prm As New ScanerService.SetParmType
 133:          If IsNumeric(TextBoxIN.Text) Then
 134:              ' нельзя оприходовать на склад больше товаров, чем отправлено
 135:              If CInt(TextBoxIN.Text) + CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(3).Value.ToString.Replace("На складе: ", "")) <= CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(2).Value.ToString.Replace("Обещали: ", "")) Then
 136:                  For i As Integer = 0 To CInt(TextBoxIN.Text) - 1
 137:                      Prm.BacketID = BasketID
 138:                      Prm.NewState = 23 'ставится состояние ПРИШЛО В ОМСК
 139:                      X.SetState(Prm, AU.CreateAU(Prm))
 140:                  Next
 141:                  ButtonRequest_Click(Nothing, Nothing)
 142:              Else
 143:                  Beep()
 144:                  MsgBox("Проверь количество", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
 145:              End If
 146:          End If
 147:      End Sub
 148:   
 149:      'после прихода товара, менеджер с сайта ставит РАЗРЕШЕНИЕ НА ВЫДАЧУ
 150:      'после выдачи всего товара менеджер с сайта удаляет обработанный товар в архив
 151:   
 152:      Private Sub TextBoxOUT_Click(sender As Object, e As System.EventArgs) Handles ButtonOUT.Click
 153:          Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
 154:          'возможна выдача только товаров в состоянии "Готово к выдаче" и "Выдано со склада" (уже началась выдача)
 155:          If DataGridView1.Rows(CurrentSelectedRow + 2).Cells(0).Value = "Готово к выдаче" Or DataGridView1.Rows(CurrentSelectedRow + 2).Cells(0).Value = "Выдано со склада" Then
 156:              Dim BasketID As Guid = DataGridView1.Rows(CurrentSelectedRow).Cells(4).Value
 157:              Dim X As New ScanerService.Service1Client
 158:              Dim Prm As New ScanerService.SetParmType
 159:              If IsNumeric(TextBoxOUT.Text) Then
 160:                  'нельзя выдать больше чем есть
 161:                  If CInt(TextBoxOUT.Text) + CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(4).Value.ToString.Replace("Выдано: ", "")) <= CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(3).Value.ToString.Replace("На складе: ", "")) Then
 162:                      For i As Integer = 0 To CInt(TextBoxOUT.Text) - 1
 163:                          Prm.BacketID = BasketID
 164:                          Prm.NewState = 25 'после отгрузки первой единицы товара товар ставится в состояние ВЫДАНО
 165:                          X.SetState(Prm, AU.CreateAU(Prm))
 166:                      Next
 167:                      ButtonRequest_Click(Nothing, Nothing)
 168:                  Else
 169:                      Beep()
 170:                      MsgBox("Проверь количество", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
 171:                  End If
 172:              End If
 173:          Else
 174:              Beep()
 175:              MsgBox("Нет разрешения на выдачу", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
 176:          End If
 177:      End Sub
 178:   
 179:  #End Region
 180:   
 181:  End Class

Посилання на B2B web-cервіс я побудував за допомогою студії автоматично - WCF як раз і зроблений для того, щоб величезний класс Reference.vb (на 500 стрічок) був побудований автоматично і абсолютно точно по WSDL за допомогою утиліти SVCUTIL, що на задньому плані відпрацьовує за формами Студії.



Ну ось таку (можливо щонайменшу із можливих за багато років) програм мені довелося зробити у цьому році. Може декому буде цікаво щось подивитися у моєму коді.


Ще почитати про будівництво web-сервісів ви можете тут:



Comments ( )
Link to this page: //www.vb-net.com/Scaner/index.htm
< THANKS ME>