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 代码
- 代码生成器已创建
- 已应用客户端缓存和模板




