هذا إطار عمل 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... وحدد الدليل DemoAppDALlib.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 = ?", idset 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 Subput يلتف Response.Write ويغير مخرجاته بناءً على النوع الذي تم تمريره، مع مخرجات خاصة للقوائم والمصفوفات.H(string) HTMLترميز سلسلةAssign(target, src) بتجريد الحاجة إلى استخدام set للكائنات في الحالات التي نتعامل فيها مع متغيرات من النوع التعسفيChoice(condition, trueval, falseval) هو أكثر فعالية iifHTML.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)*ExtEdit : 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.ActionNameMVC.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 واستخدام وظائف مفردة ذات نطاق عالمي محملة ببطء.