Este es un marco MVC, todo escrito en 100% VBScript. Piense en ello como algo así como Spring Boot para ASP clásico.
Sane es un marco MVC relativamente completo que aporta cordura al ASP clásico. Tiene algunas similitudes en estilo con .NET MVC y Rails, pero no se parece exactamente a ninguno de los dos. Es obstinado porque supone que los controladores existirán en una ubicación de carpeta específica, pero esa ubicación es algo configurable.
Las principales características que distinguen este marco incluyen:
All/Any
prueba booleana, Min/Max/Sum
, Map/Select
para proyecciones y Where
para filtros, con expresiones básicas de estilo lambda compatibles.Up
y Down
: versión que controla los cambios en la base de datosScripting.Dictionary
cada vezKVArray
y sus métodos auxiliares para facilitar la creación de HTML.Todo esto fue escrito en VBScript. En realidad.
Sane tiene licencia según los términos de GPLv3.
Nota: Este marco se extrajo de un proyecto de enrutamiento de flujo de trabajo interno del mundo real, por lo que tiene algunas aristas.
Esto brinda una descripción general rápida del flujo de código para un controlador y los modelos y vistas que utiliza. Muestra cuántas de las funciones enumeradas anteriormente se utilizan realmente juntas.
"Hay muchos iguales, pero este es el mío".
Sobre todo porque era un proyecto interesante que supera los límites del ASP clásico. La gran mayoría de los desarrolladores odian VBScript y Classic ASP, en su mayoría con razón. Muchos de los problemas que afectan al ASP clásico surgen de las limitaciones de la época en que se desarrolló, a mediados de los años 1990. Los desarrolladores no pudieron utilizar lo que hoy se consideran prácticas fundamentales (uso generalizado de clases, etc.) porque el lenguaje no fue diseñado para ejecutarse de una manera que llamaríamos "rápida" y el uso de estas prácticas provocaría que la aplicación se atascara y fallara. Debido a esto, la comunidad ASP se vio obligada a usar ASP de la misma manera que PHP: como un procesador de plantillas basado en páginas en línea, no como un marco de aplicación completo por derecho propio. Además, seamos honestos, Microsoft comercializó ASP para todos, independientemente del nivel de habilidad, y la mayoría de los tutoriales que se encuentran en línea eran horribles y fomentaban prácticas terriblemente malas.
Hoy lo sabemos mejor y, gracias a la Ley de Moore, la potencia informática se ha multiplicado aproximadamente por 500 desde mediados de los años 90, por lo que podemos darnos el lujo de hacer cosas que eran impensables hace unos años.
Este marco se extrajo de un proyecto real que se construyó de esta manera. Funcionó bastante bien y no debería haber ninguna razón (funcionalmente hablando) para que no funcione como un marco de aplicación viable. Dicho esto, de manera realista, si necesitamos desarrollar una aplicación hoy, usaríamos un marco moderno como .NET MVC o uno de sus competidores, por lo que esto está aquí en caso de que sea útil para alguien más. Además, fue divertido construirlo. :)
Dependencia: la demostración se creó con la base de datos de muestra de Microsoft Northwind. Descargue el archivo BAK de SQL Server aquí y restáurelo en una instancia de SQL Server. También están disponibles scripts SQL y archivos MDF.
File -> Open Web Site...
y seleccione el directorio Demo
AppDALlib.DAL.asp
y modifique la cadena de conexión para que apunte a su base de datos./index.asp
El archivo index.asp
lo redirigirá automáticamente al controlador de inicio, /App/Controllers/HomeController.asp
y cargará la acción predeterminada, Index
.
Algunas de las características siguientes tienen pruebas ASPUnit correspondientes. Encuéntrelos en el directorio de Pruebas.
<%
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
%>
El controlador sólo conoce el dominio, no la base de datos. ¡Alegría!
Las acciones son funciones sin parámetros (como Rails y a diferencia de .NET MVC): los parámetros se extraen del objeto Request
como en ASP tradicional. MVC.RequirePost
le permite restringir la acción para responder solo a solicitudes POST
; de lo contrario, se producirán errores.
MVC.Dispatch
es el punto de entrada mágico al marco. Dado que las vistas se #include
d en el controlador, y dado que una aplicación puede tener 1..n
controladores cada uno con 1..n
acciones, tener un despachador MVC monolítico central no es factible más allá de unos pocos controladores simples. Esto se debe a que ASP carga y compila todo el archivo #include
d para cada vista de página. Dado esto, el marco delega la creación de instancias a los propios controladores, haciéndolos responsables de iniciar el marco en lugar de hacer que el marco sea responsable de cargar y crear instancias de todos los controladores y de cargar y compilar todas las vistas para cada solicitud. El marco crea una instancia del controlador dinámicamente según las convenciones de nomenclatura, pero solo se analiza y carga un controlador por solicitud en lugar de todos los controladores. Al cargar solo un controlador, podemos usar los ahorros para cargar muchas bibliotecas útiles que hacen que el desarrollo sea mucho más amigable para los desarrolladores.
Debido a este enfoque, el "motor de enrutamiento" es en realidad solo una clase que sabe cómo crear URL para archivos de controlador, por lo que Routes.UrlTo("Orders", "Show", Array("Id", order.Id))
genera el URL /App/Controllers/OrdersController.asp?_A=Show&Id=123
(para order.Id = 123). Las URL apuntan a controladores y proporcionan el nombre de la acción que se ejecutará mediante el parámetro _A
. Los parámetros de acción se pasan a través de una estructura de datos KVArray
, que es simplemente una matriz de pares clave/valor que se utiliza ampliamente en todo el marco. Por ejemplo, aquí hay dos KVArray
utilizados en uno de los muchos asistentes HTML:
<%= HTML.LinkToExt( " View Orders " , _
" Orders " , _
" List " , _
array ( " param1 " , " value1 " , " param2 " , " value2 " ), _
array ( " class " , " btn btn-primary " , " id " , " orders-button " )) %>
Detrás de escena, este método crea un ancla que enruta al combo correcto de controlador/acción, pasa los parámetros especificados a través de la cadena de consulta y tiene la class
HTML especificada y los atributos id
. Los KVArray
se manejan fácilmente gracias a algunos métodos auxiliares como KeyVal
y KVUnzip
.
La estructura de datos KVArray
es fundamental para gran parte del marco y simplifica enormemente la codificación. Básicamente, un KVArray
no es más que una matriz VBScript estándar que siempre debe consumirse en grupos de dos. En otras palabras, para construir un KVArray
solo necesitamos construir una matriz donde el elemento 0 sea la primera clave y el elemento 1 su valor, el elemento 2 la segunda clave y el elemento 3 su valor, etc.
En esencia, puede imaginar un KVArray
como una forma de utilizar llamadas de estilo System.Object
como se hace en Html.ActionLink
de .NET.
Por ejemplo:
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"
Pero en realidad nunca lo escribirías así, sino que usarías el constructor Array
en línea de esta manera:
dim params : params = Array( "Name" , "Bob" , "Age" , 35 , "FavoriteColor" , "Blue" )
O para mayor legibilidad:
dim params : params = Array( _
"Name" , "Bob" , _
"Age" , 35 , _
"FavoriteColor" , "Blue" _
)
Para iterar sobre esta matriz paso a 2 y usar KeyVal
para obtener la clave y el valor actuales:
dim idx, the_key, the_val
For idx = 0 to UBound(kvarray) step 2
KeyVal kvarray, idx, the_key, the_val
Next
En cada iteración, the_key
contendrá la clave actual (por ejemplo, "Nombre", "Edad" o "Color favorito") y the_val
contendrá el valor correspondiente de la clave.
Pero ¿por qué no utilizar un diccionario?
Los diccionarios son geniales, pero son componentes COM y, al menos históricamente, crear instancias era costoso y, debido a los subprocesos, no deberían colocarse en la sesión. También es complicado trabajar con ellos para los casos de uso en este marco y no hay una manera fácil de crear instancias en línea con una cantidad dinámica de parámetros.
Lo que realmente necesitamos es una estructura de datos clave-valor rápida y de solo avance que nos permita iterar sobre los valores y extraer cada clave y valor para construir algo así como una etiqueta HTML con atributos arbitrarios o una cláusula SQL where
con columnas arbitrarias, no Búsqueda rápida de claves individuales. Por lo tanto, necesitamos un híbrido de matriz y diccionario que satisfaga nuestras necesidades específicas y permita la declaración en línea de una cantidad arbitraria de parámetros. KVArray
nos permite escribir código de forma muy natural como en el ejemplo LinkToExt
anterior, o crear URL manualmente usando Routes.UrlTo()
:
<%
< a href = " <%= Routes.UrlTo( " Users " , " Edit " , array( " Id " , user.Id)) %> " >
< i class = " glyphicon glyphicon-user " >< /a >
< /a >
%>
También podemos crear métodos genéricos Find
de repositorio que se pueden usar así:
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 _
) _
)
Hay ejemplos de esto en los repositorios de demostración, donde KVUnzip
también se usa de manera muy efectiva para ayudar a construir fácilmente la cláusula SQL where
. El siguiente ejemplo es del método ProductRepository.Find()
que acepta un KVArray
que contiene pares clave-valor de predicado y lo descomprime en dos matrices separadas que se utilizan para generar la consulta:
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
%>
Valide los modelos llamando al método auxiliar Validate*
apropiado desde el constructor Class_Initialize
del modelo:
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
Actualmente, solo se incluyen ValidateExists
, ValidateMinLength
, ValidateMaxLength
, ValidateNumeric
y ValidatePattern
. Lo que realmente hacen estos métodos auxiliares es crear una nueva instancia de la clase de validación correspondiente y adjuntarla a la propiedad Validator
del modelo. Por ejemplo, cuando un modelo declara una validación usando ValidateExists Me, "Name", "Name must exist."
Lo siguiente es lo que realmente sucede detrás de escena:
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
Aquí Me
la instancia del modelo de dominio. Luego se usa Validator_Class
(a través de YourModel.Validator
) para validar todas las reglas de validación registradas, configurando los campos Errors
y HasErrors
si se encuentran errores. Esto es similar al patrón Observer. La razón por la que pasamos Me
es porque esto nos permite tener un método convenientemente redactado para cada validación que tenga un fuerte significado semántico, por ejemplo, ValidateExists
. Se necesita un poco de código jutsu pero vale la pena.
Agregar nuevas validaciones es fácil, simplemente agregue una nueva clase de validación y Sub
auxiliar. Por ejemplo, para agregar una validación que requiere que una cadena comience con la letra "A", crearía una StartsWithLetterAValidation_Class
y un método auxiliar Sub ValidateStartsWithA(instance, field_name, message)
y luego lo llamaría a través de ValidateStartsWithA Me, "MyField", "Field must start with A."
Los modelos de dominio se pueden crear convirtiendo un conjunto de registros ADO en una lista vinculada de modelos de dominio mediante transformaciones de estilo Automapper. ¿Di qué?
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
El uso de la palabra clave empty
es un enfoque común adoptado por este marco. Una queja común de VBScript es que no permite parámetros opcionales. Si bien esto es técnicamente cierto, es fácil solucionarlo, pero prácticamente todos los ejemplos que se encuentran en línea implican pasar cadenas vacías, valores nulos o un enfoque similar. Usar la palabra clave empty
de VBScript incorporada es una forma semánticamente significativa de manejar parámetros opcionales, dejando en claro que nuestra intención específica era ignorar el parámetro opcional. En este caso, el método DAL.Query
acepta dos parámetros, la consulta SQL y un segundo parámetro opcional que contiene valores de enlace. El segundo parámetro puede ser un valor único como en DAL.Query("select a from b where a = ?", "foo")
o una matriz de enlaces, por ejemplo, DAL.Query("select a from b where a = ? and c = ?", Array("foo", "bar")
. En el ejemplo anterior se ignora explícitamente ya que no hay variables de enlace en SQL.
En este ejemplo, la variable DAL
es simplemente una instancia de Database_Class
de lib.Data.asp
. En el proyecto original, DAL era una clase personalizada que actuaba como punto de entrada para un conjunto de instancias Database_Class
cargadas de forma diferida, lo que permitía compartir y mover datos entre bases de datos durante el flujo de trabajo.
El objeto Automapper
es una clase de VBScript que intenta asignar cada campo del objeto de origen a un campo correspondiente en el objeto de destino. El objeto de origen puede ser un conjunto de registros o una clase personalizada. La función se puede asignar a un objeto nuevo o existente. El objeto Automapper
contiene tres métodos: AutoMap
que intenta asignar todas las propiedades; FlexMap
que le permite elegir un subconjunto de propiedades para asignar, por ejemplo, Automapper.FlexMap(rs, new OrderModel_Class, array("DateOrdered", "CustomerName"))
solo copiará los dos campos especificados del conjunto de registros de origen a la nueva instancia del modelo. ; y DynMap
, que le permite reasignar valores dinámicamente; para ver un ejemplo artificial, consulte:
Automapper.DynMap(rs, new OrderModel_Class, _
array( "target.CustomerName = UCase(src.CustomerName)" , _
"target.LikedOrder = src.CustomerWasHappy" ))
Dado que tanto el origen como el destino pueden ser cualquier objeto con métodos de instancia, esta es una forma muy útil de gestionar el enlace de modelos en métodos CRUD, por ejemplo:
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
... etc
End Sub
Debido a que #include
d en la acción del controlador, la vista tiene acceso completo a la instancia del Model
del controlador. Aquí accede a la propiedad Order
del modelo de vista e itera sobre la propiedad LineItems
(que sería una instancia LinkedList_Class
creada dentro del repositorio) para construir la vista. Al utilizar modelos de vista, puede crear vistas enriquecidas que no están vinculadas a una estructura de conjunto de registros específica. Consulte HomeController
en la demostración para ver un modelo de vista de ejemplo que contiene cuatro listas separadas de objetos de dominio para crear una vista resumida del panel.
El método MVC.RequireModel
proporciona la capacidad de escribir fuertemente la vista, imitando la directiva @model
en .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 >
Proporciona llamadas estilo lambda encadenables en una lista. De las pruebas unitarias:
Enumerable(list) _
.Where( "len(item_) > 5" ) _
.Map( "set V_ = new ChainedExample_Class : V_.Data = item_ : V_.Length = len(item_)" ) _
.Max( "item_.Length" )
V_
es una variable de instancia especial utilizada por el método Map
para representar el resultado de la expresión "lambda". item_
es otra variable de instancia especial que representa el elemento actual que se está procesando. Entonces, en este caso, Map
itera sobre cada elemento de la lista y ejecuta la expresión "lambda" pasada. El resultado de Map
es una nueva instancia de EnumerableHelper_Class
que contiene una lista de instancias ChainedExample_Class
creadas por la expresión. Luego, Max
procesa este enumerable para devolver un valor único, la longitud máxima.
Incluye detalles de conexión y acceso a la base de datos. Además de los ejemplos ya mostrados, también puede manejar:
DAL.Execute "delete from Orders where OrderId = ?", id
set rs = DAL.PagedQuery(sql, params, per_page, page_num)
DAL.BeginTransaction
, DAL.CommitTransaction
y DAL.RollbackTransaction
La clase también cierra y destruye automáticamente la conexión empaquetada mediante el método Class_Terminate
, que se llama cuando la clase está lista para su destrucción.
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"
Las migraciones se pueden acelerar o reducir a través de la interfaz web ubicada en migrate.asp
. Migration.Do
ejecuta comandos SQL. Las migraciones se procesan en el orden en que se cargan. Se recomienda seguir un esquema de nomenclatura estructurado como se muestra arriba para realizar pedidos fácilmente. Hay algunos comandos especiales, como Migration.Irreversible
, que le permiten detener el proceso de migración, etc.
El proyecto del mundo real del que se extrajo el marco contenía aproximadamente 3 docenas de migraciones, por lo que funcionó muy bien para versionar la base de datos durante el desarrollo.
Nota: la interfaz web de migraciones es muy básica y poco bonita.
Dependencia: Para utilizar la función de migraciones, primero debe crear la tabla meta_migrations
usando el script [ ! Create Migrations Table.sql
] (Sane/Framework/Data/Migrations/! Crear tabla de migraciones.sql).
Dado que el uso de la depuración paso a paso no siempre es posible en ASP clásico, esto facilita mucho la depuración y el seguimiento.
Dump
genera objetos de forma significativa:
dim a : a = GetSomeArray()
Dump a
Producción:
[Array:
0 => «elt1»
1 => «elt2»
2 => «elt3»
]
Incluso maneja clases personalizadas, usando el campo Class_Get_Properties
:
Dump Product
Producción:
{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»
}
Y maneja el anidamiento, como se ve aquí cuando se realizó una llamada a Dump Model
en la acción Show
de OrdersController.asp
en la demostración:
{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
detiene inmediatamente la ejecución. die "some message"
detiene la ejecución y muestra un "algún mensaje" en la pantalla. trace "text"
y comment "text"
escriben comentarios HTML que contienen "texto", útiles para rastrear detrás de escena sin alterar el diseño.
Flash.Success = "Product updated."
, Flash.Errors = model.Validator.Errors
, etc.
Si se encuentran errores al crear un modelo, deberíamos poder volver a mostrar el formulario con el contenido del usuario aún completo. Para simplificar esto, el marco proporciona el objeto FormCache
que serializa/deserializa los datos del formulario a través de la sesión.
Por ejemplo, en una acción Create
podemos tener:
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
Y en 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
envuelve Response.Write
y varía su salida según el tipo pasado, con salida especial para listas y matrices.H(string)
HTMLCodifica una cadenaAssign(target, src)
abstrae la necesidad de usar set
para objetos en los casos en que tratamos con variables de tipo arbitrarioChoice(condition, trueval, falseval)
es un iif
más funcionalHTML.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)
o MVC.RedirectToActionPOST(action_name)
con variantes *Ext
El marco proporciona herramientas para ayudar a mitigar los siguientes tres elementos del OWASP Top 10:
Database_Class
admite consultas parametrizadas.H()
se proporciona para una codificación simple de todas las demás salidas.HtmlSecurity
proporciona comprobaciones nonce por formulario y por sitio para mitigar esta amenaza.Las siete vulnerabilidades restantes son en su mayor parte o totalmente responsabilidad del desarrollador y/o administrador.
Un modismo utilizado en todo el marco es una solución para que VBScript no permita sobrecargas de métodos. En términos generales, hay dos casos, uno en el que el método tiene todas las funciones con varios parámetros y otro en el que la firma del método está simplificada. Esto se maneja haciendo que el método con todas las funciones agregue Ext
al final para indicarlo como una versión "extendida" del método simplificado.
Por ejemplo, esto es de 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
Y esto es de 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
y Func
construidas dinámicamente (con la intención de abordar directamente las críticas de este tipo), pero nunca lo publiqué. Brian traspasó los límites mucho más allá de eso. LinkedList_Class
y sus iteradores están adaptados de su trabajo, con sus capacidades lambda extremadamente poderosas diseñadas para evitar atascar demasiado ASP. El marco también adopta algunas de sus convenciones de codificación, como los sufijos _Class
y el uso de funciones singleton de alcance global cargadas de forma diferida.