Classic ASP MVC 用于动态 JavaScript 页面
Classic ASP 重启。
引言
本主题是为 Classic ASP 程序员创建一个完全动态的 JavaScript 应用程序的“操作指南”。
背景
在 上一主题 中讨论的 Classic ASP VBScript 应用程序中存在 MVC 模式。 [^]
现在我们将把 MVC 模式 应用到 Classic ASP 应用程序,并使用 JavaScript 作为服务器端和客户端代码的语言。
本主题的价值在于为那些使用 Classic ASP IIS 应用程序的开发者提供服务器端 MVC JavaScript Classic ASP 和客户端 MVC JavaScript 应用程序的经验。
使用代码
- 将存档下载并解压缩到 IIS 虚拟目录。
- 为虚拟目录设置默认页面 (default.asp)
- 设置 404 错误处理程序 (router.asp)
- 创建数据模型,并使用 在线 Classic ASP 代码生成器 生成您自己的 Classic ASP JavaScript MVC 应用程序。
服务器端代码
路由器是 MVC Web 应用程序的入口点。
路由
- 解析 URL,计算 Controller/Action
- 将执行传递给带参数的 Controller/Action
在 router.asp 中将会有一些其他函数(因为它是应用程序的单一入口点)
- 加载所需的库
- 加载 Controllers
- 加载 Model
- 认证/授权用户
- 表单清理等。
您需要捕获 404 IIS 错误以启用漂亮的 URL。
您可以手动设置 IIS 错误处理程序或使用脚本。 此处 有关于如何设置 404 错误处理程序以通过自定义 Classic ASP 路由器路由漂亮格式化 URL 的说明。(从菜单中选择“架构师”。)
让我们将服务器端路由器存储在 /Router.asp 文件中。
Controller/Action
应用程序中通常有许多不同的 Controllers。Controllers 是包含应用程序业务逻辑的类。Actions 是这些类中的函数。
{Controller/Actions 业务逻辑的示例}
<% var SomethingController = { About: function(){ var model = someCalculation(); //render model in the view %> <!--#include file="../Views/Something/About.asp" --> <% }, //read operation Read: function(callback){ var model = {data : {items: SomethingHelper.SelectAll()} }; //send model as JSON data callback(JSON.stringify(model),"application/json") ; }, //create operation Create: function(callback){ var model = {data : new Something()}; callback(JSON.stringify(model),"application/json") ; }, //create operation - put the model into DB CreatePost: function(callback){ var obj = getArguments(); var result = createModelSomething(obj); var model = {result : result, data: obj }; callback(JSON.stringify(model),"application/json") ; }, //update operation - get the model from DB Edit: function (callback,vars){ var model = {data: readModelSomething(vars)}; callback(JSON.stringify(model),"application/json") ; }, //update operation - put the model back to DB EditPost: function (callback){ var obj = getArguments(); var result = updateModelSomething(obj); var model = {result : result, data: obj }; callback(JSON.stringify(model),"application/json") ; }, //basic delete operation - get the model from DB Delete: function(callback,vars){ var model = {data: updateModelSomething(vars)}; callback(JSON.stringify(model),"application/json") ; }, //basic delete operation - delete the object from DB DeletePost: function(callback){ var obj = getArguments(); var result = deleteModelSomething(obj); var model = {result : result, data: obj }; callback(JSON.stringify(model),"application/json") ; } }; //list of allowed controller/action controllers['Something']=SomethingController; %>
在 MVC 模式中,Controller 处理用户输入,操作 Model,并更新 View。
早期的 Web MVC 模式采用了瘦客户端的方法,将整个 Model、View 和 Controller 逻辑放在服务器上。
在这种方法中,客户端通过 Router 发送“get”或“post”请求到 Controller,然后接收一个完整且更新的网页。
这种瘦客户端方法将用于显示整个页面。例如,默认的服务器端 HomeController.Index() 将显示示例应用程序的主页。
服务器端 Controller、Action 和 View 生成整个页面时的逻辑
一旦您可能想为您的网页添加更多活力。客户端 JavaScript 代码将有所帮助。客户端代码将向服务器发送 AJAX 请求并更新 DOM(文档对象模型)。
然后,在进行了一些更新/添加功能后,客户端代码会变得混乱、结构不良、难以维护。为了解决这个问题,同样,也曾有过组织客户端代码的努力。
随着客户端技术的成熟,出现了许多客户端 MVC 框架,它们允许 MVC 组件部分地在客户端执行。
您可能听说过 Backbone.js、Knockout、AngularJs 等。
在本主题中,它们都不会被使用,但纯粹的 MVC 模式将应用于客户端 JavaScript 代码。
大量的客户端代码也会影响服务器端代码:大多数服务器端 Controllers/Actions 将返回数据而不是编译后的 HTML。
对于 JavaScript 代码,使用 JSON 传输数据是很自然的,因此示例应用程序中的大多数服务器端控制器将向客户端返回 JSON 数据。服务器将不会将 Model 与 Views 合并以生成 HTML。
以下示例显示了服务器端 Controller 和 Action 生成 JSON 字符串
服务器端 Model
MVC 模式中“Model”一词有广义和狭义之分。
广义是指用于数据库对象的 CRUD(创建、读取、更新、删除)操作以及数据库本身的类集。
“Model”一词的狭义是指我们一次性发送到客户端的数据。它可以是序列化为 JSON 的对象(如上例),也可以是操作的结果 true/false,或者服务器可能用 HTML 模板显示或发送到客户端的任何其他数据。
我将使用类集作为数据模型。这些类将包含具有 SQL 子句的方法,用于在关系数据库中更新/检索数据。数据模型的文件将存储在 /Server/Model/(Name).asp 文件中。
服务器端 View
虽然现在选择模板引擎时有多种选择,但在本主题中我将使用 Classic ASP 模板。
Classic ASP 引擎在解析 .ASP 文件时,会提取并执行 <% %> 标签之间的代码。
我将使用“服务器端 View”这个词来称呼逻辑较少、内容较多的 .asp 页面。我在这里使用的代码生成器创建了一个单一的服务器端视图 /Server/View/Home/Index.asp
客户端代码
当用户从服务器请求页面时,浏览器会向服务器发送请求,其中包含请求资源的 URL。它构成了用户与服务器端代码之间的交互协议。
使用客户端 JavaScript 代码,这种交互通常建立在绑定到 HTML 控件的事件之上,例如 onclick 事件 [^]。像 JQuery 这样的框架可以帮助您将事件设置为适当的客户端事件处理程序。
但是,为了应用 MVC 模式,应该有一种通用方法来捕获用户交互,将其转换为对 Router 的调用。在本主题中,客户端 URL 将用于发出用户请求信号。
URL 的客户端部分。
URL 包含标识服务器端资源和客户端资源的这部分。客户端资源通常被称为“同页面链接”或“命名锚点”。例如 URL
https:///Home/Index/#User/List
包含服务器端部分
https:///Home/Index/
和客户端部分
#User/List.
当用户单击带有此类 URL 的链接时,如果服务器端部分与当前页面位置不同,浏览器将请求一个新页面。
如果服务器端部分未更改,浏览器将不会请求页面,而是会尝试在页面上找到锚点并将其焦点设置在其上。
在本主题中,我们将修改此行为。Jquery-hashchange-plugin 将捕获 URL 客户端部分的变化并将它们发送到客户端 Router。
{钩子到 URL 变化的 code}
$(document).ready(function() { // Bind the event. $(window).hashchange(function() { route(); }) });
路由
客户端 Router 将解析 URL 的客户端部分并将执行传递给客户端 Controller/Action。
{客户端 Router 的 code}
var controllers = {}; function route() { var defaultController = "" var defaultAction = "" var controller = null; var action = null; var vars = null; var globalpath = location.hash.split('#'); if (globalpath.length < 2) return; //the path should be like: /Controller/Action/var1/var2/var3 var path = globalpath[1]; //normalyze path if (path.charAt(0) != "/") path = "/" + path; var pathchuncks = path.split('/'); if (pathchuncks.length) { if (pathchuncks.length >= 3) { controller = pathchuncks[1]; //suffix 'Controller' is not used on client action = pathchuncks[2]; } if (pathchuncks.length > 3) { vars = pathchuncks.slice(3).join('/'); } else {vars='';} }; if (!controller) { controller = defaultController; }; if (!action) { action = defaultAction; }; //alert(controller); //alert(action); if (controllers[controller]){ if (controllers[controller][action]){ var ControllerAction = controllers[controller][action]; ControllerAction(null,vars); } } }
当一个 controller 被初始化时,它被添加到允许的 controllers 列表中。
var ProjectController = { List: function(callback, args){ ... }, Create: function(callback, args){ ... }, CreatePost: function(callback, args){ ... }, ... Details: function(callback, args){ ... } } //add to list of allowed controllers/actions controllers['Project']=ProjectController;
所以,在这一行
var ControllerAction = controllers['Project']['List'];
变量 ControllerAction 获取 ProjectController.List() 函数的指针,该函数在下一行被调用。
ControllerAction(null,vars);使用了javascript 对象方括号表示法。
客户端路由器位于 /Client/router.js 文件中
客户端 Controllers/Actions
当用户单击链接且 URL 已更改时,“hashchange”事件将被触发,并调用客户端 Router。Router 解析 URL。它调用客户端 Controller/Action。
客户端 Controller/Action 可以访问 DOM(文档对象模型)并更新界面,但它应该首先获取 Model 和 View 以生成 HTML。
因此,将进行 2 次对服务器的调用以获取 Model 和 View。Model 是服务器端 Controller/Action 发送的 JSON,而 View 只是服务器文件系统中的一个文件。
客户端 Controller/Action 执行以下操作:
- 准备回调函数,以便在 Model 和 View 就绪时更新 DOM。
- 调用服务器端 controller/action 并通过 ModelProxy 接收 JSON 数据作为 Model。
- 调用服务器端 controller/action 以通过 ViewProxy 检索 View。
- 当回调被调用,并且 Model 和 View 都就绪时,客户端 Controller/Action 将 Model 渲染到 View 中以生成 HTML - 页面的一个部分。
- 使用新内容更新页面部分。
客户端 Controller ProjectController /action List 的示例
var ProjectController = { List: function(callback, args){ var view=null, model=null; //3. this is called via callback when the Model or View is ready. // thus it is called twice. When both Model and View are ready - then the final part: //locally render the model in the view when they are ready, and update the DOM var UpdateDOM = function(Model,View) { if (Model != null){ model = Model; } if (View != null){ view = View; } if ((model != null) && (view != null)) { //render the Model into the Template var templateOutput = Mustache.render(view, model); //update the DOM: $("#dialog1-body").empty(); $("#dialog1-body").append(templateOutput); $("#dialog1-label").empty().append("list of Project"); $("#dialog1").modal("show"); NormalizeHash(); } } //1. this is called first //call for the model, async ModelProxy(UpdateDOM, 'Project','List', args, 'get'); //2. this is called second, simultaneously with the first call //get the view, async ViewProxy(UpdateDOM, 'Project','List'); }, //get the view and model and update the DOM Create: function(callback, args){ var view=null, model=null; //locally render the model in the view when they are ready, and update the DOM var UpdateDOM = function(Model,View) { if (Model != null){ model = Model; } if (View != null){ view = View; } if ((model != null) && (view != null)) { var templateOutput = Mustache.render(view, model); $("#dialog2-body").empty(); $("#dialog2-body").append(templateOutput); $("#dialog2-label").empty().append(""); $("#dialog2").modal("show"); NormalizeHash(); } } //call for the model, async ModelProxy(UpdateDOM, 'Project','Create', args, 'get'); //get the view, async ViewProxy(UpdateDOM, 'Project','Create'); }, //this is called from dynamic script from the /Client/Views/Project/Create.html CreatePost: function(callback, args){ var view=null, model=null; //locally render the model in the view when they are ready, and update the DOM var UpdateDOM = function(Model,View) { if (Model != null){ model = Model; } if (View != null){ view = View; } //may show the form/components on the client here if ((model != null) && (view != null)) { if (model.result == true) { $("#dialog2-body").empty(); $("#dialog2").modal("hide"); NormalizeHash(); var templateOutput = Mustache.render(view, model); //find the table Projectdatatable //find the last row and inset the row with attribute rowid='id' if ($("#Projectdatatable")[0]!=undefined){ $("#Projectdatatable tr:last").after(templateOutput); bindEvents(); } } if (model.result == false) { var templateOutput = Mustache.render(view, { error: model.result }); $("#dialog2-label").empty().append(templateOutput); } } } //call for the model, async ModelProxy(UpdateDOM, 'Project','CreatePost', args, 'post'); //get the view, async ViewProxy(UpdateDOM, 'Project','CreatePost'); }, ... } //add to list of allowed controllers/actions controllers['Project']=ProjectController;
架构概览
客户端 View (mustache 模板)
客户端 controller 通过 ModelProxy 接收带有数据的 JSON 字符串。这些数据应转换为 HTML。
可以在 JavaScript 中解析 JSON 数据为 HTML。但是,更好的方法是使用模板和专门的模板库。
有许多 JavaScript 模板引擎:dustjs、underscore.js、jade.js、mustache 等。在本主题中,我将使用 **mustache**。
将客户端 Views 存储在 /Client/Views/(Controller)/(Action).html 中很方便
Views 存储在服务器上,但它们在客户端代码中转换为 HTML。这就是为什么它们被称为客户端 Views。
在客户端 Controller/Action 生成 HTML 后,它会更新 DOM。例如,它可以是:显示从服务器接收的数据,显示将表单发布到服务器时的状态,在表中添加/删除行,显示/隐藏对话框、菜单等。
一组客户端 controllers 将位于文件 /Client/Controllers/(nameController).js 中
我将使用的代码生成器为每个 controller 创建一个文件。
客户端视图 /Views/Project/List.html 的示例
{{#data}}<div id='ProjectListContainer'> <ul class="pagination pagination-sm pull-right">
- {{#previous}}
- {{/previous}} {{#links}}
- {{n}} {{/links}} {{#next}}
- {{/next}}
ProjectName | POP3Address | Active | |
{{ProjectName}} | {{POP3Address}} | {{Active}} |
这些模板的工作方式类似于 Web Applet。它们可以包含 HTML 以及支持的客户端 JavaScript 代码。当生成的 HTML 添加到 DOM 时,两者都会动态加载。模板在缓存方面效果更好,如后面讨论的。
正如您所见,一部分客户端逻辑存储在 Controllers/Actions 之外。架构受到了影响,但编码更容易。
其他注意事项
典型的基于服务器的 MVC 应用程序具有非常简单的用户和服务器代码之间的接口:只有一个 get 或 post HTTP 请求/响应。
浏览器发出请求,服务器发送响应,然后浏览器加载整个页面,然后用户单击链接或提交表单。
然而,对于具有大量客户端代码的应用程序,不存在这种简洁性。一个页面可以与多个服务器端 URL 进行数据获取和发布,而无需刷新整个页面。
具有大量客户端代码并采用 MVC 模式构建的应用程序通常称为“单页应用程序”。它并不意味着应用程序应该只有一个页面。它意味着视角(或注意力焦点)应该转移到您正在处理的页面上。
您应该查找您页面的资源。开发变得非常以页面为中心。在页面上,重点在于以下问题:
- 页面应该调用哪里来获取用户输入的表单
- 或者页面应该从哪里获取列表数据
- 或者表单应该将用户输入发送到哪里,等等。
页面变得非常类似于 Microsoft 的 WebForms 及其回调。然而,最大的区别在于,这里的事件被驱动到多个服务器端 controllers/actions,请求是从客户端代码发送的(无需重新加载页面),界面的更新发生在客户端代码中。
让我们看看这个系统如何处理标准任务,例如:获取、显示和发送表单,加载和显示列表和网格,加载重型 bootstrap 轮播滑块等。
表单
表单显示为页面上的 bootstrap 模态对话框。代码从服务器加载 Model 和 Template,生成 HTML 表单并更新 DOM:将带有表单的 HTML 片段插入对话框。
显示编辑表单的代码示例 (/Client/Controllers/Project.js)
//get the view and model and update the DOM Edit: function(callback, args){ var view=null, model=null; var UpdateDOM = function(Model,View) { if (Model != null){ model = Model; } if (View != null) { view = View; } if ((model != null) && (view != null)) { var templateOutput = Mustache.render(view, model); $("#dialog2-body").empty(); $("#dialog2-body").append(templateOutput); $("#dialog2-label").empty().append("Update Project"); $("#dialog2").modal("show"); NormalizeHash(); } } //call for the model, async ModelProxy(UpdateDOM, 'Project','Edit', args, 'get'); //get the view, async ViewProxy(UpdateDOM, 'Project','Edit'); }
客户端代码验证表单,并通过 POST 请求将其发送到服务器。表单的发布发生在单独的客户端 POST Controller/Action 中。
处理“编辑”表单值的代码示例 (/Client/Views/Project/Edit.html)
var SubmittingProjectEditForm = function() { //alert("The form has been validated. Now send the form..."); //make an AJAX call var args = {id: $('#ProjectEditContainer #Projectid').val(), ProjectName: $('#ProjectEditContainer #ProjectName').val() , POP3Address: $('#ProjectEditContainer #POP3Address').val() , Active:($('#ProjectEditContainer #Active').is(':checked')) ? "on" : "" }; //call Controller/Action eaither via controllers or directly //controllers['Project']['EditPost'](null, args); ProjectController.EditPost(null, args); };
服务器端 controller 发送 POST 表单处理结果。结果可在客户端代码中通过服务器端的数据或状态获得。
处理“编辑”表单值并将其发布到服务器的代码示例 (/Client/Controllers/Project.js)
var ProjectController = { EditPost: function(callback, args){ //locally render the model in the view when they are ready, and update the DOM var UpdateDOM = function(Model,View) { show results of post request here }; //call for the model, async ModelProxy(UpdateDOM, 'Project','EditPost', args, 'post'); } }
列表(网格)
当您需要显示列表时,请调用适当的 Controller/Action,例如 /ProjectController/List
客户端 controller (ProjectController.List()) 将调用服务器端 controller/action 并获取列表数据(Model)。同时,它将加载适当的视图 - 一个 mustache 模板 (/Client/Views/Project/List.html)。
然后,客户端 Controller/Action 将更新 DOM:在页面选定的元素中显示渲染后的列表。
重型 bootstrap 轮播滑块、菜单、语言资源
动态应用程序的一个巨大优势是能够首先加载轻量级页面,然后稍后加载和更新重型组件。Bootstrap 滑块可能包含大量图像。如果您将重型图像嵌入页面,加载页面可能需要一些时间。您可以先加载一两个项目的轮播滑块,然后按以下方式加载其余项目:
在“页面加载”事件上调用客户端 Controller/Action。它将调用服务器端 controller/action 并获取轮播数据(Model)。
同时,它将加载适当的视图:一个用于渲染 bootstrap 轮播的 mustache 模板。
然后,客户端 Controller/Action 将更新 DOM:在页面选定的元素中显示渲染后的轮播项目。
我正在使用这个后台页面更新类来在代码生成器中显示菜单和模型。
客户端缓存
您可以在客户端实现 Models 和 Templates 的缓存,以启用离线应用程序使用。现代浏览器中有 Web SQL、IndexedDB 和 localStorage。ModelProxy 和 ViewProxy 是用于 Models 和 Views 缓存的固定点。
如果支持缓存,在客户端缓存 Views 是一个显而易见的主意。优点是:减少网络流量,调用本地 controller/action 时延迟更低。
{不带缓存的 ViewProxy}
function ViewProxy (callback, controller, action) { //Load the view $.get('Client/Views/' + controller + '/' + action + '.html', {}, function(view) { callback(null,view); //may put the template into the client's local cache; }).fail(function() { //alert("error"); //failed to load the view, try render with empty one var view = ""; callback(null,view); }); };
在 使用不带缓存的 ViewProxy 的截图 中,您可以看到每次单击(项目)链接时都会发出 2 个查询。请注意,其中一些是由浏览器缓存的。
{带缓存的 ViewProxy}
function ViewProxy(callback, controller, action) { //may load template from/to cache: either Web SQL , IndexedDB or localStorage var cache = new ViewCache(controller, action); if (!cache.read(callback)) { //Load the view $.get(baseurl + 'Client/Views/' + controller + '/' + action + '.html', {}, function(view) { //Template = Mustache.compile(view); callback(null, view); //may put the template into the client's local cache; cache.write(view); }).fail(function() { //alert("error"); //failed to load the view, try render with empty one var view = ""; callback(null, view); }); } }; // // This defines the cache // ViewCache = function(controller, action, data) { var enabled = true; var Controller = String(controller); var Action = String(action); var Data = null; var appName = "test"; if (data) Data = String(data); var storagename = appName+ "/Views/" + Controller + "/" + Action; return { write: function(data) { //if (!enabled) return false; //write anyway if (data) Data = String(data); if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! localStorage[storagename] = Data; return true; } else { // Sorry! No web storage support.. return false; } }, read: function(callback) { if (!enabled) return false; if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! Data = localStorage[storagename]; if (Data) { if (Data.length > 1) { callback(null, Data); return true; } } return false; //read was not successful } else { // Sorry! No web storage support.. return false; } }, clear: function(callback) { if (!enabled) return false; if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! //localStorage.clear(); //clear removes all cache values for domain. for subdomains remove values via iteration. for (var key in localStorage) { if (key.substring(0, appName.length) === appName) localStorage.removeItem(key); } return true; } else { // Sorry! No web storage support.. return false; } } }//end public }//end ViewCache
在 ViewProxy 使用缓存的截图 中,您可以看到每次单击(项目)链接时只会发出 1 个查询。
虽然 View 缓存的原因很明显,但对于数据缓存来说,它们并不那么明显。
- 为什么您可能需要它
您可能需要数据缓存来存储经常使用但很少更新的数据。它可以是菜单或下拉列表值,或渲染到界面的某些数据,例如语言资源等。在本主题中,缓存将应用于下拉列表。
- 为什么您可能不需要它
尽管数据缓存有一些好处,但也有不使用它的理由
- 某些操作需要实时(无条件非缓存)数据。这包括创建、更新、删除操作。
- 上述操作会更改数据,因此需要缓存失效。
- 在多用户系统中,缓存失效无济于事:其他人可能在本地缓存仍标记为有效时更改了数据库。
- 它可能带您走向何方
如果您实现了本地数据缓存,那么将来它可能会带您到以下地方:
- CRUD 操作的数据缓存可以为您提供离线/在线应用程序的功能。当离线时,您的客户端代码可以读取、更新、删除缓存中的数据,并在重新上线时与服务器同步更改。
- 您可以将数据缓存替换为客户端存储。这可以为您提供一种构建混合移动应用程序的简单方法。
{不带缓存的 ModelProxy 代码}
function ModelProxy(callback, controller, action, vars, method) { //var Callback = callback; //could load the model from the local storage: either Web SQL , IndexedDB or localStorage //Load model if (!method) method = 'get'; method = method.toLowerCase(); if (method == 'post') { $.post( baseurl + controller + '/' + action + '/', vars, function(model, status) { callback(model,null); }); } else if (method == 'get') { //this string is for MVC sites $.get( baseurl + controller + '/' + action + '/' + vars, {}, function(model, status) { callback(model,null); }); } };
{带缓存的 ModelProxy 代码(仅缓存 DropDowns)}
//ModelProxy extracts the data from cache or from server(with the call to server-side Controller and Action). function ModelProxy(callback, controller, action, vars, method) { //could load the model from the local storage: either Web SQL , IndexedDB or localStorage var cache = new ModelCache(controller, action); if (cache.cacherequired) { if (cache.read(callback)) { //successful cache read, value is passed in the callback function return; } } //Load model if (!method) method = 'get'; method = method.toLowerCase(); if (method == 'post') { $.post(baseurl + controller + '/' + action + '/', vars, function(model, status) { callback(model, null); if (cache.cacherequired) { cache.write(model); } }); } else if (method == 'get') { //this string is for MVC server-side code $.get(baseurl + controller + '/' + action + '/' + vars, {}, function(model, status) { callback(model, null); if (cache.cacherequired) { cache.write(model); } }); } }; // // This defines the cache // it reads/writes the data to localStorage // can invalidate cache either internally or via call ModelCache = function(controller, action, data) { //private var enabled = true; var Controller = String(controller); var Action = String(action); var Data = null; var appName = "test"; if (data) { Data = JSON.stringify(data); } var storagename = appName + "/Model/" + Controller + "/" + Action; //this defines which Actions to cache. usually this is a heavy dropdowns var cachedActions = new Array("DropDown"); //this defines which Actions to cause the cache invalidation. Usually this is a data change actions var dataChangeActions = new Array("CreatePost", "EditPost", "DeletePost"); var invalidate = function() { if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! var iAction; for (iAction in cachedActions) { localStorage.removeItem(appName + "/Model/" + Controller + "/" + cachedActions[iAction]); } } }; var invalidationrequired = (dataChangeActions.indexOf(action) >= 0); if (invalidationrequired) { invalidate(); } var cacherequired = (cachedActions.indexOf(action) >= 0); //public return { write: function(data) { //if (!enabled) return false; //write anyway if (data) Data = JSON.stringify(data); if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! localStorage[storagename] = Data; return true; } else { // Sorry! No web storage support.. return false; } }, read: function(callback) { if (!enabled) return false; if (typeof (Storage) !== "undefined") { // Yes! localStorage and sessionStorage support! Data = localStorage[storagename]; if (Data) { if (Data.length > 1) { Data = JSON.parse(Data); callback(Data, null); return true; } } return false; //read was not successful } else { // Sorry! No web storage support.. return false; } }, // a reference to internal function invalidate: invalidate, cacherequired: cacherequired, invalidationrequired: invalidationrequired }//end public }//end ModelCache
JavaScript 是一种解释型语言。我们需要将代码存储在单独的文件中。有许多方法可以将代码模块作为一个系统保存在一起。
- 在编写意大利面条式代码时,有很多全局变量被重用。您无法简单地连接文件;它们将无法工作。使用 MVC 模式,您可以简单地将所有脚本代码文件连接在一起,从路由器开始,按包含的顺序。如果仍然可以工作,那么您可以使用一个免费的可用服务(例如 http://jscompress.com/)来压缩/缩小代码。额外的好处是缩小提供了自然的混淆。
服务器端和客户端代码都可以被压缩。 - 将代码与内置代码链接器一起保存
Classic ASP 使用 #include 指令来链接模块。Router.asp 文件包含链接Controllers、Model 文件、Lib 文件的指令。这 3 个作为模块工作并包含相应的自定义文件。
可以使用 Classic ASP Server.Execute() 命令来包含代码模块。
包含服务器端模块还有另一种鲜为人知的选项:带有 **runat="server"** 选项的标准 <script> 标签。
<script language="JScript" src="Controller/controllers.inc" runat="server"></script> <script language="JScript" src="Model/lib.inc" runat="server"></script> <script language="JScript" src="lib/utils.inc" runat="server"></script>
最后一个选项来自 MS Scripting Engine。以这种方式包含文件允许不同脚本语言中的代码,例如 VBScript
<script language="VBScript" src="lib/VBModule.inc" runat="server"></script>
但是,您应该仔细验证您的 IIS 如何处理您模块的文件扩展名。如果您将模块保留在“.vbs”或“.js”文件中,并且有人直接从服务器请求这些文件,它们将被原样发送到客户端。这将暴露您代码中的服务器端逻辑和常量。“.inc”扩展名在 IIS 默认设置中应该是安全的。
如果您对访问服务器端代码存有疑虑,可以将整个服务器端代码移出 IIS 文件夹结构。由于我们这里使用的模式假定应用程序的单一入口点,因此在 IIS 应用程序文件夹的单个文件中可以有一个单独的 **#include** 指令。
客户端代码链接
在客户端链接 JavaScript 有 2 种通用方法:
- 使用标准 <script> 标签链接脚本
- 通过动态加载脚本到代码中链接脚本:使用 Ajax 加载脚本,然后使用 **eval()** 调用它们。
当 JavaScript 代码文件不太大时,第一种选择是可以的,否则加载它们所有可能需要一些时间。如果脚本很大,不幸的是,标准 <script> 标签还有一个问题:浏览器不会并行加载脚本和其他资源,因此首次显示页面需要更长的时间。
第二种选择很容易使用 jQuery.getScript()、RequireJS、labjs 等库。但是在使用它们时可能会遇到一些问题:
- 当 DOM 加载完毕但支持的 JavaScript 库尚未加载时,可能会出现界面故障。
- 可能会出现事件序列中断:假设您有一个模块,它从 Require.js 加载。jQuery document.ready() 可能不会调用模块中定义的函数,因为它在事件触发时仍在加载。
调试
您可以使用 MS Visual Studio 调试服务器端应用程序。您需要
- 启用 IIS 脚本调试
- 将应用程序打开为 本地 IIS 网站项目
- 在 JavaScript 代码中使用“debugger”指令 来调用调试功能。
优点和缺点
- 服务器和客户端使用的相同 JavaScript 方言非常相似。由于客户端和服务器语言版本相似,因此上下文切换更少。
- 服务器和客户端上使用的相同 MVC 模式的实现相似。由于客户端和服务器语言版本相似,因此上下文切换更少。
- 代码具有可移植性,只需最少的修改即可在客户端和服务器之间进行重新定位。
- 在进行服务器端 JavaScript 编程时,可以从最意想不到的来源获得 JavaScript 库。
- 它是 IIS。您可以带来您的 VBScript 代码并将其与服务器端混合。不要忘记通过 Server.CreateObject() 和 ASP.NET 页面进行 Windows 上的 ActiveX 扩展。
- 脚本为每次服务器调用加载和卸载,因此它是真正无状态的,但应该比内存中系统慢。
- Classic ASP 不支持 异步 代码执行。
- Classic ASP 可用于从 XP 开始的许多 MS 操作系统。
关注点
- MVC 模式应用于 Classic ASP 服务器端 JavaScript 代码
- MVC 模式应用于客户端 JavaScript 代码
- 代码生成器已创建
- 已应用客户端缓存和模板