Dies ist ein MVC-Framework, das vollständig in 100 % VBScript geschrieben ist. Betrachten Sie es als eine Art Spring Boot für Classic ASP.
Sane ist ein MVC-Framework mit relativ umfassendem Funktionsumfang, das klassisches ASP vernünftig macht. Der Stil weist gewisse Ähnlichkeiten mit .NET MVC und Rails auf, ähnelt jedoch keinem der beiden. Es wird davon ausgegangen, dass Controller an einem bestimmten Ordnerspeicherort vorhanden sind, dieser Speicherort jedoch einigermaßen konfigurierbar ist.
Zu den Hauptmerkmalen, die dieses Framework auszeichnen, gehören:
All/Any
boolesche Tests, Min/Max/Sum
, Map/Select
für Projektionen und Where
für Filter, wobei grundlegende Ausdrücke im Lambda-Stil unterstützt werdenUp
und Down
– Versionskontrolle bei DatenbankänderungenScripting.Dictionary
Objekt zu erstellenKVArray
und seine Hilfsmethoden verwenden, um das Erstellen von HTML zu vereinfachenAll dies wurde in VBScript geschrieben. Wirklich.
Sane ist unter den Bedingungen der GPLv3 lizenziert.
Hinweis: Dieses Framework wurde aus einem realen internen Workflow-Routing-Projekt extrahiert und weist daher einige Ecken und Kanten auf.
Dies gibt einen schnellen Überblick über den Codefluss für einen Controller und die von ihm verwendeten Modelle und Ansichten. Es zeigt, wie viele der oben aufgeführten Funktionen tatsächlich zusammen genutzt werden.
„Es gibt viele davon, aber dieses hier ist meins.“
Vor allem, weil es ein interessantes Projekt war, das die Grenzen von Classic ASP verschiebt. Die überwiegende Mehrheit der Entwickler hasst VBScript und Classic ASP, meist aus gutem Grund. Viele der Probleme, die Classic ASP plagen, sind auf die Zwänge der Zeit zurückzuführen, in der es entwickelt wurde, Mitte der 1990er Jahre. Entwickler waren nicht in der Lage, die heute als grundlegende Vorgehensweisen geltenden Methoden (die weitverbreitete Verwendung von Klassen usw.) anzuwenden, da die Sprache nicht für eine Ausführung ausgelegt war, die wir als „schnell“ bezeichnen würden, und die Verwendung dieser Vorgehensweisen dazu führen würde, dass die Anwendung ins Stocken gerät und abstürzt. Aus diesem Grund war die ASP-Community gezwungen, ASP auf die gleiche Weise zu verwenden, wie PHP verwendet wurde – als seitenbasierter Inline-Vorlagenprozessor und nicht als eigenständiges vollständiges Anwendungsframework. Seien wir ehrlich: Microsoft vermarktete ASP bei jedem, unabhängig von seinem Kenntnisstand, und die meisten der online verfügbaren Tutorials waren schrecklich und förderten schrecklich schlechte Praktiken.
Heute wissen wir es besser und dank des Mooreschen Gesetzes hat sich die Rechenleistung seit Mitte der 90er Jahre etwa um das 500-fache erhöht, sodass wir es uns leisten können, Dinge zu tun, die vor ein paar Jahren undenkbar waren.
Dieses Framework wurde aus einem realen Projekt extrahiert, das auf diese Weise erstellt wurde. Es hat ganz gut funktioniert und es sollte (funktionell gesehen) keinen Grund geben, dass es nicht als brauchbares Anwendungsframework funktionieren sollte. Das heißt, wenn wir heute eine Anwendung entwickeln müssen, würden wir realistischerweise ein modernes Framework wie .NET MVC oder einen seiner Konkurrenten verwenden, daher ist dies hier eigentlich nur für den Fall, dass es für jemand anderen hilfreich ist. Außerdem hat es Spaß gemacht, es zu bauen. :) :)
Abhängigkeit: Die Demo wurde anhand der Microsoft Northwind-Beispieldatenbank erstellt. Laden Sie die SQL Server BAK-Datei hier herunter und stellen Sie sie in einer SQL Server-Instanz wieder her. SQL-Skripte und MDF-Dateien sind ebenfalls verfügbar.
File -> Open Web Site...
und wählen Sie das Demo
-Verzeichnis ausAppDALlib.DAL.asp
und ändern Sie die Verbindungszeichenfolge so, dass sie auf Ihre Datenbank verweist./index.asp
Die Datei index.asp
leitet Sie automatisch zum Home-Controller /App/Controllers/HomeController.asp
weiter und lädt die Standardaktion Index
.
Für einige der folgenden Funktionen gibt es entsprechende ASPUnit-Tests. Sie finden sie im Verzeichnis „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
%>
Der Controller kennt nur die Domäne, nicht die Datenbank. Freude!
Aktionen sind Funktionen ohne Parameter (wie Rails und im Gegensatz zu .NET MVC) – Parameter werden wie in herkömmlichem ASP aus dem Request
Objekt gezogen. MVC.RequirePost
können Sie die Aktion so beschränken, dass sie nur auf POST
Anfragen reagiert – andernfalls treten Fehler auf.
MVC.Dispatch
ist der magische Einstiegspunkt zum Framework. Da Ansichten im Controller #include
d sind und eine App 1..n
Controller mit jeweils 1..n
Aktionen haben kann, ist ein zentraler monolithischer MVC-Dispatcher über ein paar einfache Controller hinaus nicht möglich. Dies liegt daran, dass ASP bei jedem Seitenaufruf die gesamte #include
-Datei lädt und kompiliert. Vor diesem Hintergrund delegiert das Framework die Instanziierung stattdessen an die Controller selbst und macht diese für den Start des Frameworks verantwortlich, anstatt das Framework für das Laden und Instanziieren aller Controller sowie das Laden und Kompilieren aller Ansichten für jede Anfrage verantwortlich zu machen. Das Framework instanziiert den Controller zwar dynamisch basierend auf Namenskonventionen, es wird jedoch nur ein Controller pro Anfrage analysiert und geladen, nicht alle Controller. Indem wir nur einen Controller laden, können wir die Einsparungen nutzen, um stattdessen viele hilfreiche Bibliotheken zu laden, die die Entwicklung viel entwicklerfreundlicher machen.
Aufgrund dieses Ansatzes ist die „Routing-Engine“ eigentlich nur eine Klasse, die weiß, wie man URLs zu Controller-Dateien erstellt, also generiert Routes.UrlTo("Orders", "Show", Array("Id", order.Id))
die URL /App/Controllers/OrdersController.asp?_A=Show&Id=123
(für order.Id = 123). URLs verweisen auf Controller und geben über den Parameter _A
den Namen der auszuführenden Aktion an. Aktionsparameter werden über eine KVArray
-Datenstruktur übergeben, bei der es sich einfach um ein Array von Schlüssel/Wert-Paaren handelt, das im gesamten Framework häufig verwendet wird. Hier sind zum Beispiel zwei KVArray
s, die in einem der vielen HTML-Helfer verwendet werden:
<%= HTML.LinkToExt( " View Orders " , _
" Orders " , _
" List " , _
array ( " param1 " , " value1 " , " param2 " , " value2 " ), _
array ( " class " , " btn btn-primary " , " id " , " orders-button " )) %>
Hinter den Kulissen erstellt diese Methode einen Anker, der an die richtige Kombination aus Controller und Aktion weiterleitet, die angegebenen Parameter über den Querystring übergibt und über die angegebenen HTML- class
und id
-Attribute verfügt. KVArray
s sind dank einiger Hilfsmethoden wie KeyVal
und KVUnzip
einfach zu handhaben.
Die KVArray
Datenstruktur ist für einen Großteil des Frameworks von grundlegender Bedeutung und vereinfacht die Codierung erheblich. Im Grunde ist ein KVArray
nichts anderes als ein Standard-VBScript-Array, das immer in Zweiergruppen genutzt werden sollte. Mit anderen Worten: Um ein KVArray
zu erstellen, müssen wir lediglich ein Array erstellen, wobei Element 0 der erste Schlüssel und Element 1 sein Wert, Element 2 der zweite Schlüssel und Element 3 sein Wert usw. sind.
Im Wesentlichen können Sie sich ein KVArray
als eine Möglichkeit vorstellen, Aufrufe im System.Object
-Stil zu verwenden, wie dies in .NETs Html.ActionLink
der Fall ist.
Zum Beispiel:
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"
Aber in Wirklichkeit würden Sie es nie so schreiben, sondern stattdessen den Inline- Array
Konstruktor wie folgt verwenden:
dim params : params = Array( "Name" , "Bob" , "Age" , 35 , "FavoriteColor" , "Blue" )
Oder zur besseren Lesbarkeit:
dim params : params = Array( _
"Name" , "Bob" , _
"Age" , 35 , _
"FavoriteColor" , "Blue" _
)
Um dieses Array Schritt für Schritt zu durchlaufen und KeyVal
zu verwenden, um den aktuellen Schlüssel und Wert abzurufen:
dim idx, the_key, the_val
For idx = 0 to UBound(kvarray) step 2
KeyVal kvarray, idx, the_key, the_val
Next
Bei jeder Iteration enthält the_key
den aktuellen Schlüssel (z. B. „Name“, „Alter“ oder „FavoriteColor“) und the_val
den entsprechenden Wert des Schlüssels.
Aber warum nicht ein Wörterbuch verwenden?
Wörterbücher sind großartig, aber es handelt sich um COM-Komponenten, deren Instanziierung in der Vergangenheit zumindest kostspielig war und die aufgrund des Threadings nicht in die Sitzung eingefügt werden sollten. Außerdem ist die Arbeit mit ihnen für die Anwendungsfälle in diesem Framework umständlich und es gibt keine einfache Möglichkeit, sie inline mit einer dynamischen Anzahl von Parametern zu instanziieren.
Was wir tatsächlich brauchen, ist eine schnelle, nur vorwärts gerichtete Schlüssel-Wert-Datenstruktur, die es uns ermöglicht, über die Werte zu iterieren und jeden Schlüssel und Wert herauszusuchen, um so etwas wie ein HTML-Tag mit beliebigen Attributen oder eine SQL- where
Klausel mit beliebigen Spalten zu erstellen, nicht schnelle Suche nach einzelnen Schlüsseln. Wir benötigen also eine Mischung aus Array und Wörterbuch, die unsere spezifischen Anforderungen erfüllt und die Inline-Deklaration einer beliebigen Anzahl von Parametern ermöglicht. Mit dem KVArray
können wir ganz natürlich Code wie im LinkToExt
-Beispiel oben schreiben oder URLs manuell mit Routes.UrlTo()
erstellen:
<%
< a href = " <%= Routes.UrlTo( " Users " , " Edit " , array( " Id " , user.Id)) %> " >
< i class = " glyphicon glyphicon-user " >< /a >
< /a >
%>
Wir können auch generische Repository- Find
-Methoden erstellen, die wie folgt verwendet werden können:
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 _
) _
)
Beispiele hierfür gibt es in den Demo-Repositories, wo KVUnzip
auch sehr effektiv verwendet wird, um die SQL- where
-Klausel einfach zu erstellen. Das folgende Beispiel stammt von der Methode ProductRepository.Find()
die ein KVArray
mit Prädikat-Schlüssel-Wert-Paaren akzeptiert und es in zwei separate Arrays entpackt , die zum Erstellen der Abfrage verwendet werden:
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
%>
Validieren Sie Modelle, indem Sie die entsprechende Validate*
-Hilfsmethode im Class_Initialize
Konstruktor des Modells aufrufen:
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
Derzeit sind nur ValidateExists
, ValidateMinLength
, ValidateMaxLength
, ValidateNumeric
und ValidatePattern
enthalten. Was diese Hilfsmethoden tatsächlich tun, ist, eine neue Instanz der entsprechenden Validierungsklasse zu erstellen und sie an die Validator
Eigenschaft des Modells anzuhängen. Wenn ein Modell beispielsweise eine Validierung mit ValidateExists Me, "Name", "Name must exist."
Folgendes passiert tatsächlich hinter den Kulissen:
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
Hier ist Me
die Domänenmodellinstanz. Die Validator_Class
wird dann (über YourModel.Validator
) verwendet, um alle registrierten Validierungsregeln zu validieren und die Felder Errors
und HasErrors
zu setzen, wenn Fehler gefunden werden. Dies ähnelt dem Observer-Muster. Der Grund, warum wir Me
übergehen, ist, dass wir dadurch für jede Validierung eine bequem formulierte Methode mit starker semantischer Bedeutung haben können, z. B. ValidateExists
. Es erfordert ein wenig Code-Jutsu, aber es lohnt sich.
Das Hinzufügen neuer Validierungen ist einfach. Fügen Sie einfach eine neue Validierungsklasse und den Hilfsassistenten Sub
hinzu. Um beispielsweise eine Validierung hinzuzufügen, die erfordert, dass eine Zeichenfolge mit dem Buchstaben „A“ beginnt, würden Sie eine StartsWithLetterAValidation_Class
und die Hilfsmethode Sub ValidateStartsWithA(instance, field_name, message)
erstellen und sie dann über ValidateStartsWithA Me, "MyField", "Field must start with A."
aufrufen ValidateStartsWithA Me, "MyField", "Field must start with A."
Domänenmodelle können erstellt werden, indem ein ADO-Recordset über Transformationen im Automapper-Stil in eine verknüpfte Liste von Domänenmodellen konvertiert wird. Sag was?
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
Die Verwendung des empty
Schlüsselworts ist ein gängiger Ansatz dieses Frameworks. Eine häufige Beschwerde über VBScript ist, dass es keine optionalen Parameter zulässt. Dies ist zwar technisch gesehen richtig, lässt sich aber leicht umgehen, doch praktisch jedes online gefundene Beispiel beinhaltet die Übergabe leerer Zeichenfolgen oder Nullwerte oder eines ähnlichen Ansatzes. Die Verwendung des integrierten VBScript-Schlüsselworts empty
ist eine semantisch sinnvolle Möglichkeit, optionale Parameter zu verarbeiten, und macht deutlich, dass wir ausdrücklich beabsichtigt haben, den optionalen Parameter zu ignorieren. In diesem Fall akzeptiert die DAL.Query
-Methode zwei Parameter, die SQL-Abfrage und einen optionalen zweiten Parameter, der Bindungswerte enthält. Der zweite Parameter kann entweder ein einzelner Wert wie in DAL.Query("select a from b where a = ?", "foo")
oder ein Array von Bindungen sein, z. B. DAL.Query("select a from b where a = ? and c = ?", Array("foo", "bar")
. Im obigen Beispiel wird es explizit ignoriert, da es in der SQL keine Bind-Variablen gibt.
In diesem Beispiel ist die DAL
Variable einfach eine Instanz der Database_Class
aus lib.Data.asp
. Im ursprünglichen Projekt war die DAL eine benutzerdefinierte Klasse, die als Einstiegspunkt für eine Reihe verzögert geladener Database_Class
Instanzen fungierte und die gemeinsame Nutzung und Verschiebung von Daten zwischen Datenbanken während des Workflows ermöglichte.
Das Automapper
-Objekt ist eine VBScript-Klasse, die versucht, jedes Feld im Quellobjekt einem entsprechenden Feld im Zielobjekt zuzuordnen. Das Quellobjekt kann ein Recordset oder eine benutzerdefinierte Klasse sein. Die Funktion kann einem neuen oder vorhandenen Objekt zugeordnet werden. Das Automapper
-Objekt enthält drei Methoden: AutoMap
, das versucht, alle Eigenschaften abzubilden; FlexMap
können Sie eine Teilmenge der zuzuordnenden Eigenschaften auswählen, z. B. Automapper.FlexMap(rs, new OrderModel_Class, array("DateOrdered", "CustomerName"))
kopiert nur die beiden angegebenen Felder aus dem Quell-Recordset in die neue Modellinstanz ; und DynMap
, mit dem Sie Werte dynamisch neu zuordnen können. Ein künstliches Beispiel finden Sie unter:
Automapper.DynMap(rs, new OrderModel_Class, _
array( "target.CustomerName = UCase(src.CustomerName)" , _
"target.LikedOrder = src.CustomerWasHappy" ))
Da sowohl Quelle als auch Ziel jedes Objekt mit Instanzmethoden sein kann, ist dies eine sehr nützliche Möglichkeit, die Modellbindung in CRUD-Methoden zu verwalten, zum Beispiel:
Public Sub CreatePost
dim new_product_model : set new_product_model = Automapper.AutoMap(Request.Form, new ProductModel_Class)
... etc
End Sub
Da es #include
in die Controller-Aktion aufgenommen wird, hat die Ansicht vollen Zugriff auf die Model
des Controllers. Hier greift es auf die Order
Eigenschaft des Ansichtsmodells zu und iteriert über die LineItems
-Eigenschaft (die eine im Repository erstellte LinkedList_Class
-Instanz wäre), um die Ansicht zu erstellen. Mithilfe von Ansichtsmodellen können Sie umfangreiche Ansichten erstellen, die nicht an eine bestimmte Recordset-Struktur gebunden sind. Sehen Sie sich den HomeController
in der Demo an, um ein Beispielansichtsmodell zu sehen, das vier separate Listen von Domänenobjekten enthält, um eine Dashboard-Zusammenfassungsansicht zu erstellen.
Die MVC.RequireModel
-Methode bietet die Möglichkeit, die Ansicht stark zu typisieren und die @model
Direktive in .NET MVC nachzuahmen.
<% 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 >
Bietet verkettbare Aufrufe im Lambda-Stil für eine Liste. Aus den Unit-Tests:
Enumerable(list) _
.Where( "len(item_) > 5" ) _
.Map( "set V_ = new ChainedExample_Class : V_.Data = item_ : V_.Length = len(item_)" ) _
.Max( "item_.Length" )
V_
ist eine spezielle Instanzvariable, die von der Map
Methode verwendet wird, um das Ergebnis des „Lambda“-Ausdrucks darzustellen. item_
ist eine weitere spezielle Instanzvariable, die das aktuell verarbeitete Element darstellt. In diesem Fall durchläuft Map
also jedes Element in der Liste und führt den übergebenen „Lambda“-Ausdruck aus. Das Ergebnis von Map
ist eine neue Instanz von EnumerableHelper_Class
die eine Liste von ChainedExample_Class
-Instanzen enthält, die durch den Ausdruck erstellt wurden. Diese Aufzählung wird dann von Max
verarbeitet, um einen einzelnen Wert zurückzugeben, die maximale Länge.
Umschließt Verbindungsdetails und Zugriff auf die Datenbank. Zusätzlich zu den bereits gezeigten Beispielen kann es auch Folgendes verarbeiten:
DAL.Execute "delete from Orders where OrderId = ?", id
set rs = DAL.PagedQuery(sql, params, per_page, page_num)
DAL.BeginTransaction
, DAL.CommitTransaction
und DAL.RollbackTransaction
Die Klasse schließt und zerstört die umschlossene Verbindung außerdem automatisch über die Methode Class_Terminate
, die aufgerufen wird, wenn die Klasse zur Zerstörung bereit ist.
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"
Migrationen können über die Weboberfläche unter migrate.asp
schrittweise erhöht und verringert werden. Migration.Do
führt SQL-Befehle aus. Migrationen werden in der geladenen Reihenfolge verarbeitet. Um die Bestellung zu vereinfachen, empfehlen wir Ihnen, ein strukturiertes Benennungsschema wie oben gezeigt zu befolgen. Es gibt einige spezielle Befehle, wie z. B. Migration.Irreversible
, mit denen Sie die Fortsetzung einer Downmigration usw. stoppen können.
Das reale Projekt, aus dem das Framework extrahiert wurde, enthielt etwa drei Dutzend Migrationen, sodass es für die Versionierung der Datenbank während der Entwicklung sehr gut funktionierte.
Hinweis: Die Weboberfläche der Migrationen ist sehr einfach und nicht hübsch
Abhängigkeit: Um die Migrationsfunktion nutzen zu können, müssen Sie zunächst die Tabelle meta_migrations
mit dem Skript [ ! Create Migrations Table.sql
](Sane/Framework/Data/Migrations/! Migrationen Table.sql erstellen).
Da die Verwendung von Step-Through-Debugging in Classic ASP nicht immer möglich ist, erleichtern diese das Debuggen und Tracing erheblich.
Dump
gibt Objekte auf sinnvolle Weise aus:
dim a : a = GetSomeArray()
Dump a
Ausgabe:
[Array:
0 => «elt1»
1 => «elt2»
2 => «elt3»
]
Es verarbeitet sogar benutzerdefinierte Klassen mithilfe des Felds Class_Get_Properties
:
Dump Product
Ausgabe:
{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»
}
Und es kümmert sich um die Verschachtelung, wie hier zu sehen ist, als in der Demo ein Aufruf von Dump Model
in der Show
Aktion von OrdersController.asp
platziert wurde:
{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
stoppt die Ausführung sofort. die "some message"
stoppt die Ausführung und gibt eine „some message“ auf dem Bildschirm aus. trace "text"
und comment "text"
schreiben beide HTML-Kommentare, die „text“ enthalten. Dies ist nützlich, um hinter den Kulissen nachzuverfolgen, ohne das Layout zu stören.
Flash.Success = "Product updated."
, Flash.Errors = model.Validator.Errors
usw.
Wenn beim Erstellen eines Modells Fehler auftreten, sollten wir in der Lage sein, das Formular erneut anzuzeigen, während der Inhalt des Benutzers noch ausgefüllt ist. Um dies zu vereinfachen, stellt das Framework das FormCache
Objekt bereit, das Formulardaten über die Sitzung serialisiert/deserialisiert.
In einer Aktion Create
können wir beispielsweise Folgendes haben:
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
Und in 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
umschließt Response.Write
und variiert seine Ausgabe basierend auf dem übergebenen Typ, mit spezieller Ausgabe für Listen und Arrays.H(string)
HTMLCodiert einen StringAssign(target, src)
abstrahiert die Notwendigkeit, set
für Objekte zu verwenden, wenn wir mit Variablen beliebigen Typs arbeitenChoice(condition, trueval, falseval)
ist ein funktionaleres 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
-VariantenEdit
: HTMLSecurity.SetAntiCSRFToken "ProductEditForm"
<%= HTML.Hidden("nonce", HTMLSecurity.GetAntiCSRFToken("ProductEditForm")) %>
EditPost
Aktion: HTMLSecurity.OnInvalidAntiCsrfTokenRedirectToActionExt "ProductEditForm", Request.Form("nonce"), "Edit", Array("Id", Request.Form("Id"))
MVC.ControllerName
, MVC.ActionName
MVC.RedirectTo(controller_name, action_name)
oder MVC.RedirectToActionPOST(action_name)
mit *Ext
-VariantenDas Framework bietet Tools, die dabei helfen, die folgenden drei Punkte aus den OWASP Top 10 zu mindern:
Database_Class
unterstützt parametrisierte Abfragen.H()
-Methode wird für die einfache Kodierung aller anderen Ausgaben bereitgestellt.HtmlSecurity
Helfer bietet Nonce-Prüfungen pro Formular und pro Site, um diese Bedrohung zu mindern.Die verbleibenden sieben Schwachstellen liegen größtenteils oder vollständig in der Verantwortung des Entwicklers und/oder Administrators.
Eine im gesamten Framework verwendete Redewendung ist eine Problemumgehung dafür, dass VBScript keine Methodenüberladungen zulässt. Im Allgemeinen gibt es zwei Fälle: einen, in dem die Methode alle Funktionen mit mehreren Parametern aufweist, und einen, in dem die Methodensignatur vereinfacht ist. Dies wird dadurch gehandhabt, dass die Methode mit vollem Funktionsumfang Ext
an das Ende anhängt, um sie als „erweiterte“ Version der vereinfachten Methode zu kennzeichnen.
Dies stammt beispielsweise aus der 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
Und das ist aus der 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
und Func
Klassen implementiert (um direkt auf die Kritik dieses Mannes einzugehen), es aber nie veröffentlicht. Brian hat die Grenzen weit darüber hinaus verschoben. Die LinkedList_Class
und ihre Iteratoren sind aus seiner Arbeit übernommen, wobei seine extrem leistungsstarken Lambda-Fähigkeiten darauf zugeschnitten sind, ASP nicht zu sehr zu belasten. Das Framework übernimmt auch einige seiner Codierungskonventionen, wie z. B. _Class
Suffixe und die Verwendung von verzögert geladenen Singleton-Funktionen mit globalem Gültigkeitsbereich.