(ASP.NET) ASP.NET (2008 год)

Практическое применение наследования, полиморфизма, интерфейсов, дженериков и делегатов на примерах в Visual Basic .NET

На этой страничке я опишу пять практических приемов обьектного программирования на Visual Basic .NET:



Interface




В этом разделея опишу сначала один свой промежуточный класс в одном своем сайте. Этот класс является одним из примерно сотни классов этого моего сайта, в котором есть много чего - начиная от виртуальных клавиатур, программируемых гугловских карт, динамически формируемых флеш-роликов и много-много еще чего. Из всего этого большого сайта я тут опишу маленький, но весьма поучительный компонент. Полагаю, для начинающих это будет весьма полезное чтиво - ибо в нем есть все - начиная от XQUERY выражений на уровне SQL и кончая интерфейсами и дженериками.

Как всегда - этот класс был написан достаточно быстро - за один вечер. Правда для этого потребовалась вся предыдущая жизнь.


Итак, вначале постановка задачи на этот промеджуточный класс.

На входе мы имеем сайт киберплата. На сайте Киберплата можно сгрузить этот список организаций, с которыми заключил договора Киберплат (в виде XML). Далее некий мой парсер (который мы тут рассматривать не будем) - парсит этот XML и загружает его в базу примерно вот в таком виде.

Для чего он это делает? А для скорости. Ну не будем же мы парсить огромнейший XML при каждом реквесте. Значит нужна предварительная обработка этого XML. Это и делает тот мой парсер, описание которого здесь приведено не будет - он сгружает WebClient.zip с сайта киберплата, раскрывает его и превращает его в тот вид, что вы увидели. Правда, вы увидели совсем сырой вид в базе - работать с ним совершенно невозможно.

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

Итак, мы увидели, что описываемый тут класс имеет на входе. Теперь посмотрим, что бы нам хотелось бы от него получить.

Надо понимать, что вообще-то даже этот небольшой платежный фрагмент этого моего сайта состоит не только из парсера, описание которого мы опустили, но и из множества-множества других компонентов, например модуля приложения цифровой подписи - который тут тоже описан не будет. Именно прилагаемая цифровая подпись позволит киберплату понять, от кого именно он получил требование на перевод денег. И при наличии депозита в нужном размере - киберплат выполнит перевод.

Итак, наш класс - принимая на вход переработанные определения с сайта КИберплата, должен сформировать на выходе некую хитро сформированную строку, которая в дальнейшем будет скормлена модулю приложения цифровой подписи, и уже с ней все это будет скормлено следующим компонентам. Технология формирования этой хитрой строки, которая скармливается модулю формирования цифровой подписи - тоже описана в тех же самых полученных от киберплата XML-данных.

Теперь, когда понятно место этого класса в информационном потоке - надо понять поток управления. А именно откуда и кто вызывает этот класс.

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

И прежде всего нам придется определится с важнейшим требованием к нашему классу - он должен ТИПИЗИРОВАННО работать с нечеткими структурами, описанными в XML. Именно это и является определяющим при программировании этого класса - ибо все остальное здесь - это простая тривиальная тягомотина.


Итак, класс должен хранить В СЕБЕ все, что позволит ему в какой-то момент сериализовать это в строку в нужном нам формате. Значит, в этом плане - это бизнес-обьект, более всего похожий на вот такой или вот такой.

C другой стороны, класс должен хранить В СЕБЕ всю технологию работы с этим самым XML, содержащим все определения киберплата (ну не будем же мы размазывать работу с этим XML по множеству приложений и множеству человек?). С третьей стороны, поскольку как становится понятно из определений киьерплата - описание платежа включает поля типа ENUM и INT, поля типа MASKED и TEXT. Следовательно, класс должен хранить в себе определение каждого из этих типов полей. И понятное дело, таких полей любого из этих четырех типов может быть множество.

Вот и вся постановка задачи на программирование. Остается только СДЕЛАТЬ это типизированно и УДОБНО для вызовов со страничек.


Итак, мы подошли собственно к технологии построение такого рода кода. Обратите внимание, что все начинается с определения четырех типо необходимых нам полей. Далее мы определяем общий интерфейс для каждого из типов полей. Если этого не сделать - то вызов со страничек нешего класса не будет похожим для любого из типов полей. И мы не получим Intellisense. Далее каждое из типов полей просто наследует интерфейс CyberCommonFields. Обратите внимание также на полиморфный конструтор - который сработает по своей ветке NEW в зависимости от типа поля CyberFieldsType. И далее собственно в каждом из типо реализуем метод хранения заданных со старнички данных:

00217: ''' <summary>
00218: ''' все начитается с четырех типов полей киберплата
00219: ''' </summary>
00220: Public Enum CyberFieldsType
00221:     _Enum_ = 1
00222:     _Int_ = 2
00223:     _Masked_ = 3
00224:     _Text_ = 4
00225: End Enum
00226: 
00227: ''' <summary>
00228: ''' все определения полей киберплата делают это
00229: ''' </summary>
00230: Public Interface CyberCommonFields
00231:     Function SetUserValue(ByVal Text As String) As Boolean
00232:     ReadOnly Property ID() As Integer
00233:     ReadOnly Property Name() As String
00234:     ReadOnly Property UserValue() As String
00235: End Interface
00236: 
00237: ''' <summary>
00238: ''' Определение одного поля параметра платежа партнера киберплата
00239: ''' </summary>
00240: Public Class OneCyberField
00241:     Public ReadOnly FieldType As CyberFieldsType
00242:     Public ReadOnly FieldDefinition As CyberCommonFields
00243:     Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberInt)
00244:         FieldType = _FieldType
00245:         FieldDefinition = _FieldDefinition
00246:     End Sub
00247:     Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberMasked)
00248:         FieldType = _FieldType
00249:         FieldDefinition = _FieldDefinition
00250:     End Sub
00251:     Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberText)
00252:         FieldType = _FieldType
00253:         FieldDefinition = _FieldDefinition
00254:     End Sub
00255:     Public Sub New(ByVal _FieldType As CyberFieldsType, ByVal _FieldDefinition As CyberEnum)
00256:         FieldType = _FieldType
00257:         FieldDefinition = _FieldDefinition
00258:     End Sub
00259: End Class
00260: 
00261: ''' <summary>
00262: ''' определение целого числа как параметра платежа и сохраненненное сюда значение целого числа, введенного юзером
00263: ''' </summary>
00264: Public Class CyberInt
00265:     Implements CyberCommonFields
00266:     Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID
00267:         Get
00268:             Return ID_
00269:         End Get
00270:     End Property
00271:     Public ReadOnly Property Name() As String Implements CyberCommonFields.Name
00272:         Get
00273:             Return Name_
00274:         End Get
00275:     End Property
00276:     Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue
00277:         Get
00278:             Return UserValue_
00279:         End Get
00280:     End Property
00281: 
00282:     Dim ID_ As Integer
00283:     Dim Name_ As String
00284:     Public ReadOnly MinLength As Integer
00285:     Public ReadOnly MaxLength As Integer
00286:     Public ReadOnly Comment As String
00287:     Dim UserValue_ As String
00288: 
00289:     Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _MinLength As Integer, ByVal _MaxLength As Integer, ByVal _Comment As String)
00290:         ID_ = _ID
00291:         Name_ = _Name
00292:         MinLength = _MinLength
00293:         MaxLength = _MaxLength
00294:         Comment = _Comment
00295:     End Sub
00296: 
00297:     Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue
00298:         If IsNumeric(Text) Then
00299:             Dim X As Integer = CInt(Text)
00300:             If X.ToString.Length <= MaxLength And X.ToString.Length >= MinLength Then
00301:                 UserValue_ = X.ToString
00302:                 Return True
00303:             Else
00304:                 Return False
00305:             End If
00306:         Else
00307:             Return False
00308:         End If
00309:     End Function
00310: End Class
00311: 
00312: ''' <summary>
00313: ''' определение маскированного поля как параметра платежа и сохраненненное сюда значение, введенного юзером
00314: ''' </summary>
00315: Public Class CyberMasked
00316:     Implements CyberCommonFields
00317: 
00318:     Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID
00319:         Get
00320:             Return ID_
00321:         End Get
00322:     End Property
00323:     Public ReadOnly Property Name() As String Implements CyberCommonFields.Name
00324:         Get
00325:             Return Name_
00326:         End Get
00327:     End Property
00328:     Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue
00329:         Get
00330:             Return UserValue_
00331:         End Get
00332:     End Property
00333: 
00334:     Dim ID_ As Integer
00335:     Dim Name_ As String
00336:     Public ReadOnly Mask As String
00337:     Public ReadOnly Comment As String
00338:     Dim UserValue_ As String
00339: 
00340:     Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _Mask As String, ByVal _Comment As String)
00341:         ID_ = _ID
00342:         Name_ = _Name
00343:         Mask = _Mask
00344:         Comment = _Comment
00345:     End Sub
00346: 
00347:     ''' <summary>
00348:     ''' cырой перенос текста с маской в значение поля
00349:     ''' </summary>
00350:     Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue
00351:         UserValue_ = Text
00352:     End Function
00353: 
00354:     ''' <summary>
00355:     ''' вставка символов в маску выполняется этим методом
00356:     ''' </summary>
00357:     Public Function SetUserValue1(ByVal RawText As String) As Boolean
00358:         Dim Mask1 As New StringBuilder(Mask)
00359:         Dim j As Integer = Mask.IndexOf("*") 'индекс по маске
00360:         For i As Integer = 0 To RawText.Length  'индекс по переданной текстовой строке
00361: Asteric:
00362:             If Mask1(j) = "*" Then
00363:                 Mask1.Replace("*", RawText(i), j, 1)
00364:                 If j >= Mask1.Length - 1 Then
00365:                     Exit For
00366:                 Else
00367:                     j += 1
00368:                 End If
00369:             Else
00370:                 If j >= Mask1.Length - 1 Then
00371:                     Exit For
00372:                 Else
00373:                     j += 1
00374:                     GoTo Asteric
00375:                 End If
00376:             End If
00377:         Next
00378:         UserValue_ = Mask1.ToString
00379:         Return True
00380:     End Function
00381: 
00382: End Class
00383: 
00384: 
00385: 
00386: ''' <summary>
00387: ''' определение текстового поля как параметра платежа и сохраненненный сюда текст, введенный юзером
00388: ''' </summary>
00389: Public Class CyberText
00390:     Implements CyberCommonFields
00391: 
00392:     Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID
00393:         Get
00394:             Return ID_
00395:         End Get
00396:     End Property
00397:     Public ReadOnly Property Name() As String Implements CyberCommonFields.Name
00398:         Get
00399:             Return Name_
00400:         End Get
00401:     End Property
00402:     Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue
00403:         Get
00404:             Return UserValue_
00405:         End Get
00406:     End Property
00407: 
00408:     Dim ID_ As Integer
00409:     Dim Name_ As String
00410:     Public ReadOnly Klava As String
00411:     Public ReadOnly Comment As String
00412:     Dim UserValue_ As String
00413: 
00414:     Public Sub New(ByVal _ID As Integer, ByVal _Name As String, ByVal _Klava As String, ByVal _Comment As String)
00415:         ID_ = _ID
00416:         Name_ = _Name
00417:         Klava = _Klava
00418:         Comment = _Comment
00419:     End Sub
00420: 
00421:     Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue
00422:         UserValue_ = Text
00423:     End Function
00424: 
00425: End Class
00426: 
00427: ''' <summary>
00428: ''' определение перечисления как параметра платежа партнера киберплата
00429: ''' </summary>
00430: Public Class CyberEnum
00431:     Implements CyberCommonFields
00432: 
00433:     Public ReadOnly Property ID() As Integer Implements CyberCommonFields.ID
00434:         Get
00435:             Return ID_
00436:         End Get
00437:     End Property
00438:     Public ReadOnly Property Name() As String Implements CyberCommonFields.Name
00439:         Get
00440:             Return Name_
00441:         End Get
00442:     End Property
00443:     Public ReadOnly Property UserValue() As String Implements CyberCommonFields.UserValue
00444:         Get
00445:             Return UserValue_
00446:         End Get
00447:     End Property
00448: 
00449:     Dim ID_ As Integer
00450:     Dim Name_ As String
00451:     Dim UserValue_ As String
00452:     Public ReadOnly EnumFields As System.Collections.Generic.List(Of CyberEnumItem)
00453: 
00454:     Public Sub New(ByVal _ID As Integer, ByVal _Name As String)
00455:         EnumFields = New System.Collections.Generic.List(Of CyberEnumItem)
00456:         ID_ = _ID
00457:         Name_ = _Name
00458:     End Sub
00459: 
00460:     Public Function SetUserValue(ByVal Text As String) As Boolean Implements CyberCommonFields.SetUserValue
00461:         For Each One As CyberEnumItem In EnumFields
00462:             If Text = One.Value Then
00463:                 UserValue_ = Text
00464:                 Return True
00465:             End If
00466:         Next
00467:         Return False
00468:     End Function
00469: End Class
00470: 
00471: ''' <summary>
00472: ''' определение одного элемента перечисляемого поля
00473: ''' </summary>
00474: Public Class CyberEnumItem
00475:     Public ReadOnly Text As String
00476:     Public ReadOnly Value As String
00477:     Public Sub New(ByVal _Text As String, ByVal _Value As String)
00478:         Text = _Text
00479:         Value = _Value
00480:     End Sub
00481: End Class
00482: 

Теперь нам нужен метод, который возьмет в статические переменные нашего класса то, что было возможно распарсить предварительным парсером и что теперь видно через XML-вьюшку. Как уже говорилось - теперь парсить весь огромный XML не требуется и мы берем из реляционной структуры одну сроку с номером оператора:

Я определил этот класс конструктором - ибо пока нету определения полей, требуемых для платежа неким конкретным оператором - нету и темы работы этого класса вообще.

00001: ''' <summary>
00002: ''' Представляет собой описание платежа в адрес ОДНОГО заданного контрагента, 
00003: ''' (зарегистрированного в КиберПлата и описанного в operators.xml) 
00004: ''' </summary>
00005: Public Class CyberPlat
00006:     'До этого уровня описание партнеров Киберплата распарсил сам SQL 
00007:     '
00008:     'select ID,GroupID,CyberID,
00009:     'Node.value('/operator[1]/name[1]', 'nvarchar(256)') as [name],
00010:     'Node.value('/operator[1]/name_for_cheque[1]', 'nvarchar(256)') as [name_for_cheque],
00011:     'Node.value('/operator[1]/inn_for_cheque[1]', 'nvarchar(256)')as [inn_for_cheque],
00012:     'Node.value('/operator[1]/comission[1]', 'nvarchar(256)')as [comission],
00013:     'Node.value('/operator[1]/image[1]', 'nvarchar(256)')as [image],
00014:     'Node.value('/operator[1]/rootmenuimage[1]', 'nvarchar(256)')as [rootmenuimage],
00015:     'Node.query('/operator/fields')as [fields],
00016:     'Node.query('/operator/processor')as [processor]
00017:     'from dbo.MobileOperator
00018:     '
00019:     'эти поля напрямую переносим в описание порядка платежа Киберплата
00020:     '
00021:     Public ReadOnly ID As Integer
00022:     Public ReadOnly GroupID As Integer
00023:     Public ReadOnly CyberID As Integer
00024:     Public ReadOnly Name As String
00025:     Public ReadOnly Name_for_cheque As String
00026:     Public ReadOnly Inn_for_cheque As String
00027:     Public ReadOnly Comission As String
00028:     Public ReadOnly MinSumm As Decimal
00029:     Public ReadOnly MaxSumm As Decimal
00030:     Public PaySumm As Decimal 'сумма платежа
00031:     Public ReadOnly Image As String
00032:     Public ReadOnly RootMenuImage As String
00033:     Public ReadOnly Fields_RAW As New System.Xml.XmlDocument     
00034:     Public ReadOnly Processor_RAW As New System.Xml.XmlDocument  
00035: 
00036:     Public Sub New(ByVal OperatorID As Integer)
00037:         Dim ReadOneDescriptionsCMD As VBNET2000.ExecSQL_RDR = New VBNET2000.ExecSQL_RDR("@id", OperatorID)
00038:         Dim RDR1 As Data.SqlClient.SqlDataReader = ReadOneDescriptionsCMD.ExecSQL("select * from GISIS.dbo.GetOperator where [ID]=" & OperatorID, Data.CommandType.Text, Data.CommandBehavior.SingleRow)
00039:         If RDR1.Read Then
00040:             ID = IIf(IsDBNull(RDR1("ID")), 0, RDR1("ID"))
00041:             GroupID = IIf(IsDBNull(RDR1("GroupID")), 0, RDR1("GroupID"))
00042:             CyberID = IIf(IsDBNull(RDR1("CyberID")), 0, RDR1("CyberID"))
00043:             Name = IIf(IsDBNull(RDR1("Name")), "", RDR1("Name"))
00044:             Name_for_cheque = IIf(IsDBNull(RDR1("Name_for_cheque")), "", RDR1("Name_for_cheque"))
00045:             Inn_for_cheque = IIf(IsDBNull(RDR1("Inn_for_cheque")), "", RDR1("Inn_for_cheque"))
00046:             Comission = IIf(IsDBNull(RDR1("Comission")), "", RDR1("Comission"))
00047:             MinSumm = IIf(IsDBNull(RDR1("min")), "", RDR1("min"))
00048:             MaxSumm = IIf(IsDBNull(RDR1("max")), "", RDR1("max"))
00049:             Image = IIf(IsDBNull(RDR1("Image")), "", RDR1("Image"))
00050:             RootMenuImage = IIf(IsDBNull(RDR1("RootMenuImage")), "", RDR1("ID"))
00051:             Fields_RAW.LoadXml(IIf(IsDBNull(RDR1("Fields")), "", RDR1("Fields")))
00052:             Processor_RAW.LoadXml(IIf(IsDBNull(RDR1("Processor")), "", RDR1("Processor")))
00053:         End If
00054:         ReadOneDescriptionsCMD.Close()
00055:     End Sub

Теперь нам нужен будет еще метод, который глубоко распрсит XML - cодержащий определение требуемых полей платежа. Тех, что должен ввести пользователь нашего сайта.

00057:     Public Function LoadFieldsDefinition() As Collections.Generic.List(Of OneCyberField)
00058:         Dim AllFields As System.Xml.XmlNodeList = Fields_RAW.SelectNodes("/fields/field")
00059:         For Each OneField As System.Xml.XmlNode In AllFields
00060:             Dim FieldType As String = OneField.Attributes("type").InnerText
00061:             Dim CurrentNode As CyberFieldsType
00062:             Select Case FieldType
00063:                 Case "text" : CurrentNode = CyberFieldsType._Text_
00064:                     Dim ID As Integer
00065:                     Dim Name As String
00066:                     Dim Klava As String
00067:                     Dim Comment As String
00068:                     If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0
00069:                     If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = ""
00070:                     If OneField.Attributes("klava") IsNot Nothing Then Klava = OneField.Attributes("klava").InnerText Else Klava = ""
00071:                     If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = ""
00072:                     Dim OneTextFiekds As New CyberText(ID, Name, Klava, Comment)
00073:                     Fields.Add(New OneCyberField(CurrentNode, OneTextFiekds))
00074:                 Case "integer" : CurrentNode = CyberFieldsType._Int_
00075:                     Dim ID As Integer
00076:                     Dim Name As String
00077:                     Dim MinLength As Integer
00078:                     Dim MaxLength As Integer
00079:                     Dim Comment As String
00080:                     If OneField.Attributes("id").InnerText IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0
00081:                     If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = ""
00082:                     If OneField.Attributes("maxlength") IsNot Nothing Then MaxLength = OneField.Attributes("maxlength").InnerText Else MaxLength = 0
00083:                     If OneField.Attributes("minlength") IsNot Nothing Then MinLength = OneField.Attributes("minlength").InnerText Else MinLength = 0
00084:                     If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = ""
00085:                     Dim OneIntFields As New CyberInt(ID, Name, MinLength, MaxLength, Comment)
00086:                     Fields.Add(New OneCyberField(CurrentNode, OneIntFields))
00087:                 Case "masked" : CurrentNode = CyberFieldsType._Masked_
00088:                     Dim ID As Integer
00089:                     Dim Name As String
00090:                     Dim Mask As String
00091:                     Dim Comment As String
00092:                     If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0
00093:                     If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = ""
00094:                     If OneField.SelectSingleNode("mask") IsNot Nothing Then Mask = OneField.SelectSingleNode("mask").InnerText Else Mask = ""
00095:                     If OneField.SelectSingleNode("comment") IsNot Nothing Then Comment = OneField.SelectSingleNode("comment").InnerText Else Comment = ""
00096:                     Dim OneMaskedField As New CyberMasked(ID, Name, Mask, Comment)
00097:                     Fields.Add(New OneCyberField(CurrentNode, OneMaskedField))
00098:                 Case "enum" : CurrentNode = CyberFieldsType._Enum_
00099:                     Dim ID As Integer
00100:                     Dim Name As String
00101:                     If OneField.Attributes("id") IsNot Nothing Then ID = OneField.Attributes("id").InnerText Else ID = 0
00102:                     If OneField.SelectSingleNode("name") IsNot Nothing Then Name = OneField.SelectSingleNode("name").InnerText Else Name = ""
00103:                     Dim OneEnumFields As New CyberEnum(ID, Name)
00104:                     Dim EnumNodes As System.Xml.XmlNodeList = OneField.SelectNodes("enum/item")
00105:                     For Each OneEnum As System.Xml.XmlNode In EnumNodes
00106:                         Dim Text As String = OneEnum.InnerText
00107:                         Dim Value As String = OneEnum.Attributes("value").Value.ToString
00108:                         Dim OneCyberEnumItem As New CyberEnumItem(Text, Value)
00109:                         OneEnumFields.EnumFields.Add(OneCyberEnumItem)
00110:                     Next
00111:                     Fields.Add(New OneCyberField(CurrentNode, OneEnumFields))
00112:             End Select
00113:         Next
00114:         Return Fields
00115:     End Function

Вот здесь-то весь ключик этого класса. Этот метод возвращает КОЛЛЕКЦИЮ ТЕХ САМЫХ АТОМАРНЫХ КЛАССОВ, которые мы определили как первичные элементарные хранители информации в полях ENUM-TEXT-MASKED-INT. А поскольку там был определен ЕДИНЫЙ INterface для каждого из четырех типов полей - здесь у нас получится определить GENERIC-коллекцию:

00117:     ''' <summary>
00118:     ''' Это собственно распарсенное поле параметров платежей в адрес одного оператора КиберПлата
00119:     ''' </summary>
00120:     Public ReadOnly Fields As New Collections.Generic.List(Of OneCyberField)

Далее нам потребуется хранитель итератора по элементам этой коллекции

00121:     ''' <summary>
00122:     ''' Количество уже обработанных полей в коллекции Fields. В обработанных полях содержатся введенные значения
00123:     ''' </summary>
00124:     Public ReadOnly Property EnterFieldsCount() As Integer
00125:         Get
00126:             Return EnterFieldsCount_
00127:         End Get
00128:     End Property
00129:     Dim EnterFieldsCount_ As Integer
00130:     ''' <summary>
00131:     ''' переход к заполнению следующего поля
00132:     ''' </summary>
00133:     Public Function NextFields() As Boolean
00134:         If EnterFieldsCount < Fields.Count - 1 Then
00135:             EnterFieldsCount_ += 1
00136:             Return True
00137:         Else
00138:             Return False
00139:         End If
00140: 
00141:     End Function
00142: 

И наконец функция, которая выполнит сериализацию хранимых в полях CyberField данныъ по правилам, которые мы вычитали конструктором данного класса при создания определения платежа по данному оператору.

00155:     ''' <summary>
00156:     ''' Функция упаковывающая параметры платежа в формат необходимый процедуре
00157:     ''' </summary>
00158:     Public Function PackParam() As String
00159:         Dim Result As New System.Text.StringBuilder
00160:         Dim AllProperty As System.Xml.XmlNodeList = Processor_RAW.SelectNodes("/processor/payment/request-property")
00161:         'каждый узел здесь может либо сразу ссылатся на Field либо представлять собой сборное поле из непосредственных и ссылочных полей
00162:         For Each OneProperty As System.Xml.XmlNode In AllProperty
00163:             Dim PropertyName As String = OneProperty.Attributes("name").InnerText
00164:             Dim ImediateRefAttr As System.Xml.XmlAttribute = OneProperty.Attributes("field-id")
00165:             If ImediateRefAttr IsNot Nothing Then
00166:                 'это поле включаем в результат сразу - только найти надо источник его данных
00167:                 Result.Append(PropertyName & "=")
00168:                 For Each OneFields As OneCyberField In Fields
00169:                     If OneFields.FieldDefinition.ID = CInt(ImediateRefAttr.InnerText) Then
00170:                         Result.Append(OneFields.FieldDefinition.UserValue)
00171:                         GoTo OK1
00172:                     End If
00173:                 Next
00174:                 Throw New Exception("Ошибка. Полю " & PropertyName & " не сопоставлено определения (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.")
00175: OK1:
00176:             Else
00177:                 'в таком поле могут быть непосредственные и референсные поля
00178:                 Result.Append(PropertyName & "=")
00179:                 For Each OneFixed As System.Xml.XmlNode In OneProperty.ChildNodes
00180:                     Dim FixedType As String = OneFixed.Name
00181:                     If FixedType = "field-ref" Then
00182:                         Dim FixedImediateRef As String = OneFixed.Attributes("id").InnerText
00183:                         If FixedImediateRef = "" Then
00184:                             Throw New Exception("Ошибка. Поле " & PropertyName & " не содержит ID ссылки на поле (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.")
00185:                         Else
00186:                             'это поле можно включить в результат - только найти надо источник его данных
00187:                             For Each OneFields As OneCyberField In Fields
00188:                                 If OneFields.FieldDefinition.ID = CInt(FixedImediateRef) Then
00189:                                     Result.Append(OneFields.FieldDefinition.UserValue)
00190:                                     GoTo OK2
00191:                                 End If
00192:                             Next
00193:                             Throw New Exception("Ошибка. Полю " & PropertyName & " не сопоставлено определения (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.")
00194: OK2:                        '
00195:                         End If
00196:                     ElseIf FixedType = "fixed-text" Then
00197:                         'а содержимое такого поля включаем сразу непосредственно из его определения
00198:                         Dim FixedText As String = OneFixed.Attributes("id").InnerText
00199:                         If FixedText = "" Then
00200:                             Throw New Exception("Ошибка. Поле " & PropertyName & " содержит пустое значение в качестве непосредственно включаемого параметра (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.")
00201:                         Else
00202:                             Result.Append(FixedText)
00203:                         End If
00204:                     Else
00205:                         Throw New Exception("Ошибка. Поле " & PropertyName & " содержит необрабатываемый тип параметра (в описании операторов киберплата). ID=" & ID & ", CyberID=" & CyberID & vbCrLf & "Формирование платежа невозможно.")
00206:                     End If
00207:                 Next
00208:             End If
00209:             Result.Append("\n")
00210:         Next
00211:         Result.Append("\n")
00212:         Return Result.ToString
00213:     End Function
00214: 
00215: End Class
00216: 

Ну и небольшая отладочная функция, которая позволит нам снаружи проконтролировать - что же там происходит в недрах этого класса.

00143:     ''' <summary>
00144:     ''' Выполнение платежа
00145:     ''' </summary>
00146:     Public Function TestPay() As String
00147:         Dim PayParm As New StringBuilder("(")
00148:         For Each One As OneCyberField In Fields
00149:             PayParm.Append("FieldType='" & One.FieldType.ToString & "': Name='" & One.FieldDefinition.Name & "', Value='" & One.FieldDefinition.UserValue & "'; ")
00150:         Next
00151:         PayParm.Append(")")
00152:         Return ("Платеж " & PaySumm.ToString & " руб, OperatorID=" & ID & ", Name='" & Name & "', Fields.Count=" & Fields.Count & ", Введено полей='" & EnterFieldsCount & "', Parm=" & PayParm.ToString)
00153:     End Function
00154: 

Как видите - в чем тут ключик? В создании ЕДИНОГО ИНТЕРФЕЙСА для любого из четырех типов полей (строки 230-235). С одной стороны это позволяет нам определять КОЛЛЕКЦИЮ (типа GENERIC) на полях соверщенно разных типов (строка 120). А далее двигатся по элементам этой коллекции единым образом (этот фрагмент перехода по требуемым полями платежа - элементам нашей GENERIC-коллекции - вы можете рассмотреть на рисунке ниже). Все определения каждого типа являюся реализацией этого интрерфейса (строки 264-482). Такое решение дает нам ПОДСКАЗКУ на страничках и единый интерфейс для обращения к нашему классу со страниц - в котором как раз и видны наши общие, определенные в интерфейсе методы:





Generic




И теперь мы рассмотрим второй весьма поучительный пример применения дженериков. Пример, в котором узнаем что такое КОНТРЕЙНТСЫ, и как их находить. В этом примере мы не будем создавать несколько GENERIC-обьектов от своего интерфейса (как в предыдущем примере - где мы потом из этих обьектов собрали коллекцию) - а мы попробуем использовать УЖЕ написанные микрософтавскими индусами классы.

Эта задача тоже чисто практическая. Существует масса форм (и у меня таких форм горы в каждом проекте) - где надо ОДНИ И ТЕ ЖЕ ДАННЫЕ вывести в различном виде на форму. В чуть-чуть различном. Неуловимо различном. Настолько, что писать две отдельные проги было бы полнейшим идиотизмом. например некую таблу с данными мы ИНОГДА выводим в GridView, а иногда в DataList.

Отличия этих двух компонентов - в ASP коде просто фантастические - например GridView выводит все только в один столбец, а DataList выводит в несколько столбцов. Как правило - всем-всем-всем нужно, чтобы например как только юзер залогинился - появился новый функционал - его много - и он еле-еле вообще в сроку влазит, как только юзер разлогинился - куча функционала пропадает - и элементы на форме пресуются по несколько штук в строку. Это один пример. Можно десятки подобных примеров привести, когда одни и те же данные надо запихать в разные контролы в зависимости от динамически возникающих условий.

Идиотизм ситуации однако состоит в том, что и DataList и GridView совершенно одинаково предствлены в итоговом HTML-коде. Здесь мы в сущности упираемся вообще в идиотизм настолько строгой типизации (и вообще в Недостатки обьектно-ориентированного программирования в WEB.). Но деватся некуда - раз MS лишила нас препроцессора и преварительного прохода макропроцессора выхода другого нет никакого в рамках MS-идеологии. И это решение прекрасно демонстрирует применение дженериков и интерфейсов.


Итак, есть на одной и той же форме есть Multiview - в одной части которого лежит Gridview, в другой DataList.

Есть несколько запросов на форме - GetUserDataStrip (тяжелый), GetLinkedStory (не очень тяжелый, но это распределенная транзакция на совершенно другой сервер) и еще есть базовая страничка VotpuskBasePage. Базовая страничка, как вы видите, тоже нехилой сложности - содержит в себе кучу криптографии, ссылку на юзера, парссеры URL, сслылки на профили. Все это много раз понадобится на каждой страничке и по идее до начала реквеста должно быть подготовлено. В сущности - это небольшой личный фреймворк - в рамках которого идет каждый реквест этого проекта.

Кроме того, есть темплейты - те шаблоны DataList и GridView. И это все, что у нас есть в постановке задачи.

Наша задача - разобраться, как все это ухватить в одно место и добится, чтобы требуемые данные легли на формы - в эти такие разные GridView и DataList (один из которых даже имеет встроенный микрософтовский язва-скрипт пейджер, а во втором работает мой собственный пейджер). Иными словами - как нам сделать такую прогу, которую можно было бы вызвать, выдав и GridView1.DataBind() и DataList1.DataBind(). Что у нас дано - я перечислял - пара рекордсетов, возвращенных из SQL, базовая страничка и шаблоны DataList и GridView.


Решение состоит в том, чтобы создать такой класс, который в сущности примет на вход и DataList и Gridview, а также результаты наши тяжелых запросов в SQL и результат отработки базовой странички - которая дает нам весь рабочий фреймворк - криптографию, профили и так далее. Именно такой класс и будет нашим GENERIC-классом:





Это ключевой момент. Видите, как я передал ссылку на свой фреймворк - это ME, а сам итоговый экземпляр класса будет создан либо на элементе данных (и шаблоне) Of Web.UI.WebControls.GridViewRow, либо на элементе данных (и шаблоне) Web.UI.WebControls.DataListItem. Оба этих класса являются различной реализацией интерфейса System.Web.UI.IDataItemContainer. Это важнейшая концепция - здесь System.Web.UI.IDataItemContainer является той самой точкой пересечения, или контрейнтсом - через которые связаны элементы данных и шаблоны внутри Gridview с одной строны и DataList с другой стороны. Кстати, реализацией этого же интерфейса является тот самый DATABINDER.EVAL - который мы так часто пишем в декларативной части форм, а также режеприменяемые FormView/DetailsView.

Теперь вы должны уловить связь с предыдущим проектом, где я сам определил такой же интерфейс CyberCommonFields и реализовал его четырьмя способами - CyberInt, CyberMasked, CyberText, CyberEnum (ну а уже потом собрал из них коллекцию - которую заполнил вручную путем распарсивания XML-определения параметров платежа). Здесь ровно то же самое, только и определение интерфейсов и их реализацию сделали индусы, нанятые микрасофтом. Но в том, проекте нам надо было создать GENIRIC-класс из этих (таких разных) обьектов, который можно было бы собрать в коллекцию и получить подсказку на форме и строгую типизацию, а в этом проекте нам надо просто воспользоваться уже сделанными индусами кирпичиками и заполнить сетку (для таких различных обьектов).

А значит, начало нашей проги будет выглядеть вот так:

00156:     Protected Class MyBinding(Of TypeOfBindingControl As {System.Web.UI.Control, System.Web.UI.IDataItemContainer})
00157:         ''' <summary>
00158:         ''' Привязка шаблона универсальная - для любого типа сетки
00159:         ''' SQLdatasource надо в этот класс передавать извне, тк они в этом динамическом классе статические со странички
00160:         ''' </summary>
00161:         Public Sub CreateOneItem(ByRef OneItem As TypeOfBindingControl, ByVal PP8 As VBNET2000.ParmProtector8, _
00162:                ByVal GetUserDataStrip As SqlDataSource, ByVal GetLinkedStory As SqlDataSource, ByVal RefToPage As VotpuskBasePage)
00163: 
00164:             If OneItem.DataItem IsNot Nothing Then
00165:                 'взяли данные из текущего элемента данных
00166:                 Dim GroupName As String
00167:                 If IsDBNull(OneItem.DataItem("GroupName")) Then
00168:                    ....

А дальше мы вытаскиваем из нашего OneItem (который является в некоторых вызовах фактически Web.UI.WebControls.GridViewRow, а иногда Web.UI.WebControls.DataListItem) - вытаскивается и заполяется-то он одинаково! И пару тысяч примерно вот таких строк - для заполнения (причем совершенно одинакового!) элементов шаблона сеток - данными

00814: 
00815: 
00816: 
00817:                 Dim LinkToSlideGroup As ImageButton = CType(OneItem.FindControl("LinkToSlideGroup"), System.Web.UI.WebControls.ImageButton)
00818:                 If LinkToSlideGroup IsNot Nothing Then
00819:                     LinkToSlideGroup.PostBackUrl = "~/user_foto_slide.aspx?i=" & HttpContext.Current.Request.QueryString("i") & "&g=" & CryptoGroupNumber
00820:                 End If

Думаю, эта страничка не одному заинтересованному человеку поможет. Остается только добавить, что есть много-много альтернативных путей для решения этой же задачи. Например мне более всего импонирует макропроцессор, который был у меня при программировании на макроассемблере. Но... он запредельно прост cам и код, им произведенный, выходит совершенно открытым - а это не соответствует целям, которые ставит MS, паразитируя в информационной индустрии. Вот и получается, что даже просто заполнить одну и ту же сетку сетку в тегах <tr> <td> - нужно досконально разбиратся в описанной на этой страничке концепции интерфейсов и дженериков. И первый описанный на этой страничке проект тоже был бы невозможным без описанного тут класса на основе дженериков - а в нем таких замороченных классов с дженериками из сотни - около десятка. Так что без этой технологии - никуда.


Polimorfism




Я решил добавить на эту страничку еще пару примеров применения специализированных техник обьектного программирования, чтобы этот обзор получился более интересным. Во первых, я с момента создания пользуюсь ad-hoc полиморфизмом. Это такие функции, которые могут принять различное число параметров. Эта функция у меня существует для MySQL - ADO.NET обвязка для работы с MySQL в ASP.NET-сайтах под Windows.




Lля знакомства c тестом этой полиморфной функции для MS SQL отсылаю вас к своей заметке 2006-го года Асинхронные странички. Большинство людей написало бы эту фукнцию с помощью ParamArray, но не я. Посмотрите трассировку IL - и вы поймете, почему более длинный код в этом случае оправдан.

Аналогичный подход у меня использован в моем сервисе WCF_CLIENT - клиент Web-сервиса.


Для меня было удивительным, что полиморфищм активно используется в языке SQL. Пример полиморфной функции GetDisk для PostgreSQL у меня описан на этой страничке - Фрагмент реальной BLL на PostgeSQL.

Inherits




И вот еще один примерчик наследования - гораздо более поздний, чем написана вся эта страничка. В 2010-м году я заинтересовался использованием PostgreSQL и решил опубликовать несколько примеров работы с Postgres. Первый же опубликованный мною примерчик BLL получился довольно поучительным в плане использования специфической техники объектного программирования - наследования.

Классы наследуются от базового класса и добавляют новые методы и свойства к определениям базового класса:



Еще один пример наследования вы можете посмотреть в опубликованной мною проге ComDetector - утилита поиска COM-оборудования - там класс работы с принтером VKP80 добавляет свои специфические методы и свойства к общему классу работы COM-портом.

Недавно я нашел на своем сайте еще один поучительный примерчик использования наследования (а заодно и событий на нестандартных делегатах) - Шлюз к 1С по протоколу Битрикс.

Delegate




И последняя специфическая техника обьектного программирования, на которой мы остановимся на этой страничке - это делегаты. Делегат - это callback, те программка обратного вызова, которая в некотором библиотечном коде лишь декларируется. Потом при вызове библиотечной функции пользователь подсовывает адрес своего калбека, и библиотечная функция вызывает калбек пользователя например в своем внутреннем цикле или при некоторых возникающих ситуациях.

У меня на сайте есть несколько примеров использования делегатов:


На этой же страничке с описанием вариантов встраивания общего кода в ASP.NET сайты - вы найдете также пример использования ключевых слов Overridable / Overrides.



Комментарии к этой страничке ( )
ссылка на эту страничку: http://www.vb-net.ru/asp2/40/index.htm
<На главную>  <В раздел ASP>  <В раздел NET>  <В раздел SQL>  <В раздел Разное>  <Написать автору>  < Поблагодарить>
Московская хельсинская группа   Радио Свобода  Новая газета   The New Times (Новое время)