Il s'agit d'un framework MVC entièrement écrit en 100% VBScript. Pensez-y comme à Spring Boot pour Classic ASP.
Sane est un framework MVC relativement complet qui apporte du bon sens à l'ASP classique. Son style présente certaines similitudes avec .NET MVC et Rails, mais ne ressemble exactement à aucun des deux. Il est d'avis qu'il suppose que les contrôleurs existeront dans un emplacement de dossier spécifique, mais cet emplacement est quelque peu configurable.
Les principales caractéristiques qui distinguent ce cadre incluent :
All/Any
, Min/Max/Sum
, Map/Select
pour les projections et Where
pour les filtres, avec expressions de base de style lambda prises en chargeUp
et Down
– version contrôlant les modifications de la base de donnéesScripting.Dictionary
à chaque foisKVArray
et ses méthodes d'assistance pour faciliter la création de HTML.Tout cela a été écrit en VBScript. Vraiment.
Sane est sous licence selon les termes de la GPLv3.
Remarque : Ce cadre a été extrait d'un projet de routage de flux de travail interne réel, il présente donc quelques aspérités.
Cela donne un aperçu rapide du flux de code pour un contrôleur et des modèles et vues qu'il utilise. Il montre dans quelle mesure les fonctionnalités répertoriées ci-dessus sont réellement utilisées ensemble.
"Il y en a beaucoup comme ça, mais celui-ci est le mien."
Principalement parce que c'était un projet intéressant qui repousse les limites de l'ASP classique. La grande majorité des développeurs détestent VBScript et Classic ASP, principalement pour de bonnes raisons. La plupart des problèmes qui affligent l'ASP classique proviennent des contraintes de l'époque de son développement, au milieu des années 1990. Les développeurs n'ont pas pu utiliser ce qui est aujourd'hui considéré comme des pratiques fondamentales (utiliser largement les classes, etc.) car le langage n'a pas été conçu pour s'exécuter d'une manière que nous appellerions « rapide » et l'utilisation de ces pratiques entraînerait l'enlisement et le crash de l'application. Pour cette raison, la communauté ASP a été obligée d'utiliser ASP de la même manière que PHP était utilisé : en tant que processeur de modèles basé sur des pages en ligne, et non en tant que cadre d'application complet à part entière. De plus, soyons honnêtes, Microsoft a commercialisé ASP auprès de tout le monde, quel que soit son niveau de compétence, et la plupart des didacticiels trouvés en ligne étaient horribles et encourageaient des pratiques horriblement mauvaises.
Aujourd'hui, nous savons mieux, et grâce à la loi de Moore, la puissance de calcul a été multipliée par 500 depuis le milieu des années 90, nous pouvons donc nous permettre de faire des choses qui étaient impensables il y a quelques années.
Ce framework a été extrait d'un projet réel construit de cette manière. Cela a plutôt bien fonctionné et il ne devrait y avoir aucune raison (fonctionnellement parlant) pour qu'il ne fonctionne pas comme un cadre d'application viable. Cela dit, de manière réaliste, si nous devons développer une application aujourd'hui, nous utiliserions un framework moderne tel que .NET MVC ou l'un de ses concurrents, donc c'est vraiment juste ici au cas où cela serait utile à quelqu'un d'autre. De plus, c'était amusant à construire. :)
Dépendance : la démo a été créée à partir de l'exemple de base de données Microsoft Northwind. Téléchargez le fichier SQL Server BAK ici et restaurez-le dans une instance SQL Server. Des scripts SQL et des fichiers MDF sont également disponibles.
File -> Open Web Site...
et sélectionnez le répertoire Demo
AppDALlib.DAL.asp
et modifiez la chaîne de connexion pour pointer vers votre base de données./index.asp
Le fichier index.asp
vous redirigera automatiquement vers le contrôleur Home, /App/Controllers/HomeController.asp
et chargera l'action par défaut, Index
.
Quelques-unes des fonctionnalités ci-dessous ont des tests ASPUnit correspondants. Trouvez-les dans le répertoire 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
%>
Le contrôleur connaît uniquement le domaine, pas la base de données. Joie!
Les actions sont des fonctions sans paramètres (comme Rails et contrairement à .NET MVC) - les paramètres sont extraits de l'objet Request
comme dans l'ASP traditionnel. MVC.RequirePost
vous permet de restreindre l'action pour répondre uniquement aux requêtes POST
, sinon les erreurs seront éliminées.
MVC.Dispatch
est le point d'entrée de la sauce magique au framework. Étant donné que les vues sont #include
d dans le contrôleur et qu'une application peut avoir 1..n
contrôleurs chacun avec 1..n
actions, avoir un répartiteur MVC monolithique central n'est pas réalisable au-delà de quelques contrôleurs simples. En effet, ASP charge et compile l'intégralité du fichier #include
d pour chaque page vue. Compte tenu de cela, le framework délègue l'instanciation aux contrôleurs eux-mêmes, les rendant responsables du lancement du framework au lieu de confier au framework la responsabilité du chargement et de l'instanciation de tous les contrôleurs ainsi que du chargement et de la compilation de toutes les vues pour chaque requête. Le framework instancie le contrôleur de manière dynamique en fonction des conventions de dénomination, mais un seul contrôleur est analysé et chargé par requête au lieu de tous les contrôleurs. En chargeant un seul contrôleur, nous pouvons utiliser les économies réalisées pour charger de nombreuses bibliothèques utiles qui rendent le développement beaucoup plus convivial pour les développeurs.
En raison de cette approche, le "moteur de routage" n'est en réalité qu'une classe qui sait comment créer des URL vers des fichiers de contrôleur, donc Routes.UrlTo("Orders", "Show", Array("Id", order.Id))
génère le URL /App/Controllers/OrdersController.asp?_A=Show&Id=123
(pour order.Id = 123). Les URL pointent vers des contrôleurs et fournissent le nom de l'action à exécuter via le paramètre _A
. Les paramètres d'action sont transmis via une structure de données KVArray
, qui est simplement un tableau de paires clé/valeur largement utilisé dans tout le framework. Par exemple, voici deux KVArray
utilisés dans l'un des nombreux assistants HTML :
<%= HTML.LinkToExt( " View Orders " , _
" Orders " , _
" List " , _
array ( " param1 " , " value1 " , " param2 " , " value2 " ), _
array ( " class " , " btn btn-primary " , " id " , " orders-button " )) %>
En coulisses, cette méthode crée une ancre qui achemine vers la combinaison contrôleur/action correcte, transmet les paramètres spécifiés via la chaîne de requête et possède la class
HTML et les attributs id
spécifiés. Les KVArray
sont facilement gérés grâce à quelques méthodes d'assistance telles que KeyVal
et KVUnzip
.
La structure de données KVArray
est fondamentale pour une grande partie du framework et simplifie grandement le codage. Fondamentalement, un KVArray
n'est rien de plus qu'un tableau VBScript standard qui doit toujours être consommé par groupes de deux. En d'autres termes, pour construire un KVArray
il suffit de construire un tableau avec l'élément 0 étant la première clé et l'élément 1 sa valeur, l'élément 2 la deuxième clé et l'élément 3 sa valeur, etc.
Essentiellement, vous pouvez imaginer un KVArray
comme un moyen d'utiliser les appels de style System.Object
comme cela se fait dans Html.ActionLink
de .NET.
Par exemple:
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"
Mais en réalité, vous ne l’écririez jamais comme ça, vous utiliseriez plutôt le constructeur Array
en ligne comme ceci :
dim params : params = Array( "Name" , "Bob" , "Age" , 35 , "FavoriteColor" , "Blue" )
Ou pour plus de lisibilité :
dim params : params = Array( _
"Name" , "Bob" , _
"Age" , 35 , _
"FavoriteColor" , "Blue" _
)
Pour parcourir ce tableau étape par 2 et utiliser KeyVal
pour obtenir la clé et la valeur actuelles :
dim idx, the_key, the_val
For idx = 0 to UBound(kvarray) step 2
KeyVal kvarray, idx, the_key, the_val
Next
À chaque itération, the_key
contiendra la clé actuelle (par exemple "Name", "Age" ou "FavoriteColor") et the_val
contiendra la valeur correspondante de la clé.
Mais pourquoi ne pas utiliser un dictionnaire ?
Les dictionnaires sont excellents, mais ce sont des composants COM et ils étaient au moins historiquement coûteux à instancier et, en raison du threading, ne devraient pas être placés dans la session. Ils sont également difficiles à utiliser pour les cas d'utilisation de ce cadre et il n'existe pas de moyen simple de les instancier en ligne avec un nombre dynamique de paramètres.
Ce dont nous avons réellement besoin, c'est d'une structure de données clé-valeur rapide et en avant uniquement qui nous permet de parcourir les valeurs et d'extraire chaque clé et valeur pour créer quelque chose comme une balise HTML avec des attributs arbitraires ou une clause SQL where
avec des colonnes arbitraires, pas recherche rapide de clés individuelles. Nous avons donc besoin d'un hybride du tableau et du dictionnaire qui réponde à nos besoins spécifiques et permette la déclaration en ligne d'un nombre arbitraire de paramètres. Le KVArray
nous permet d'écrire très naturellement du code comme l'exemple LinkToExt
ci-dessus, ou de construire manuellement des URL à l'aide de Routes.UrlTo()
:
<%
< a href = " <%= Routes.UrlTo( " Users " , " Edit " , array( " Id " , user.Id)) %> " >
< i class = " glyphicon glyphicon-user " >< /a >
< /a >
%>
Nous pouvons également créer un référentiel générique Find
méthodes qui peuvent être utilisées comme ceci :
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 _
) _
)
Il existe des exemples de cela dans les référentiels de démonstration, où KVUnzip
est également utilisé très efficacement pour aider à créer facilement la clause SQL where
. L'exemple ci-dessous provient de la méthode ProductRepository.Find()
qui accepte un KVArray
contenant des paires clé-valeur de prédicat et le décompresse en deux tableaux distincts utilisés pour créer la requête :
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
%>
Validez les modèles en appelant la méthode d'assistance Validate*
appropriée à partir du constructeur Class_Initialize
du modèle :
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
Actuellement, seuls ValidateExists
, ValidateMinLength
, ValidateMaxLength
, ValidateNumeric
et ValidatePattern
sont inclus. En réalité, ces méthodes d'assistance créent une nouvelle instance de la classe de validation correspondante et l'attachent à la propriété Validator
du modèle. Par exemple, lorsqu'un modèle déclare une validation à l'aide de ValidateExists Me, "Name", "Name must exist."
voici ce qui se passe réellement dans les coulisses :
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
Here Me
est l'instance de modèle de domaine. Le Validator_Class
est ensuite utilisé (via YourModel.Validator
) pour valider toutes les règles de validation enregistrées, en définissant les champs Errors
et HasErrors
si des erreurs sont trouvées. Ceci est similaire au modèle Observer. La raison pour laquelle nous transmettons Me
est que cela nous permet d'avoir une méthode formulée de manière pratique pour chaque validation et qui a une forte signification sémantique, par exemple ValidateExists
. Cela demande un peu de code-jutsu mais ça en vaut la peine.
L'ajout de nouvelles validations est simple, il suffit d'ajouter une nouvelle classe de validation et un assistant Sub
. Par exemple, pour ajouter une validation qui nécessite qu'une chaîne commence par la lettre "A", vous devez créer un StartsWithLetterAValidation_Class
et une méthode d'assistance Sub ValidateStartsWithA(instance, field_name, message)
, puis l'appeler via ValidateStartsWithA Me, "MyField", "Field must start with A."
Les modèles de domaine peuvent être créés en convertissant un Recordset ADO en une liste chaînée de modèles de domaine via des transformations de style Automapper. Dis quoi ?
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
L'utilisation du mot-clé empty
est une approche courante adoptée par ce framework. Une plainte courante de VBScript est qu'il n'autorise pas les paramètres facultatifs. Bien que cela soit techniquement vrai, il est facile de contourner ce problème, mais pratiquement tous les exemples trouvés en ligne impliquent la transmission de chaînes vides, de valeurs nulles ou d'une approche similaire. L'utilisation du mot-clé VBScript intégré empty
est un moyen sémantiquement significatif de gérer les paramètres facultatifs, indiquant clairement que nous avions spécifiquement l'intention d'ignorer le paramètre facultatif. Dans ce cas, la méthode DAL.Query
accepte deux paramètres, la requête SQL et un deuxième paramètre facultatif contenant des valeurs de liaison. Le deuxième paramètre peut être soit une valeur unique comme dans DAL.Query("select a from b where a = ?", "foo")
ou un tableau de liaisons, par exemple DAL.Query("select a from b where a = ? and c = ?", Array("foo", "bar")
. Dans l'exemple ci-dessus, il est explicitement ignoré car il n'y a pas de variables de liaison dans le SQL.
Dans cet exemple, la variable DAL
est simplement une instance de Database_Class
de lib.Data.asp
. Dans le projet d'origine, le DAL était une classe personnalisée qui servait de point d'entrée pour un ensemble d'instances Database_Class
chargées paresseusement, permettant aux données d'être partagées et déplacées entre les bases de données pendant le flux de travail.
L'objet Automapper
est une classe VBScript qui tente de mapper chaque champ de l'objet source à un champ correspondant dans l'objet cible. L'objet source peut être un jeu d'enregistrements ou une classe personnalisée. La fonction peut être mappée à un objet nouveau ou existant. L'objet Automapper
contient trois méthodes : AutoMap
qui tente de mapper toutes les propriétés ; FlexMap
qui vous permet de choisir un sous-ensemble de propriétés à mapper, par exemple Automapper.FlexMap(rs, new OrderModel_Class, array("DateOrdered", "CustomerName"))
copiera uniquement les deux champs spécifiés du jeu d'enregistrements source vers la nouvelle instance de modèle ; et DynMap
qui vous permet de remapper dynamiquement les valeurs, pour un exemple artificiel, voir :
Automapper.DynMap(rs, new OrderModel_Class, _
array( "target.CustomerName = UCase(src.CustomerName)" , _
"target.LikedOrder = src.CustomerWasHappy" ))
Étant donné que la source et la cible peuvent être n'importe quel objet avec des méthodes d'instance, c'est un moyen très utile de gérer la liaison de modèle dans les méthodes CRUD, par exemple :
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
... etc
End Sub
Parce qu'elle est #include
d dans l'action du contrôleur, la vue a un accès complet à l'instance Model
du contrôleur. Ici, il accède à la propriété Order
du modèle de vue et parcourt la propriété LineItems
(qui serait une instance LinkedList_Class
construite dans le référentiel) pour créer la vue. À l'aide de modèles de vue, vous pouvez créer des vues riches qui ne sont pas liées à une structure de jeu d'enregistrements spécifique. Consultez HomeController
dans la démo pour un exemple de modèle de vue contenant quatre listes distinctes d'objets de domaine pour créer une vue récapitulative de tableau de bord.
La méthode MVC.RequireModel
offre la possibilité de taper fortement la vue, imitant la directive @model
dans .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 >
Fournit des appels de style lambda chaînables sur une liste. À partir des tests unitaires :
Enumerable(list) _
.Where( "len(item_) > 5" ) _
.Map( "set V_ = new ChainedExample_Class : V_.Data = item_ : V_.Length = len(item_)" ) _
.Max( "item_.Length" )
V_
est une variable d'instance spéciale utilisée par la méthode Map
pour représenter le résultat de l'expression « lambda ». item_
est une autre variable d'instance spéciale qui représente l'élément en cours de traitement. Ainsi, dans ce cas, Map
parcourt chaque élément de la liste et exécute l'expression « lambda » transmise. Le résultat de Map
est une nouvelle instance de EnumerableHelper_Class
contenant une liste d'instances ChainedExample_Class
construites par l'expression. Cet énumérable est ensuite traité par Max
pour renvoyer une valeur unique, la longueur maximale.
Encapsule les détails de connexion et l’accès à la base de données. En plus des exemples déjà présentés, il peut également gérer :
DAL.Execute "delete from Orders where OrderId = ?", id
set rs = DAL.PagedQuery(sql, params, per_page, page_num)
DAL.BeginTransaction
, DAL.CommitTransaction
et DAL.RollbackTransaction
La classe ferme et détruit également automatiquement la connexion encapsulée via la méthode Class_Terminate
qui est appelée lorsque la classe est prête à être détruite.
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"
Les migrations peuvent être accélérées et réduites via l'interface Web située sur migrate.asp
. Migration.Do
exécute des commandes SQL. Les migrations sont traitées dans l'ordre chargé. Il est recommandé de suivre un schéma de dénomination structuré comme indiqué ci-dessus pour faciliter la commande. Il existe quelques commandes spéciales, telles que Migration.Irreversible
, qui vous permettent d'arrêter une migration vers le bas, etc.
Le projet réel à partir duquel le framework a été extrait contenait environ 3 douzaines de migrations, il a donc très bien fonctionné pour la gestion des versions de la base de données pendant le développement.
Remarque : l'interface Web des migrations est très basique et peu jolie
Dépendance : Pour utiliser la fonctionnalité de migrations, vous devez d'abord créer la table meta_migrations
à l'aide du script [ ! Create Migrations Table.sql
](Sane/Framework/Data/Migrations/! Créer une table de migrations.sql).
Étant donné que l’utilisation du débogage pas à pas n’est pas toujours possible dans Classic ASP, cela facilite grandement le débogage et le traçage.
Dump
génère des objets de manière significative :
dim a : a = GetSomeArray()
Dump a
Sortir:
[Array:
0 => «elt1»
1 => «elt2»
2 => «elt3»
]
Il gère même les classes personnalisées, en utilisant le champ Class_Get_Properties
:
Dump Product
Sortir:
{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»
}
Et il gère l'imbrication, comme on le voit ici lorsqu'un appel à Dump Model
a été placé dans l'action Show
de OrdersController.asp
dans la démo :
{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
interrompt immédiatement l'exécution. die "some message"
arrête l'exécution et affiche un "un message" à l'écran. trace "text"
et comment "text"
écrivent tous deux des commentaires HTML contenant du "texte", utiles pour tracer les coulisses sans perturber la mise en page.
Flash.Success = "Product updated."
, Flash.Errors = model.Validator.Errors
, etc.
Si des erreurs sont rencontrées lors de la création d'un modèle, nous devrions pouvoir réafficher le formulaire avec le contenu de l'utilisateur toujours rempli. Pour simplifier cela, le framework fournit l'objet FormCache
qui sérialise/désérialise les données du formulaire via la session.
Par exemple, dans une action Create
nous pouvons avoir :
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
Et dans 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
encapsule Response.Write
et fait varier sa sortie en fonction du type transmis, avec une sortie spéciale pour les listes et les tableaux.H(string)
HTMLEncode une chaîneAssign(target, src)
élimine le besoin d'utiliser set
pour les objets dans les cas où nous traitons de variables de type arbitraireChoice(condition, trueval, falseval)
est un iif
plus fonctionnelHTML.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)
avec variantes *Ext
Le cadre fournit des outils pour aider à atténuer les trois éléments suivants du Top 10 de l'OWASP :
Database_Class
prend en charge les requêtes paramétrées.H()
est fournie pour un encodage simple de toutes les autres sorties.HtmlSecurity
fournit des vérifications occasionnelles par formulaire et par site pour atténuer cette menace.Les sept vulnérabilités restantes relèvent principalement ou entièrement de la responsabilité du développeur et/ou de l'administrateur.
Un idiome utilisé dans tout le framework est une solution de contournement pour que VBScript n'autorise pas les surcharges de méthodes. De manière générale, il existe deux cas, l'un où la méthode est complète avec plusieurs paramètres et l'autre où la signature de la méthode est simplifiée. Ceci est géré en faisant en sorte que la méthode complète ajoute Ext
à la fin pour la désigner comme une version « étendue » de la méthode simplifiée.
Par exemple, ceci provient 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
Et ceci vient du 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
et Func
construites dynamiquement (dans le but de répondre directement aux critiques de ce type), mais je ne l'ai jamais publié. Brian a repoussé les limites bien au-delà de cela. La LinkedList_Class
et ses itérateurs sont adaptés de son travail, avec ses capacités lambda extrêmement puissantes conçues pour éviter de trop enliser ASP. Le framework adopte également certaines de ses conventions de codage, telles que les suffixes _Class
et l'utilisation de fonctions singleton de portée globale chargées paresseusement.