هذا إطار عمل 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 من قيود الوقت الذي تم تطويره فيه، في منتصف التسعينيات. لم يتمكن المطورون من استخدام ما يعتبر اليوم ممارسات أساسية (استخدام الفئات على نطاق واسع، وما إلى ذلك) لأن اللغة لم تكن مصممة للتنفيذ بطريقة يمكن أن نسميها "سريعة" واستخدام هذه الممارسات قد يتسبب في تعطل التطبيق وتعطله. وبسبب هذا، اضطر مجتمع ASP إلى استخدام ASP بنفس الطريقة التي تم بها استخدام PHP - كمعالج قوالب مضمن قائم على الصفحة، وليس إطار عمل تطبيق كامل في حد ذاته. بالإضافة إلى ذلك، لنكن صادقين، قامت Microsoft بتسويق ASP للجميع بغض النظر عن مستوى المهارة، وكانت معظم البرامج التعليمية الموجودة عبر الإنترنت فظيعة وشجعت على ممارسات سيئة للغاية.
اليوم نحن نعرف أفضل، وبفضل قانون مور، ارتفعت قوة الحوسبة بنحو 500 ضعف منذ منتصف التسعينيات، حتى نتمكن من القيام بأشياء لم يكن من الممكن تصورها قبل بضع سنوات.
تم استخراج هذا الإطار من مشروع حقيقي تم بناؤه بهذه الطريقة. لقد نجح الأمر بشكل جيد، ولا ينبغي أن يكون هناك سبب (من الناحية الوظيفية) يمنعه من العمل كإطار تطبيق قابل للتطبيق. ومع ذلك، من الناحية الواقعية، إذا كنا بحاجة إلى تطوير تطبيق ما اليوم، فسنستخدم إطارًا حديثًا مثل .NET MVC أو أحد منافسيه، لذا فهذا موجود هنا فقط في حالة أنه مفيد لشخص آخر. بالإضافة إلى أنه كان ممتعًا في البناء. :)
التبعية: تم إنشاء العرض التوضيحي مقابل نموذج قاعدة بيانات Microsoft Northwind. قم بتنزيل ملف SQL Server BAK هنا واستعادته إلى مثيل SQL Server. تتوفر أيضًا برامج نصية SQL وملف MDF.
File -> Open Web Site...
وحدد الدليل Demo
AppDALlib.DAL.asp
وقم بتعديل سلسلة الاتصال للإشارة إلى قاعدة البيانات الخاصة بك./index.asp
سيعيد الملف index.asp
توجيهك تلقائيًا إلى وحدة التحكم الرئيسية، /App/Controllers/HomeController.asp
وسيقوم بتحميل الإجراء الافتراضي، Index
.
تحتوي بعض الميزات أدناه على اختبارات ASPUnit مقابلة. ابحث عنها في دليل الاختبارات.
<%
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
هي نقطة دخول الصلصة السحرية إلى إطار العمل. نظرًا لأن طرق العرض #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
(للطلب.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
المضمن مثل هذا:
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
على المفتاح الحالي (على سبيل المثال، "الاسم" أو "العمر" أو "اللون المفضل") وسيحتوي 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*
المناسب من داخل مُنشئ 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
في حالة العثور على أخطاء. وهذا مشابه لنمط المراقب. سبب اجتيازنا Me
هو أن هذا يتيح لنا الحصول على طريقة ذات صياغة ملائمة لكل عملية تحقق لها معنى دلالي قوي، على سبيل المثال ValidateExists
. يتطلب الأمر القليل من تقنيات البرمجة ولكنه يستحق ذلك.
من السهل إضافة عمليات تحقق جديدة، ما عليك سوى إضافة فئة تحقق جديدة ومساعد Sub
. على سبيل المثال، لإضافة عملية تحقق من الصحة تتطلب أن تبدأ سلسلة بالحرف "A"، يمكنك إنشاء StartsWithLetterAValidation_Class
وأسلوب مساعد Sub ValidateStartsWithA(instance, field_name, message)
، ثم استدعائها عبر ValidateStartsWithA Me, "MyField", "Field must start with A."
يمكن إنشاء نماذج المجال عن طريق تحويل مجموعة سجلات ADO إلى قائمة مرتبطة بنماذج المجال عبر تحويلات نمط 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 هي أنها لا تسمح بالمعلمات الاختيارية. في حين أن هذا صحيح من الناحية الفنية، إلا أنه من السهل التغلب عليه، إلا أن كل مثال موجود عبر الإنترنت تقريبًا يتضمن تمرير سلاسل فارغة، أو قيم فارغة، أو أسلوب مشابه. يعد استخدام الكلمة الأساسية 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
المحملة ببطء، مما يسمح بمشاركة البيانات ونقلها بين قواعد البيانات أثناء سير العمل.
كائن 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 >
يوفر مكالمات قابلة للتسلسل على غرار لامدا في القائمة. من اختبارات الوحدة:
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 عشرات من عمليات الترحيل، لذلك كان يعمل بشكل جيد جدًا لإصدار قاعدة البيانات أثناء التطوير.
ملحوظة: واجهة الويب الخاصة بالترحيلات أساسية جدًا وغير جميلة
التبعية: لاستخدام ميزة الترحيل، يجب عليك أولاً إنشاء جدول meta_migrations
باستخدام البرنامج النصي [ ! Create Migrations Table.sql
](Sane/Framework/Data/Migrations/! إنشاء جدول عمليات الترحيل.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"
يوقف التنفيذ ويخرج "رسالة ما" إلى الشاشة. يقوم كل من 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
يلتف 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
عمليات فحص لكل نموذج ولكل موقع للتخفيف من هذا التهديد.تقع نقاط الضعف السبعة المتبقية في الغالب أو كليًا على عاتق المطور و/أو المسؤول.
أحد المصطلحات المستخدمة في إطار العمل هو الحل البديل لعدم السماح لـ 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
المبنية ديناميكيًا (بقصد معالجة انتقادات هذا الرجل بشكل مباشر) ولكنني لم أنشرها مطلقًا. لقد دفع برايان الحدود إلى أبعد من ذلك. تم تكييف LinkedList_Class
ومكرراتها من عمله، مع إمكانات lambda القوية للغاية المصممة لتجنب إعاقة ASP كثيرًا. يتبنى الإطار أيضًا بعضًا من اصطلاحات الترميز الخاصة به، مثل لاحقات _Class
واستخدام وظائف مفردة ذات نطاق عالمي محملة ببطء.