Это фреймворк MVC, полностью написанный на VBScript. Думайте об этом как о Spring Boot для Classic ASP.
Sane — это относительно полнофункциональная среда MVC, которая привносит в Classic ASP разумность. По стилю он имеет некоторое сходство как с .NET MVC, так и с Rails, но не совсем похож ни на один из них. Это мнение заключается в том, что предполагается, что контроллеры будут существовать в определенной папке, но это место в некоторой степени настраивается.
Основные особенности, отличающие эту структуру, включают в себя:
All/Any
, Min/Max/Sum
, Map/Select
для проекций и Where
для фильтров, с поддержкой базовых лямбда-выражений.Up
и Down
— изменения в базе данных с контролем версийScripting.Dictionary
KVArray
и его вспомогательные методы, чтобы упростить создание HTML.Все это было написано на VBScript. Действительно.
Sane распространяется по лицензии GPLv3.
Примечание. Эта структура была извлечена из реального проекта маршрутизации внутреннего рабочего процесса, поэтому в ней есть несколько неровностей.
Это дает краткий обзор потока кода для одного контроллера, а также моделей и представлений, которые он использует. Он показывает, какая часть перечисленных выше функций фактически используется вместе.
«Таких много, но этот мой».
Главным образом потому, что это был интересный проект, расширяющий возможности Classic ASP. Подавляющее большинство разработчиков ненавидят VBScript и Classic ASP, и в основном на это есть веские причины. Многие проблемы Classic ASP проистекают из ограничений времени его разработки, середины 1990-х годов. Разработчики не смогли использовать то, что сегодня считается фундаментальными практиками (широко использовать классы и т. д.), потому что язык не был предназначен для выполнения в режиме, который мы бы назвали «быстрым», и использование этих методов могло привести к зависанию приложения и сбою. Из-за этого сообщество ASP было вынуждено использовать ASP так же, как использовался PHP — как встроенный обработчик шаблонов на основе страниц, а не как полноценную платформу приложений как таковую. Плюс, давайте будем честными, Microsoft рекламировала ASP для всех, независимо от уровня навыков, и большинство учебных пособий, найденных в Интернете, были ужасными и поощряли ужасно плохие практики.
Сегодня мы знаем лучше, и благодаря закону Мура вычислительная мощность выросла примерно в 500 раз с середины 90-х годов, поэтому мы можем позволить себе делать вещи, которые были немыслимы несколько лет назад.
Эта структура была извлечена из реального проекта, построенного таким образом. Он работал достаточно хорошо, и не должно быть причин (с функциональной точки зрения), что он не сможет работать как жизнеспособная платформа приложений. Тем не менее, на самом деле, если нам нужно разработать приложение сегодня, мы будем использовать современную среду, такую как .NET MVC или один из ее конкурентов, так что это на самом деле здесь, на случай, если это будет полезно кому-то еще. Плюс было весело строить. :)
Зависимость: Демо было создано на основе образца базы данных Microsoft Northwind. Загрузите BAK-файл SQL Server здесь и восстановите его в экземпляр SQL Server. Также доступны сценарии SQL и файл MDF.
File -> Open Web Site...
и выберите каталог Demo
.AppDALlib.DAL.asp
и измените строку подключения, чтобы она указывала на вашу базу данных./index.asp
Файл index.asp
автоматически перенаправит вас на контроллер Home /App/Controllers/HomeController.asp
и загрузит действие по умолчанию Index
.
Некоторые из приведенных ниже функций имеют соответствующие тесты ASPUnit. Найдите их в каталоге Tests.
<%
Class OrdersController
Public Model
Public Sub Show
MVC.RequirePost
dim id : id = Request ( " Id " )
set Model = new Show_ViewModel_Class
set Model.Order = OrderRepository.FindById(id)
%> <!-- #include file="../../Views/Orders/Index.asp" --> <%
End Sub
End Class
MVC.Dispatch
%>
Контроллер знает только о домене, а не о базе данных. Радость!
Действия — это функции без параметров (например, Rails и в отличие от .NET MVC) — параметры извлекаются из объекта Request
, как в традиционном ASP. MVC.RequirePost
позволяет вам ограничить действие ответом только на запросы POST
— в противном случае возникнут ошибки.
MVC.Dispatch
— это волшебная точка входа в структуру. Поскольку представления #include
d входят в контроллер и поскольку приложение может иметь 1..n
контроллеров, каждый из которых имеет 1..n
действий, наличие центрального монолитного диспетчера MVC невозможно, кроме нескольких простых контроллеров. Это связано с тем, что ASP загружает и компилирует весь файл #include
d для каждого просмотра страницы. Учитывая это, фреймворк вместо этого делегирует создание экземпляров самим контроллерам, возлагая на них ответственность за запуск фреймворка вместо того, чтобы возлагать на фреймворк ответственность за загрузку и создание экземпляров всех контроллеров, а также загрузку и компиляцию всех представлений для каждого запроса. Платформа динамически создает экземпляр контроллера на основе соглашений об именах, но для каждого запроса анализируется и загружается только один контроллер, а не все контроллеры. Загрузив только один контроллер, мы можем использовать сэкономленные средства для загрузки множества полезных библиотек, которые делают разработку более удобной для разработчиков.
Из-за такого подхода «механизм маршрутизации» на самом деле представляет собой просто класс, который знает, как создавать URL-адреса для файлов контроллера, поэтому Routes.UrlTo("Orders", "Show", Array("Id", order.Id))
генерирует URL-адрес /App/Controllers/OrdersController.asp?_A=Show&Id=123
(для order.Id = 123). URL-адреса указывают на контроллеры и предоставляют имя действия, которое должно быть выполнено, через параметр _A
. Параметры действий передаются через структуру данных KVArray
, которая представляет собой просто массив пар ключ/значение, широко используемый во всей платформе. Например, вот два KVArray
, используемые в одном из многих помощников HTML:
<%= HTML.LinkToExt( " View Orders " , _
" Orders " , _
" List " , _
array ( " param1 " , " value1 " , " param2 " , " value2 " ), _
array ( " class " , " btn btn-primary " , " id " , " orders-button " )) %>
За кулисами этот метод создает привязку, которая направляет к правильной комбинации контроллер/действие, передает указанные параметры через строку запроса и имеет указанные атрибуты HTML- class
и id
. KVArray
легко обрабатывается благодаря нескольким вспомогательным методам, таким как KeyVal
и KVUnzip
.
Структура данных KVArray
является фундаментальной для большей части платформы и значительно упрощает кодирование. По сути, KVArray
— это не что иное, как стандартный массив VBScript, который всегда следует использовать группами по два. Другими словами, чтобы построить KVArray
нам просто нужно создать массив, в котором элемент 0 является первым ключом, элемент 1 — его значением, элемент 2 — вторым ключом, элемент 3 — его значением и т. д.
По сути, вы можете представить KVArray
как способ использования вызовов стиля System.Object
, как это сделано в .NET Html.ActionLink
.
Например:
dim kvarray : kvarray = Array( 6 )
'Element 1: Name = Bob
kvarray( 0 ) = "Name"
kvarray( 1 ) = "Bob"
'Element 2: Age = 35
kvarray( 2 ) = "Age"
kvarray( 3 ) = 35
'Element 3: FavoriteColor = Blue
kvarray( 4 ) = "FavoriteColor"
kvarray( 5 ) = "Blue"
Но на самом деле вы бы никогда не написали это так, вместо этого вы бы использовали встроенный конструктор Array
следующим образом:
dim params : params = Array( "Name" , "Bob" , "Age" , 35 , "FavoriteColor" , "Blue" )
Или для большей читабельности:
dim params : params = Array( _
"Name" , "Bob" , _
"Age" , 35 , _
"FavoriteColor" , "Blue" _
)
Чтобы перебрать этот массив шаг на 2 и использовать KeyVal
для получения текущего ключа и значения:
dim idx, the_key, the_val
For idx = 0 to UBound(kvarray) step 2
KeyVal kvarray, idx, the_key, the_val
Next
На каждой итерации the_key
будет содержать текущий ключ (например, «Name», «Age» или «FavoriteColor»), а the_val
будет содержать соответствующее значение ключа.
Но почему бы не использовать словарь?
Словари — это здорово, но они являются COM-компонентами, и их создание, по крайней мере, исторически было дорогостоящим, и из-за многопоточности их не следует размещать в сеансе. С ними также сложно работать в сценариях использования в этой среде, и не существует простого способа создать их встроенные экземпляры с динамическим количеством параметров.
На самом деле нам нужна быстрая структура данных «ключ-значение», которая позволяет нам перебирать значения и извлекать каждый ключ и значение для создания чего-то вроде where
HTML с произвольными атрибутами или предложения SQL с произвольными столбцами, а не быстрый поиск отдельных ключей. Поэтому нам нужен гибрид массива и словаря, который отвечает нашим конкретным потребностям и позволяет встроенное объявление произвольного количества параметров. KVArray
позволяет нам очень естественно писать код, как в примере LinkToExt
выше, или вручную создавать URL-адреса с помощью Routes.UrlTo()
:
<%
< a href = " <%= Routes.UrlTo( " Users " , " Edit " , array( " Id " , user.Id)) %> " >
< i class = " glyphicon glyphicon-user " >< /a >
< /a >
%>
Мы также можем создать общие методы Find
репозитория, которые можно использовать следующим образом:
set expensive_products_starting_with_C = ProductRepository.Find( _
array( "name like ?" , "C%" , _
"price > ?" , expensive_price _
) _
)
set cheap_products_ending_with_Z = ProductRepository.Find( _
array( "name like ?" , "%Z" , _
"price < ?" , cheap_price _
) _
)
Примеры этого есть в демо-репозиториях, где KVUnzip
также очень эффективно используется для облегчения создания предложения sql where
SQL. Приведенный ниже пример взят из метода ProductRepository.Find()
который принимает KVArray
содержащий пары ключ-значение предиката, и разархивирует его в два отдельных массива, которые используются для построения запроса:
If Not IsEmpty(where_kvarray) then
sql = sql & " WHERE "
dim where_keys, where_values
KVUnzip where_kvarray, where_keys, where_values
dim i
For i = 0 to UBound(where_keys)
If i > 0 then sql = sql & " AND "
sql = sql & " " & where_keys(i) & " "
Next
End If
...
dim rs : set rs = DAL.Query(sql, where_values)
set Find = ProductList(rs)
<%
Class OrderModel_Class
Public Validator
Public OrderNumber, DateOrdered, CustomerName, LineItems
Public Property Get SaleTotal
SaleTotal = Enumerable(LineItems).Sum( " item_.Subtotal " ) ' whaaaa?
End Property
Public Sub Class_Initialize
ValidatePattern Me, OrderNumber, " ^d{9}[d|X]$ " , " Order number format is incorrect. "
ValidateExists Me, DateOrdered, " DateOrdered cannot be blank. "
ValidateExists Me, CustomerName, " Customer name cannot be blank. "
End Sub
End Class
Class OrderLineItemModel_Class
Public ProductName, Price, Quantity, Subtotal
End Class
%>
Проверьте модели, вызвав соответствующий вспомогательный метод Validate*
из конструктора Class_Initialize
модели:
Private Sub Class_Initialize
ValidateExists Me , "Name" , "Name must exist."
ValidateMaxLength Me , "Name" , 10 , "Name cannot be more than 10 characters long."
ValidateMinLength Me , "Name" , 2 , "Name cannot be less than 2 characters long."
ValidateNumeric Me , "Quantity" , "Quantity must be numeric."
ValidatePattern Me , "Email" , "[w-]+@([w-]+.)+[w-]+" , "E-mail format is invalid."
End Sub
В настоящее время включены только ValidateExists
, ValidateMinLength
, ValidateMaxLength
, ValidateNumeric
и ValidatePattern
. На самом деле эти вспомогательные методы создают новый экземпляр соответствующего класса проверки и присоединяют его к свойству Validator
модели. Например, когда модель объявляет проверку с помощью ValidateExists Me, "Name", "Name must exist."
вот что на самом деле происходит за кулисами:
Sub ValidateExists(instance, field_name, message)
if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class
instance.Validator.AddValidation new ExistsValidation_Class.Initialize(instance, field_name, message)
End Sub
Здесь Me
— экземпляр модели предметной области. Затем Validator_Class
используется (через YourModel.Validator
) для проверки всех зарегистрированных правил проверки, устанавливая поля Errors
и HasErrors
, если обнаружены ошибки. Это похоже на шаблон Observer. Причина, по которой мы передаем Me
заключается в том, что это позволяет нам иметь удобно сформулированный метод для каждой проверки, имеющий сильное семантическое значение, например ValidateExists
. Это требует немного код-дзюцу, но оно того стоит.
Добавить новые проверки легко, просто добавьте новый класс проверки и вспомогательный Sub
. Например, чтобы добавить проверку, требующую, чтобы строка начиналась с буквы «A», вы должны создать StartsWithLetterAValidation_Class
и вспомогательный метод Sub ValidateStartsWithA(instance, field_name, message)
, а затем вызвать его через ValidateStartsWithA Me, "MyField", "Field must start with A."
Модели предметной области можно создавать путем преобразования набора записей ADO в связанный список моделей предметной области с помощью преобразований в стиле Automapper. Скажи Чтоооо?
Class OrderRepository_Class
Public Function GetAll()
dim sql : sql = "select OrderNumber, DateOrdered, CustomerName from Orders"
dim rs : set rs = DAL.Query(sql, empty) 'optional second parameter, can be scalar or array of binds
dim list : set list = new LinkedList_Class
Do until rs.EOF
list.Push Automapper.AutoMap(rs, new OrderModel_Class) ' keanuwhoa.jpg
rs.MoveNext
Loop
set GetAll = list
Destroy rs ' no passing around recordsets, no open connections to deal with
End Function
End Class
' Convenience wrapper lazy-loads the repository
dim OrderRepository__Singleton
Function OrderRepository()
If IsEmpty(OrderRepository__Singleton) then
set OrderRepository__Singleton = new OrderRepository_Class
End If
set OrderRepository = OrderRepository__Singleton
End Function
Использование empty
ключевого слова является распространенным подходом, используемым в этой платформе. Распространенной жалобой на VBScript является то, что он не допускает необязательных параметров. Хотя технически это верно, это легко обойти, однако практически каждый пример, найденный в Интернете, предполагает передачу пустых строк, нулевых значений или аналогичный подход. Использование встроенного ключевого слова VBScript empty
— это семантически значимый способ обработки необязательных параметров, давая понять, что мы специально намеревались игнорировать необязательный параметр. В этом случае метод DAL.Query
принимает два параметра: запрос SQL и необязательный второй параметр, содержащий значения привязки. Второй параметр может быть либо одним значением, как в DAL.Query("select a from b where a = ?", "foo")
либо массивом привязок, например DAL.Query("select a from b where a = ? and c = ?", Array("foo", "bar")
. В приведенном выше примере он явно игнорируется, поскольку в SQL нет переменных связывания.
В этом примере переменная DAL
— это просто экземпляр Database_Class
из lib.Data.asp
. В исходном проекте DAL представлял собой специальный класс, который выступал в качестве точки входа для набора экземпляров Database_Class
с отложенной загрузкой, позволяя совместно использовать данные и перемещать их между базами данных в ходе рабочего процесса.
Объект Automapper
— это класс VBScript, который пытается сопоставить каждое поле исходного объекта с соответствующим полем целевого объекта. Исходный объект может быть набором записей или пользовательским классом. Функция может сопоставляться с новым или существующим объектом. Объект Automapper
содержит три метода: AutoMap
, который пытается сопоставить все свойства; FlexMap
, который позволяет вам выбрать подмножество свойств для сопоставления, например Automapper.FlexMap(rs, new OrderModel_Class, array("DateOrdered", "CustomerName"))
скопирует только два указанных поля из исходного набора записей в новый экземпляр модели. ; и DynMap
, который позволяет динамически переназначать значения. Надуманный пример см.:
Automapper.DynMap(rs, new OrderModel_Class, _
array( "target.CustomerName = UCase(src.CustomerName)" , _
"target.LikedOrder = src.CustomerWasHappy" ))
Поскольку и источником, и целью может быть любой объект с методами экземпляра, это очень полезный способ управления привязкой модели в методах CRUD, например:
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
... etc
End Sub
Поскольку в действие контроллера добавлен #include
d, представление имеет полный доступ к экземпляру Model
контроллера. Здесь он обращается к свойству Order
модели представления и перебирает свойство LineItems
(которое будет экземпляром LinkedList_Class
построенным внутри репозитория) для построения представления. Используя модели представлений, вы можете создавать расширенные представления, не привязанные к определенной структуре набора записей. См. HomeController
в демо-версии примера модели представления, которая содержит четыре отдельных списка объектов домена для создания сводного представления информационной панели.
Метод MVC.RequireModel
предоставляет возможность строго типизировать представление, имитируя директиву @model
в .NET MVC.
<% MVC.RequireModel Model, " Show_ViewModel_Class " %>
< h2 >Order Summary</ h2 >
< div class = " row " >
< div class = " col-md-2 " >
Order # <%= Model.Order.OrderNumber %>
</ div >
< div class = " col-md-10 " >
Ordered on <%= Model.Order.DateOrdered %>
by <%= Model.Order.CustomerName %>
for <%= FormatCurrency (Model.Order.SaleTotal) %>
</ div >
</ div >
< table class = " table " >
< thead >
< tr >
< th >Product</ th >
< th >Price</ th >
< th >Qty</ th >
< th >Subtotal</ th >
</ tr >
<% dim it : set it = Model.Order.LineItems.Iterator %>
<% dim item %>
<% While it.HasNext %>
<% set item = it.GetNext() %>
< tr >
< td > <%= item .ProductName %> </ td >
< td > <%= item .Price %> </ td >
< td > <%= item .Quantity %> </ td >
< td > <%= item .Subtotal %> </ td >
</ tr >
<% Wend %>
</ thead >
</ table >
Предоставляет цепочки вызовов в стиле лямбда в списке. Из юнит-тестов:
Enumerable(list) _
.Where( "len(item_) > 5" ) _
.Map( "set V_ = new ChainedExample_Class : V_.Data = item_ : V_.Length = len(item_)" ) _
.Max( "item_.Length" )
V_
— это специальная переменная экземпляра, используемая методом Map
для представления результата лямбда-выражения. item_
— это еще одна специальная переменная экземпляра, которая представляет текущий обрабатываемый элемент. Итак, в этом случае Map
перебирает каждый элемент в списке и выполняет переданное «лямбда»-выражение. Результатом Map
является новый экземпляр EnumerableHelper_Class
, содержащий список экземпляров ChainedExample_Class
созданный с помощью выражения. Затем это перечисляемое обрабатывается Max
для возврата одного значения — максимальной длины.
Содержит сведения о соединении и доступе к базе данных. В дополнение к уже показанным примерам он также может обрабатывать:
DAL.Execute "delete from Orders where OrderId = ?", id
set rs = DAL.PagedQuery(sql, params, per_page, page_num)
DAL.BeginTransaction
, DAL.CommitTransaction
и DAL.RollbackTransaction
Класс также автоматически закрывает и уничтожает обернутое соединение с помощью метода Class_Terminate
, который вызывается, когда класс готов к уничтожению.
Class Migration_01_Create_Orders_Table
Public Migration
Public Sub Up
Migration.Do "create table Orders " & _
"(OrderNumber varchar(10) not null, DateOrdered datetime, CustomerName varchar(50))"
End Sub
Public Sub Down
Migration.Do "drop table Orders"
End Sub
End Class
Migrations.Add "Migration_01_Create_Orders_Table"
Миграцию можно выполнять пошагово через веб-интерфейс, расположенный по migrate.asp
. Migration.Do
выполняет команды SQL. Миграции обрабатываются в порядке загрузки. Для удобства заказа рекомендуется следовать структурированной схеме именования, показанной выше. Существует несколько специальных команд, таких как Migration.Irreversible
, которые позволяют остановить нисходящую миграцию и т. д.
Реальный проект, из которого был извлечен фреймворк, содержал примерно 3 десятка миграций, поэтому очень хорошо подходил для версионирования БД во время разработки.
Примечание. Веб-интерфейс миграции очень простой и некрасивый.
Зависимость: Чтобы использовать функцию миграции, необходимо сначала создать таблицу meta_migrations
с помощью сценария [ ! Create Migrations Table.sql
](Sane/Framework/Data/Migrations/! Создать таблицу миграций.sql).
Поскольку использование пошаговой отладки не всегда возможно в Classic ASP, это значительно упрощает отладку и отслеживание.
Dump
выводит объекты осмысленным образом:
dim a : a = GetSomeArray()
Dump a
Выход:
[Array:
0 => «elt1»
1 => «elt2»
2 => «elt3»
]
Он даже обрабатывает пользовательские классы, используя поле Class_Get_Properties
:
Dump Product
Выход:
{ProductModel_Class:
Id : Long => «17»,
Name : String => «Alice Mutton»,
CategoryId : Long => «6»,
Category : Empty => «»,
CategoryName : String => «Meat/Poultry»,
SupplierId : Long => «7»,
Supplier : Empty => «»,
SupplierName : String => «Pavlova, Ltd.»,
UnitPrice : Currency => «250»,
UnitsInStock : Integer => «23»,
UnitsOnOrder : Integer => «0»,
ReorderLevel : Integer => «0»,
Discontinued : Boolean => «True»
}
И он обрабатывает вложенность, как показано здесь, когда вызов Dump Model
был помещен в действие Show
OrdersController.asp
в демонстрации:
{OrderModel_Class:
Id : Long => « 11074 » ,
CustomerId : String => « SIMOB » ,
OrderDate : Date => « 5 / 6 / 1998 » ,
RequiredDate : Date => « 6 / 3 / 1998 » ,
ShippedDate : Null => «» ,
ShipName : String => « Simons bistro » ,
ShipAddress : String => « Vinbæltet 34 » ,
ShipCity : String => « Kobenhavn » ,
ShipCountry : String => « Denmark » ,
LineItems : LinkedList_Class =>
[ List:
1 =>
{OrderLineItemModel_Class:
ProductId : Long => « 16 » ,
ProductName : String => « Pavlova » ,
UnitPrice : Currency => « 17.45 » ,
Quantity : Integer => « 14 » ,
Discount : Single => « 0.05 » ,
ExtendedPrice : Currency => « 232.09 »
}
] }
quit
немедленно останавливает выполнение. die "some message"
останавливает выполнение и выводит на экран «какое-то сообщение». trace "text"
и comment "text"
пишут HTML-комментарии, содержащие «текст», что полезно для отслеживания «за кулисами», не нарушая макет.
Flash.Success = "Product updated."
, Flash.Errors = model.Validator.Errors
и т. д.
Если при создании модели возникают ошибки, мы должны иметь возможность повторно отобразить форму с заполненным пользовательским содержимым. Чтобы упростить это, инфраструктура предоставляет объект FormCache
, который сериализует/десериализует данные формы через сеанс.
Например, в действии Create
мы можем иметь:
Public Sub Create
dim form_params : set form_params = FormCache.DeserializeForm( "NewProduct" )
If Not form_params Is Nothing then
set Model = Automapper.AutoMap(form_params, new Create_ViewModel_Class)
Else
set Model = new Create_ViewModel_Class
End If
% > <!--#include file= "../../Views/Products/Create.asp" --> < %
End Sub
И в CreatePost
:
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
new_product_model.Validator.Validate
If new_product_model.Validator.HasErrors then
FormCache.SerializeForm "NewProduct" , Request.Form
Flash.Errors = new_product_model.Validator.Errors
MVC.RedirectToAction "Create"
Else
ProductRepository.AddNew new_product_model
FormCache.ClearForm "NewProduct"
Flash.Success = "Product added."
MVC.RedirectToAction "Index"
End If
End Sub
put
оборачивает Response.Write
и изменяет его вывод в зависимости от переданного типа, со специальным выводом для списков и массивов.H(string)
HTMLКодирует строкуAssign(target, src)
абстрагирует необходимость использования set
для объектов в тех случаях, когда мы имеем дело с переменными произвольного типа.Choice(condition, trueval, falseval)
— более функциональный iif
HTML.FormTag(controller_name, action_name, route_attribs, form_attribs)
HTML.TextBox(id, value)
HTML.TextArea(id, value, rows, cols)
HTML.DropDownList(id, selected_value, list, option_value_field, option_text_field)
*Ext
Edit
: HTMLSecurity.SetAntiCSRFToken "ProductEditForm"
<%= HTML.Hidden("nonce", HTMLSecurity.GetAntiCSRFToken("ProductEditForm")) %>
EditPost
: HTMLSecurity.OnInvalidAntiCsrfTokenRedirectToActionExt "ProductEditForm", Request.Form("nonce"), "Edit", Array("Id", Request.Form("Id"))
MVC.ControllerName
, MVC.ActionName
MVC.RedirectTo(controller_name, action_name)
или MVC.RedirectToActionPOST(action_name)
с вариантами *Ext
Платформа предоставляет инструменты, помогающие смягчить следующие три пункта из списка OWASP Top 10:
Database_Class
поддерживает параметризованные запросы.H()
предназначен для простого кодирования всех остальных выходных данных.HtmlSecurity
обеспечивает проверку nonce для каждой формы и для каждого сайта, чтобы минимизировать эту угрозу.Остальные семь уязвимостей большей частью или полностью являются ответственностью разработчика и/или администратора.
Одна идиома, используемая во всей платформе, — это обходной путь для VBScript, не допускающий перегрузки методов. Вообще говоря, есть два случая: один, когда метод является полнофункциональным с несколькими параметрами, и другой, когда сигнатура метода упрощена. Это решается путем добавления в конец полнофункционального метода Ext
чтобы обозначить его как «расширенную» версию упрощенного метода.
Например, это из HTML_Helper_Class
:
Public Function LinkTo(link_text, controller_name, action_name)
LinkTo = LinkToExt(link_text, controller_name, action_name, empty, empty)
End Function
Public Function LinkToExt(link_text, controller_name, action_name, params_array, attribs_array)
LinkToExt = "<a href='" & Encode(Routes.UrlTo(controller_name, action_name, params_array)) & "'" & _
HtmlAttribs(attribs_array) & ">" & link_text & "</a>" & vbCR
End Function
А это из MVC_Dispatcher_Class
:
Public Sub RedirectTo(controller_name, action_name)
RedirectToExt controller_name, action_name, empty
End Sub
' Redirects the browser to the specified action on the specified controller with the specified querystring parameters.
' params is a KVArray of querystring parameters.
Public Sub RedirectToExt(controller_name, action_name, params)
Response.Redirect Routes.UrlTo(controller_name, action_name, params)
End Sub
Proc
и Func
(намереваясь напрямую ответить на критику со стороны этого парня), но так и не опубликовал это. Брайан раздвинул границы далеко за пределы этого. LinkedList_Class
и его итераторы адаптированы на основе его работы, а его чрезвычайно мощные лямбда-функции специально разработаны, чтобы не слишком сильно увязнуть в ASP. Фреймворк также принимает некоторые из его соглашений по кодированию, такие как суффиксы _Class
и использование ленивых загружаемых одноэлементных функций глобальной области видимости.