Este é um framework MVC todo escrito em 100% VBScript. Pense nisso como uma espécie de Spring Boot para Classic ASP.
Sane é uma estrutura MVC relativamente completa que traz sanidade ao ASP clássico. Ele tem algumas semelhanças de estilo com o .NET MVC e o Rails, mas não se parece exatamente com nenhum deles. É opinativo porque assume que os controladores existirão em um local de pasta específico, mas esse local é um tanto configurável.
Os principais recursos que distinguem esta estrutura incluem:
All/Any
, Min/Max/Sum
, Map/Select
para projeções e Where
para filtros, com suporte para expressões básicas de estilo lambdaUp
e Down
– controle de versão das alterações do banco de dadosScripting.Dictionary
a cada vezKVArray
e seus métodos auxiliares para facilitar a construção de HTMLTudo isso foi escrito em VBScript. Realmente.
Sane é licenciado sob os termos da GPLv3.
Observação: esta estrutura foi extraída de um projeto de roteamento de fluxo de trabalho interno do mundo real, portanto, possui algumas arestas.
Isso fornece uma visão geral rápida do fluxo de código de um controlador e dos modelos e visualizações que ele usa. Mostra quanto das funcionalidades listadas acima são realmente usadas em conjunto.
"Há muitos como este, mas este é meu."
Principalmente porque foi um projeto interessante que ultrapassa os limites do ASP Clássico. A grande maioria dos desenvolvedores odeia VBScript e ASP clássico, principalmente por um bom motivo. Muitos dos problemas que atormentam o ASP Clássico decorrem das restrições da época em que foi desenvolvido, meados da década de 1990. Os desenvolvedores não conseguiram usar o que hoje são consideradas práticas fundamentais (uso amplo de classes, etc.) porque a linguagem não foi projetada para ser executada de uma maneira que chamaríamos de "rápida" e o uso dessas práticas faria com que o aplicativo travasse e travasse. Por causa disso, a comunidade ASP foi forçada a usar o ASP da mesma forma que o PHP foi usado - como um processador de modelo baseado em página inline, e não como uma estrutura de aplicativo completa por si só. Além disso, sejamos honestos, a Microsoft comercializava ASP para todos, independentemente do nível de habilidade, e a maioria dos tutoriais encontrados online eram horríveis e incentivavam práticas terrivelmente ruins.
Hoje sabemos melhor e, graças à Lei de Moore, o poder da computação aumentou cerca de 500 vezes desde meados dos anos 90, por isso podemos dar-nos ao luxo de fazer coisas que eram impensáveis há alguns anos.
Este framework foi extraído de um projeto real que foi construído desta forma. Funcionou muito bem e não deveria haver razão (funcionalmente falando) para que não funcionasse como uma estrutura de aplicativo viável. Dito isso, realisticamente, se precisarmos desenvolver um aplicativo hoje, usaríamos uma estrutura moderna como o .NET MVC ou um de seus concorrentes, então isso está aqui apenas para o caso de ser útil para outra pessoa. Além disso, foi divertido construir. :)
Dependência: a demonstração foi criada no banco de dados de exemplo Microsoft Northwind. Baixe o arquivo BAK do SQL Server aqui e restaure-o em uma instância do SQL Server. Scripts SQL e arquivo MDF também estão disponíveis.
File -> Open Web Site...
e selecione o diretório Demo
AppDALlib.DAL.asp
e modifique a string de conexão para apontar para seu banco de dados./index.asp
O arquivo index.asp
irá redirecioná-lo automaticamente para o controlador Home, /App/Controllers/HomeController.asp
e carregará a ação padrão, Index
.
Alguns dos recursos abaixo possuem testes ASPUnit correspondentes. Encontre-os no diretório Testes.
<%
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
%>
O controlador conhece apenas o domínio, não o banco de dados. Alegria!
Ações são funções sem parâmetros (como Rails e diferentemente do .NET MVC) - os parâmetros são extraídos do objeto Request
como no ASP tradicional. MVC.RequirePost
permite restringir a ação para responder apenas a solicitações POST
- caso contrário, ocorrerão erros.
MVC.Dispatch
é o ponto de entrada mágico para a estrutura. Como as visualizações são #include
d no controlador e como um aplicativo pode ter 1..n
controladores, cada um com 1..n
ações, ter um despachante MVC monolítico central não é viável além de alguns controladores simples. Isso ocorre porque o ASP carrega e compila todo o arquivo #include
d para cada visualização de página. Dado isso, a estrutura delega a instanciação aos próprios controladores, tornando-os responsáveis por iniciar a estrutura, em vez de tornar a estrutura responsável por carregar e instanciar todos os controladores e carregar e compilar todas as visualizações para cada solicitação. A estrutura instancia o controlador dinamicamente com base nas convenções de nomenclatura, mas apenas um controlador é analisado e carregado por solicitação, em vez de todos os controladores. Ao carregar apenas um controlador, podemos usar a economia para carregar muitas bibliotecas úteis que tornam o desenvolvimento muito mais amigável ao desenvolvedor.
Por causa dessa abordagem, o "mecanismo de roteamento" é na verdade apenas uma classe que sabe como construir URLs para arquivos do controlador, então Routes.UrlTo("Orders", "Show", Array("Id", order.Id))
gera o URL /App/Controllers/OrdersController.asp?_A=Show&Id=123
(para order.Id = 123). URLs apontam para controladores e fornecem o nome da ação a ser executada através do parâmetro _A
. Os parâmetros de ação são passados por meio de uma estrutura de dados KVArray
, que é simplesmente uma matriz de pares chave/valor que é usada extensivamente em toda a estrutura. Por exemplo, aqui estão dois KVArray
s usados em um dos muitos auxiliares HTML:
<%= HTML.LinkToExt( " View Orders " , _
" Orders " , _
" List " , _
array ( " param1 " , " value1 " , " param2 " , " value2 " ), _
array ( " class " , " btn btn-primary " , " id " , " orders-button " )) %>
Nos bastidores, esse método cria uma âncora que direciona para a combinação correta de controlador/ação, passa os parâmetros especificados por meio da string de consulta e possui a class
HTML e os atributos id
especificados. KVArray
s são facilmente manipulados graças a alguns métodos auxiliares, como KeyVal
e KVUnzip
.
A estrutura de dados KVArray
é fundamental para grande parte da estrutura e simplifica bastante a codificação. Fundamentalmente, um KVArray
nada mais é do que um array VBScript padrão que deve sempre ser consumido em grupos de dois. Em outras palavras, para construir um KVArray
precisamos apenas construir um array com o elemento 0 sendo a primeira chave e o elemento 1 o seu valor, o elemento 2 a segunda chave e o elemento 3 o seu valor, etc.
Em essência, você pode imaginar um KVArray
como uma maneira de usar chamadas de estilo System.Object
como é feito no Html.ActionLink
do .NET.
Por exemplo:
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"
Mas, na realidade, você nunca escreveria assim; em vez disso, usaria o construtor Array
embutido assim:
dim params : params = Array( "Name" , "Bob" , "Age" , 35 , "FavoriteColor" , "Blue" )
Ou para maior legibilidade:
dim params : params = Array( _
"Name" , "Bob" , _
"Age" , 35 , _
"FavoriteColor" , "Blue" _
)
Para iterar sobre esta matriz passo a 2 e usar KeyVal
para obter a chave e o valor atuais:
dim idx, the_key, the_val
For idx = 0 to UBound(kvarray) step 2
KeyVal kvarray, idx, the_key, the_val
Next
Em cada iteração, the_key
conterá a chave atual (por exemplo, "Name", "Age" ou "FavoriteColor") e the_val
conterá o valor correspondente da chave.
Mas por que não usar um Dicionário?
Os dicionários são ótimos, mas são componentes COM e eram pelo menos historicamente caros para instanciar e, por causa do threading, não deveriam ser colocados na sessão. Eles também são complicados de trabalhar para os casos de uso nesta estrutura e não há uma maneira fácil de instanciá-los em linha com um número dinâmico de parâmetros.
O que realmente precisamos é de uma estrutura de dados de valor-chave rápida e somente de encaminhamento que nos permita iterar sobre os valores e extrair cada chave e valor para construir algo como uma tag HTML com atributos arbitrários ou uma cláusula SQL where
com colunas arbitrárias, não pesquisa rápida de chaves individuais. Portanto, precisamos de um híbrido de array e dicionário que atenda às nossas necessidades específicas e permita a declaração in-line de um número arbitrário de parâmetros. O KVArray
nos permite escrever código de maneira muito natural, como o exemplo LinkToExt
acima, ou construir URLs manualmente usando Routes.UrlTo()
:
<%
< a href = " <%= Routes.UrlTo( " Users " , " Edit " , array( " Id " , user.Id)) %> " >
< i class = " glyphicon glyphicon-user " >< /a >
< /a >
%>
Também podemos criar métodos Find
de repositório genérico que podem ser usados assim:
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 _
) _
)
Existem exemplos disso nos repositórios de demonstração, onde KVUnzip
também é usado de forma muito eficaz para ajudar a construir facilmente a cláusula where
sql. O exemplo abaixo é do método ProductRepository.Find()
que aceita um KVArray
contendo pares de valores-chave predicados e descompacta -o em duas matrizes separadas que são usadas para construir a 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 os modelos chamando o método auxiliar Validate*
apropriado de dentro do construtor Class_Initialize
do 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
Atualmente, apenas ValidateExists
, ValidateMinLength
, ValidateMaxLength
, ValidateNumeric
e ValidatePattern
estão incluídos. O que esses métodos auxiliares realmente fazem é criar uma nova instância da classe de validação correspondente e anexá-la à propriedade Validator
do modelo. Por exemplo, quando um modelo declara uma validação usando ValidateExists Me, "Name", "Name must exist."
o seguinte é o que realmente acontece nos bastidores:
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
Aqui Me
é a instância do modelo de domínio. O Validator_Class
é então usado (via YourModel.Validator
) para validar todas as regras de validação registradas, definindo os campos Errors
e HasErrors
caso sejam encontrados erros. Isso é semelhante ao padrão Observer. A razão pela qual passamos Me
é porque isso nos permite ter um método convenientemente formulado para cada validação que tenha um significado semântico forte, por exemplo, ValidateExists
. É preciso um pouco de jutsu de código, mas vale a pena.
Adicionar novas validações é fácil, basta adicionar uma nova classe de validação e auxiliar Sub
. Por exemplo, para adicionar uma validação que requer que uma string comece com a letra "A", você criaria um StartsWithLetterAValidation_Class
e um método auxiliar Sub ValidateStartsWithA(instance, field_name, message)
e, em seguida, chamaria-o via ValidateStartsWithA Me, "MyField", "Field must start with A."
Os modelos de domínio podem ser criados convertendo um conjunto de registros ADO em uma lista vinculada de modelos de domínio por meio de transformações no estilo Automapper. Diga o 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
O uso da palavra-chave empty
é uma abordagem comum adotada por esta estrutura. Uma reclamação comum do VBScript é que ele não permite parâmetros opcionais. Embora isso seja tecnicamente verdade, é fácil de contornar, mas praticamente todos os exemplos encontrados on-line envolvem a passagem de strings vazias, ou valores nulos, ou uma abordagem semelhante. Usar a palavra-chave VBScript integrada empty
é uma maneira semanticamente significativa de lidar com parâmetros opcionais, deixando claro que pretendíamos especificamente ignorar o parâmetro opcional. Neste caso o método DAL.Query
aceita dois parâmetros, a consulta SQL e um segundo parâmetro opcional contendo valores de ligação. O segundo parâmetro pode ser um valor único como em DAL.Query("select a from b where a = ?", "foo")
ou uma matriz de ligações, por exemplo, DAL.Query("select a from b where a = ? and c = ?", Array("foo", "bar")
. No exemplo acima, ele é explicitamente ignorado, pois não há variáveis de ligação no SQL.
Neste exemplo, a variável DAL
é simplesmente uma instância de Database_Class
de lib.Data.asp
. No projeto original, o DAL era uma classe personalizada que atuava como um ponto de entrada para um conjunto de instâncias Database_Class
de carregamento lento, permitindo que os dados fossem compartilhados e movidos entre bancos de dados durante o fluxo de trabalho.
O objeto Automapper
é uma classe VBScript que tenta mapear cada campo no objeto de origem para um campo correspondente no objeto de destino. O objeto de origem pode ser um conjunto de registros ou uma classe personalizada. A função pode ser mapeada para um objeto novo ou existente. O objeto Automapper
contém três métodos: AutoMap
que tenta mapear todas as propriedades; FlexMap
, que permite escolher um subconjunto de propriedades para mapear, por exemplo, Automapper.FlexMap(rs, new OrderModel_Class, array("DateOrdered", "CustomerName"))
copiará apenas os dois campos especificados do conjunto de registros de origem para a nova instância do modelo ; e DynMap
, que permite remapear valores dinamicamente. Para ver um exemplo inventado:
Automapper.DynMap(rs, new OrderModel_Class, _
array( "target.CustomerName = UCase(src.CustomerName)" , _
"target.LikedOrder = src.CustomerWasHappy" ))
Como tanto a origem quanto o destino podem ser qualquer objeto com métodos de instância, esta é uma maneira muito útil de gerenciar a vinculação de modelos em métodos CRUD, por exemplo:
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
... etc
End Sub
Por estar #include
d na ação do controlador, a visualização tem acesso total à instância Model
do controlador. Aqui ele acessa a propriedade Order
do modelo de visualização e itera sobre a propriedade LineItems
(que seria uma instância LinkedList_Class
construída dentro do repositório) para construir a visualização. Usando modelos de exibição, você pode criar exibições avançadas que não estão vinculadas a uma estrutura específica de conjunto de registros. Consulte o HomeController
na demonstração para obter um exemplo de modelo de visualização que contém quatro listas separadas de objetos de domínio para construir uma visualização resumida do painel.
O método MVC.RequireModel
fornece a capacidade de digitar fortemente a visualização, imitando a diretiva @model
no .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 >
Fornece chamadas encadeadas no estilo lambda em uma lista. Dos testes de unidade:
Enumerable(list) _
.Where( "len(item_) > 5" ) _
.Map( "set V_ = new ChainedExample_Class : V_.Data = item_ : V_.Length = len(item_)" ) _
.Max( "item_.Length" )
V_
é uma variável de instância especial usada pelo método Map
para representar o resultado da expressão "lambda". item_
é outra variável de instância especial que representa o item atual que está sendo processado. Portanto, neste caso, Map
itera sobre cada item da lista e executa a expressão "lambda" passada. O resultado de Map
é uma nova instância de EnumerableHelper_Class
contendo uma lista de instâncias de ChainedExample_Class
construídas pela expressão. Este enumerável é então processado por Max
para retornar um único valor, o comprimento máximo.
Envolve detalhes de conexão e acesso ao banco de dados. Além dos exemplos já mostrados ele também pode lidar com:
DAL.Execute "delete from Orders where OrderId = ?", id
set rs = DAL.PagedQuery(sql, params, per_page, page_num)
DAL.BeginTransaction
, DAL.CommitTransaction
e DAL.RollbackTransaction
A classe também fecha e destrói automaticamente a conexão encapsulada por meio do método Class_Terminate
que é chamado quando a classe está pronta para destruição.
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"
As migrações podem ser intensificadas ou reduzidas por meio da interface da web localizada em migrate.asp
. Migration.Do
executa comandos SQL. As migrações são processadas na ordem carregada. Recomendamos seguir um esquema de nomenclatura estruturado conforme mostrado acima para facilitar o pedido. Existem alguns comandos especiais, como Migration.Irreversible
, que permitem interromper o andamento de uma migração inativa, etc.
O projeto real do qual a estrutura foi extraída continha aproximadamente 3 dúzias de migrações, portanto funcionou muito bem para versionar o banco de dados durante o desenvolvimento.
Nota: a interface web de migrações é muito básica e nada bonita
Dependência: Para utilizar o recurso de migrações você deve primeiro criar a tabela meta_migrations
utilizando o script [ ! Create Migrations Table.sql
](Sane/Framework/Data/Migrations/! Criar Tabela de Migrações.sql).
Como o uso da depuração passo a passo nem sempre é possível no ASP clássico, isso torna a depuração e o rastreamento muito mais fáceis.
Dump
gera objetos de maneira significativa:
dim a : a = GetSomeArray()
Dump a
Saída:
[Array:
0 => «elt1»
1 => «elt2»
2 => «elt3»
]
Ele ainda lida com classes personalizadas, usando o campo Class_Get_Properties
:
Dump Product
Saída:
{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»
}
E ele lida com o aninhamento, como visto aqui quando uma chamada para Dump Model
foi colocada na ação Show
de OrdersController.asp
na Demo:
{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
interrompe imediatamente a execução. die "some message"
interrompe a execução e exibe uma "alguma mensagem" na tela. trace "text"
e comment "text"
escrevem comentários HTML contendo "text", úteis para rastrear os bastidores sem interromper o layout.
Flash.Success = "Product updated."
, Flash.Errors = model.Validator.Errors
, etc.
Se forem encontrados erros ao criar um modelo, deveremos ser capazes de exibir novamente o formulário com o conteúdo do usuário ainda preenchido. Para simplificar isso, a estrutura fornece o objeto FormCache
que serializa/desserializa os dados do formulário por meio da sessão.
Por exemplo, em uma ação Create
podemos ter:
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
E em 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
envolve Response.Write
e varia sua saída com base no tipo passado, com saída especial para listas e arrays.H(string)
HTMLCodifica uma stringAssign(target, src)
abstrai a necessidade de usar set
para objetos em casos onde lidamos com variáveis de tipo arbitrárioChoice(condition, trueval, falseval)
é mais funcional 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)
ou MVC.RedirectToActionPOST(action_name)
com *Ext
A estrutura fornece ferramentas para ajudar a mitigar os três itens a seguir do OWASP Top 10:
Database_Class
suporta consultas parametrizadas.H()
é fornecido para codificação simples de todas as outras saídas.HtmlSecurity
fornece verificações nonce por formulário e por site para mitigar essa ameaça.As sete vulnerabilidades restantes são maior ou totalmente de responsabilidade do desenvolvedor e/ou administrador.
Um idioma usado em toda a estrutura é uma solução alternativa para o VBScript não permitir sobrecargas de métodos. De modo geral, existem dois casos, um em que o método é completo com vários parâmetros e outro em que a assinatura do método é simplificada. Isso é resolvido fazendo com que o método completo acrescente Ext
ao final para denotá-lo como uma versão "estendida" do método simplificado.
Por exemplo, isto é do 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
E isso é do 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
e Func
construídas dinamicamente (com a intenção de abordar diretamente as críticas desse cara), mas nunca as publiquei. Brian ultrapassou os limites muito além disso. O LinkedList_Class
e seus iteradores são adaptados de seu trabalho, com seus recursos lambda extremamente poderosos adaptados para evitar sobrecarregar muito o ASP. A estrutura também adota algumas de suas convenções de codificação, como sufixos _Class
e o uso de funções singleton de escopo global de carregamento lento.