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