摘要:本文提供了一个模仿ASP.Net MVC的node.js MVC框架的Node.js教程,基本实现了ASP.Net MVC的Model(模型)、View(视图)和Controller(控制器)的这三项基本功能,并且基于这个MVC框架的编码方式、程序结构和文件组织,同ASP.Net MVC基本相同。
本文提供了一个模仿ASP.Net MVC的node.js MVC框架的Node.js教程,基本实现了ASP.Net MVC的Model(模型)、View(视图)和Controller(控制器)的这三项基本功能,并且基于这个MVC框架的编码方式、程序结构和文件组织,同ASP.Net MVC基本相同。
一:为什么要做这个东西
首先声明,我对ASP.Net MVC研究不深,对node.js更是仅知皮毛。
多年来我主要做ASP.Net WebForm的开发,近来偶然接触了MVC,令人耳目一新。感觉MVC同WebForm有很大的区别,源代码更简洁、目标代码更干净、系统运行速度更快。虽然还没有找到类似于WebForm的可视化开发的方法,不过对我这种已经习惯手写代码的程序员而言,却没什么障碍。
近来又接触了node.js,这真是一个神奇的东西:JavaScript做服务器端开发、代码及其简洁、(据说)速度也奇快。不过......不过对我这种已经习惯了ASP.Net开发方式的程序员,node.js仅提供基础功能支持,这对我们却是一种折磨。
灵机一动,或者说,是为了以后的懒惰做准备,决定写一个简单的MVC框架。基本原则是:其使用方法同ASP.Net MVC基本相同(为了让我们少耗费一些脑细胞)。
这个MVC框架我已经基本写完(草稿),本文就仅对已有的实现做简要说明,中间的挫折统统忽略吧。
另外,这个MVC框架用到了一些第三方组件,这里提供其官方地址,若需要下载或对其有疑问,直接导航过去看:
underscore:一个实用的JavaScript工具库,提供很多对集合、数组、对象的快捷操作方法,地址://documentcloud.github.com/underscore/
underscore.string:另一个实用的JavaScript工具库,提供对字符串的快捷操作方法(实际上同underscore没有任何关系),地址:https://github.com/epeli/underscore.string
ejs:一个强大的JavaScript模板支持库,地址:https://github.com/visionmedia/ejs
先具体说一下导致我产生这个想法的具体原因。
为了学习node.js,从官方网站(//www.nodejs.org/)下载程序包,执行安装。(安装完成后,可执行文件node.exe放在C:\Program Files\nodejs中,如果不习惯的话,可以把整个文件夹拷贝到其他的位置。)
首先,从Hello Word开始。在nodejs文件夹中新建一个文本文件,起名为index.js,用任意文本编辑器编辑:
1 var http = require('http');
2 http.createServer(function (req, res) {
3 res.end('Hello World\n');
4 }).listen(81, "127.0.0.1");
5 console.log('Server running at //127.0.0.1:81/');
然后在Dos窗口执行: node index.js。用浏览器打开://127.0.0.1:81,马上就能看到文本:Hello World。
是不是很简单?不过怨念也就在这里了:我们做传统的Web开发,每个请求需要建立不同的文件,并使用不同的http地址来请求;而node.js,所有的请求都指向到这个根文件夹的index.js,这就需要我们经常性的在这个文件中添加代码,即使是使用伟大的express也不例外。这是express的一个样例,一般起名为app.js:
1 var express = require('express '); 2 3 var app = express.createServer(); 4 5 app.get('/', function(req, res){ 6 res.send('root index'); 7 }); 8 9 app.get('/user/', function(req, res){ 10 res.send('user's index'); 11 }); 12 13 app.listen(82); 14 console.log('listening on port 82');
当然,我不会去说express不好(因为这确实是个强大的组件),只是我不习惯;可能express有很好的方法,我不知道而已。
于是我决定用node.js做一个类似于ASP.Net MVC的框架,以方便我以后继续学习使用node.js,或许也可以帮助到其他初学node.js的同学们。
二:这个东西应该是什么样子的
既然是计划模仿ASP.Net MVC,那么就要尽量模仿的像一点。
为什么我要模仿ASP.Net MVC,而不去模仿ASP.Net WebForm: 因为MVC够简单,而WebForm太复杂了,尤其是那一大堆服务器端控件,除非一个传奇级的任务,可不敢想象怎么去做。
首先是ASP.Net MVC的目录结构。这个图片就是一个经典的ASP.Net MVC站点的目录结构(VS自动创建的,做ASP.Net MVC的同学应该非常熟悉) :
在这个图片中,有几个东西是我们需要关注的:
Content文件夹:用于放置静态文件,包括images、styles等等
Controllers文件夹:用于放置控制器程序文件
Models文件夹:用于放置数据模型文件
Views文件夹:用于放置视图文件(就是页面或控件模板)
上述的四个文件夹是我们需要模仿的,而App_Start、Global.asax、Web.config等等的,或许到了后期会发现也需要做,现在就先算了。至于Scripts文件夹的内容,在我看来应该放到Content\Scripts子文件夹中 。
作为适应这个框架工作方式的结果,或者说是我们的node.js MVC的目的:
全部静态文件放入Content文件夹,并可以通过HTTP地址直接访问,而不用增加任何程序代码
全部控制器文件放入Controllers文件夹,一个Controller文件包含有多个Action,每个Action的访问方式为://.../<controller>/<action>/...
全部视图文件放入Views文件夹,并且每个Controller在这里对应有一个同名的子文件夹
ASP.Net MVC的全部视图文件放入Models文件夹,不过node.js是基于javascript动态语言的,模型定义似乎并不重要,这里就忽略了
为实现上述目的,我们就先建立一个基本的目录结构,之后的工作都围绕这个目录结构:
在这个框架中:
index.js文件永远不用修改
contents文件夹一般会包含images、script、styles等子文件夹,任何文件放进去就可以用,并且可以包含任意规格的子文件夹
controllers文件夹包含全部的服务器端javascript文件
views文件夹包含全部的服务器端模板文件,且每一个controler在此文件夹中对应一个同名的子文件夹
三:这个框架都实现了什么
这个框架的程序基本程序逻辑如下图所示:
根据以上逻辑图可以看出,此框架只有三个简单的分支:
静态请求直接输出
动态请求由控制器处理
若需要输出页面则调用模板引擎处理
这里先说明一下:在此框架中,任何有文件后缀的请求均被认为是静态请求。
为实现此框架编写了一系列的程序文件,以实现以下功能:
请求入口:即index.js文件,实现请求分发和简单的安全管理
静态页处理:读取contents文件夹中的静态文件
动态请求处理:处理动态请求并返回动态结果
Session支持:提供Web应用中的用户级缓存支持(等同于ASP.Net中的Session)
Cache支持:提供Web应用中的全局缓存支持(等同于ASP.Net中的Cache或Application)
模板处理:处理包含Html页面或板块的文件,并支持嵌入式开发
嵌入式代码支持:为嵌入式开发提供扩展支持,包括ASP.Net MVC中的Html语法
路由:用于支持除了“/<controller>/<action>/...”之外的其他浏览器请求(暂未实现)
全部文件夹和文件结构如下表所示:
index.js:请求入口 - contents:静态内容文件夹 - css - images - scripts - controllers:动态内容程序文件夹 - views:动态内容模板文件夹 - node_modules:node.js类库文件夹 - ejs:第三方模板处理组件 - mvc:我们的MVC框架文件夹 base.js:进行动态请求初始化操作,例如准备session等 cache.js:支持全局缓存 html.js:支持模板处理和嵌入式开发 index.js:无功能,仅用于nodejs mvc.js:请求分发和处理 routes.js:请求路由 session.js:支持用户缓存 utils.js:部分常用工具函数 - underscore:第三方类库 - underscore.string:第三方类库
四:MVC框架实现代码
注意:我假定阅读本文的同学:
对javascript比较熟悉(不能是生手,否则可能看不懂这些代码)
对node.js已经有了初步的了解,比如require、module.exports等的用法
1. 请求入口(index.js)
作为一个标准的node.js程序,一个根文件夹下的index.js是必须的。这个文件仅包含如下代码:
1 var http = require('http');2 var mvc = require('mvc');3 4 http.createServer(function (req, res) {5 new mvc.startRequest(req, res);6 }).listen(88, "127.0.0.1");7 8 console.log('Server running at //127.0.0.1:88/');
在这个文件中,第6行的listen方法的参数(及第8行的日志)需要根据实际情况进行修改,其他的可以总是保持不动。当然若实现了类似ASP.Net的web.config支持之后,这个index.js文件就再也不用修改了。
在index.js文件中仅做了一件事,即调用mvc.startRequest方法以开始一个请求处理。其他的代码同node.js中的hello world样例程序没啥区别。
2. 分发与请求处理 (mvc.js)
mvc.js程序是此框架的核心,主要实现了请求分发和处理入口。
首先,采用node.js推荐的方法引入外部程序:
1 var url = require('url');2 var path = require('path');3 var fs = require('fs');4 var querystring = require('querystring');5 var _ = require('underscore');6 var _s = require('underscore.string');7 var utils = require('./utils');8 var routes = require('./routes');9 var base = require('./base');
其中,1-4是node.js系统类库,5-6是第三方类库,7-9是我们的框架中的将被用到的类库(后面我们在详细介绍)。
然后我们看看在index.js用到的mvc.startRequest方法,在这个方法中我们简单地根据有无文件后缀来区分是否是动态请求。
1 module.exports.startRequest = function(req, res) { 2 //在处理请求前先在控制台打印日志 3 if (req.url!='/favicon.ico') { //favicon.ico文件一般情况下是浏览器自动请求的,对我们没用 4 console.log(_s.sprintf('Start request: %s\t%s', utils.dateString(new Date()), req.url)); 5 } 6 //初始化一个请求处理引擎实例 7 var handler = new RequestHandler(req, res) 8 var u = url.parse(req.url); 9 var ext = path.extname(u.pathname);10 if (ext) { //如果请求的文件有后缀则认为是静态请求11 handler.responseContent(u.pathname);12 }13 else { //否则是动态请求14 handler.handleDynamicRequest();15 }16 }
注:为方便起见,代码说明我尽量都卸载代码注释中。
上述代码中我们用到了一个RequestHandler类,用于处理请求。以下是这个类的定义:
1 function RequestHandler(req, res) {2 this.request = req;3 this.response = res;4 }
我们使用RequestHander.responseContent方法来处理任何静态请求,即存储在/contents/文件夹中的文件。思路是:找到文件、读取文件、输出文件。
1 RequestHandler.prototype.responseContent = function(filename) { 2 if (!filename) { //如果参数缺失,则直接提示错误(不过这种情况应该不会出现) 3 this.responseNoFound(); 4 } 5 else { 6 filename = _s.trim(filename,' \/\\'); 7 8 var self = this; 9 var filepath = path.resolve('./contents/', filename);10 fs.readFile(filepath, function (err, data) {11 if (err) { //如果文件不存在或无法访问,则提示错误12 self.responseNoFound();13 }14 else { //否则输出文件内容到客户端15 self.response.write(data);16 }17 self.response.end(); //输出完成之后必须调用response.end方法18 });19 }20 }
我们使用RequestHandler.handleDynamicRequest方法来处理动态请求。思路是:获取请求参数,调用控制器来处理请求。
1 RequestHandler.prototype.handleDynamicRequest=function() { 2 var params = null; 3 var self = this; 4 if (self.request.method == 'POST') { //如果是post方式的请求,则需要异步读取请求参数 5 var buf = []; 6 self.request.addListener('data', function(chunk){ 7 buf.push(chunk); 8 }) 9 .addListener('end', function(){10 params = querystring.parse(buf.join('')); 11 self.handleController(params);12 }) 13 }14 else { //如果不是post方式(是get方式)的请求,则直接使用url参数15 params = self.request.url.query;16 if (_.isString(params)) 17 params = querystring.parse(params);18 19 self.handleController(params);20 }21 }
上述代码中的RequestHandler.handleController方法,用于装载控制器,并执行action:
1 RequestHandler.prototype.handleController=function(params) { 2 var controllerInfo = routes.parseControllerInfo(this.request); //分析请求url,拆分出controller,action,parameters 3 4 if (controllerInfo 5 && controllerInfo.controllerName && controllerInfo.methodName 6 && !_s.startsWith(controllerInfo.controllerName, '_') 7 && !_s.startsWith(controllerInfo.methodName, '_')) { 8 9 var instance = this.createControllerInstance(controllerInfo.controllerName); //装载控制器10 11 if (instance12 && _.has(instance, controllerInfo.methodName) 13 && _.isFunction(instance[controllerInfo.methodName])) { //若存在action定义,则执行该action14 try { //注意:这里必须加try...cache,以防止action发生错误导致我们的网站崩溃15 var res = instance[controllerInfo.methodName](controllerInfo.parameters, params);16 this.response.end(res + "");17 }18 catch (err) {19 console.log(_s.sprintf("Execute a controller method failed: %s", err));20 utils.responseError(this.response, 500);21 }22 }23 else {24 this.responseNoFound();25 }26 }27 else {28 console.log(_s.sprintf('parse controller failed: %s', _.keys(controllerInfo)));29 console.log(_s.sprintf('%s', _.values(controllerInfo)));30 this.responseNoFound();31 }32 }
装载控制器的RequestHandler.createControllerInstance方法的定义是:
1 RequestHandler.prototype.createControllerInstance = function(controllerName) { 2 var instance = null; 3 4 try { 5 var controller = require(path.resolve('./controllers/', controllerName)); // 使用require方法装载控制器 6 7 if (controller && _.isFunction(controller)) { 8 instance = new controller(this.request, this.response); 9 }10 }11 catch (err) {12 utils.responseError(this.response, 500);13 }14 15 return instance;16 }
在RequestHandler中还定义了两个额外的方法:
1 //初始化页面基类 2 module.exports.controller = function(req, res) { 3 return new base(req, res); 4 } 5 6 7 //当文件不存在时记录日志并提示错误 8 RequestHandler.prototype.responseNoFound = function() { 9 if (this.request.url!='/favicon.ico') {10 console.log(_s.sprintf('No found url: %s', this.request.url));11 }12 utils.responseError(this.response, 404);13 }
3. 动态请求初始化(base.js)
在请求分发处理成功之后,我们就需要开始实际的业务处理,包括session、页面、模板等等。这些功能我们集中定义在base.js中:
1 var Cache = require('./cache'); 2 var Session = require('./session'); 3 var Html = require('./html'); 4 var routes = require('./routes'); 5 6 var BaseController = module.exports = function (req, res) { 7 this.request = req; 8 this.response = res; 9 10 this.cache = Cache; //全局缓存11 this.session = Session.current(req, res); //用户缓存12 this.html = new Html(this); //视图处理及嵌入式开发引擎13 14 this.controllerInfo = routes.parseControllerInfo(req);15 }16 17 //处理一个视图(模板),并返回结果内容18 BaseController.prototype.renderView = function(options) {19 return this.html.renderView(options);20 }21 22 //处理一个视图(模板),直接输出到客户端23 BaseController.prototype.responseView = function(options) {24 this.response.end(this.renderView(options));25 }
4. 用户缓存处理 (session.js)
Session是Web应用程序中一个非常重要的东西。对node.js来说,Session处理其实就是一个全局的数据集合的处理:
1 var _ = require('underscore'); 2 var _s = require('underscore.string'); 3 4 //全部Session的存储空间 5 var SessionPool = {}; 6 7 //全局的Session过期时间 8 Session.prototype.expireTime = 20; //缺省20分钟 9 10 //新建一个SessionID11 function newSessionID() {12 var buff = [];13 var chars = '0123456789abcdefghijklmnopqrstuvwxyz';14 for (var i=0; i<32; i++) {15 buff.push(chars[Math.floor(Math.random() * 36)]);16 }17 return buff.join('');18 }19 20 //一个Session的定义21 var Session = function() {22 var self = this;23 self.sessionId = newSessionID();24 self.sessionItems = {};25 self._expireTime = Session.prototype.expireTime; 26 self._expireHandle = setInterval(function () { //每一分钟检查一次是否过期27 self._expireTime -= 1;28 if (self._expireTime <= 0) {29 clearInterval(self._expireHandle);30 self.dispose(); //过期则自动清除31 }32 }, 60000);33 }34 35 //清理一个Session36 Session.prototype.dispose = function() {37 if (this._expireHandle) {38 clearInterval(this._expireHandle);39 this._expireHandle = null;40 }41 42 for (var key in this.sessionItems) {43 delete this.sessionItems[key];44 }45 this.sessionItems = null;46 47 delete SessionPool[this.sessionId];48 }
然后我们定义一些常用的Session操作方法:
1 //判断Session项是否存在 2 Session.prototype.has = function(key) { 3 return _.has(this.sessionItems, key); 4 }; 5 6 //添加或更新一个Session项 7 Session.prototype.set = function(key, value) { 8 if (key) { 9 this.refresh();10 11 if (this.has(key) && this.sessionItems[key] == value) {12 return;13 }14 15 this.sessionItems[key] = value;16 }17 }18 19 //获取一个Session项20 Session.prototype.get = function(key) {21 this.refresh();22 23 if (key && this.has(key)) {24 return this.sessionItems[key];25 }26 27 return null;28 }29 30 //删除一个Session项31 Session.prototype.remove = function(key) {32 this.refresh();33 34 if (key && this.has(key)) {35 delete this.sessionItems[key];36 }37 }38 39 //刷新过期时间,40 Session.prototype.refresh = function() {41 this._expireTime = Session.prototype.expireTime; 42 }4344 module.exports = Session;
用户Session应该能被自动创建和恢复,这里我们使用大部分浏览器都支持的cookie(如果你的浏览器不支持cookie那么就需要另想它法了)。
1 module.exports.current = function(req, res) { 2 //从Request的header中存储的cookie中查找当前正在使用的SessionID 3 var headers = req.headers; 4 var sessionId = null, session = null; 5 var cookie = null; 6 if (_.has(headers, 'cookie')) { 7 cookie = headers['cookie']; 8 if (_s.startsWith(cookie, 'SessionID=')) { //此处可能不是很严谨,需要优化 9 sessionId = cookie.substr(10, 36);10 }11 }12 13 //激活Session池中的一个session14 if (sessionId && _.has(SessionPool, sessionId)) {15 session = SessionPool[sessionId];16 }17 18 //如果是新session,则自动创建一个,并写回cookie中19 if (!session) {20 session = new Session();21 SessionPool[session.sessionId] = session;22 23 res.setHeader('set-cookie', 'SessionID=' + session.sessionId); 24 }25 26 return session;27 }
这个Session.current方法将在BaseController.js第11行被自动调用,然后在任何动态请求中就都可以所以用session了。
5. 系统缓存处理 (cache.js)
Cache同样是Web应用开发中的一个很重要的东西。它对应于ASP.Net中的Application集合,以及System.Web.Caching.Cache集合(ASP.Net中这两个东西可都可以实现全局缓存)。对node.js来说,Cache处理同Session差不多同样就是一个全局的数据集合的处理,不过它比Session要简单得多。
1 var _ = require('underscore'); 2 3 var CachePool = {}; 4 5 var Cache = function() {}; 6 7 //判断Cache是否存在 8 Cache.prototype.has = function(key) 9 {10 return _.has(CachePool, key);11 };12 13 //添加或更新一个Cache项14 Cache.prototype.set = function(key, value)15 {16 if (key) {17 CachePool[key] = value;18 }19 }20 21 //获取一个Cache项22 Cache.prototype.get = function(key)23 {24 if (key && Cache.prototype.has(key)) {25 return CachePool[key];26 }27 28 return null;29 }30 31 //移除一个Cache项32 Cache.prototype.remove = function(key) {33 if (Cache.prototype.has(key)) {34 delete CachePool[key];35 }36 }37 38 //建立一个全局的Cache容器39 module.exports = new Cache();
注意:上述代码的第39行直接建立了一个类实例,即保证整个应用中只有一个Cache实例,以保证全局的唯一性。
这个Cache将在BaseController.js第10行被自动调用,然后在任何动态请求中就都可以所以用cache了。
6. 视图处理 (html.js)
MVC构架中一个重要的功能点就是视图,简单地说就是模板处理。这里我们定义一个Html类(对应于ASP.Net MVC中的Html类)来实现这个功能。
基本思路是:装载模板文件、调用EJS处理模板。
1 var fs = require('fs'); 2 var path = require('path'); 3 var _ = require('underscore'); 4 var _s = require('underscore.string'); 5 var ejs = require('ejs'); 6 7 var Html = module.exports = function (controller) { 8 this.controller = controller; 9 };10 11 //处理一个模板返回结果内容12 Html.prototype.renderView = function(options) {13 //确定模板文件,若未指定,则缺省与控制器相同14 var options = options || {};15 if (!options.path || !options.name) {16 options.path = options.path || this.controller.controllerInfo.controllerName;17 options.name = options.name || this.controller.controllerInfo.methodName;18 }19 options.path = _s.trim(options.path,' \/\\');20 //模板文件必须存在于views文件夹中21 var viewPath = path.resolve(path.resolve('./views/', options.path + '/'), options.name);22 if (options.name.indexOf('.')<0) {23 options.extname = options.extname || '.htm';24 if (!_s.startsWith(options.extname,'.')) viewPath += '.';25 viewPath += options.extname;26 }27 28 try { //这里必须加try...cache,以防止文件操作及视图处理错误29 var tmpl = this.controller.cache.get(viewPath); //首先从缓存中读取模板,以提高效率30 if (!tmpl) { 31 tmpl = fs.readFileSync(viewPath);32 33 if (!_.isString(tmpl)) {34 tmpl = tmpl.toString();35 }36 37 this.controller.cache.set(viewPath, tmpl)38 }39 40 //使用EJS引擎处理模板,并将一些可能需要的参数传入引擎41 var res = ejs.render(tmpl, {42 filename: viewPath,43 model: options.data,44 html: this.controller.html,45 session: this.controller.session46 });47 48 return res;49 }50 catch (err) {51 console.log(_s.sprintf('Html.renderView error: %s', err));52 53 return null;54 }55 }
Q: 为什么我要使用EJS来处理模板,而不使用更稳定更流行underscore或jade等引擎?
A: 在选择模板处理引擎时,我重点考虑到了嵌入式开发的需求,即我应该可以将任何参数任何数据传入到模板引擎中,并参与模板的处理;而这个需求ejs做得更好。
6. 嵌入式开发 (html.js)
嵌入式开发,即在网页文件中,嵌入并运行程序代码。例如,以下的来自于ASP.Net MVC自动生成的一份代码:
<%
if (Request.IsAuthenticated) {%>
欢迎使用 <b><%: Page.User.Identity.Name %></b>!
[ <%: Html.ActionLink("注销", "LogOff", "Account") %> ]<%
}
else {%>
[ <%: Html.ActionLink("登录", "LogOn", "Account") %> ]<%
}%>
这其中包含三个关键点:
在页面中可以签入程序脚本,例如if...else...
页面中可以引用外部程序传入的数据对象,例如request, page, model,以及自定义对象等
支持Html.method的方式来创建页面控件
而这些,使用ejs都可以实现。
首先看上节代码中的第42-45行,我们在这里传入任何所需要的数据对象。同时,我们传入了一个Html对象,用于支持创建页面控件。
Html对象除了上节代码中定义的一个基本方法renderView之外,还需要定义更多的方法(具体需要哪些方法,请参见MSDN的在线帮助System.Web.Mvc.HtmlHelper对象)。
首先,我们需要定义一个生成控件的基本方法:
1 Html.prototype.element = function(tag, attributes) { 2 var buf = [], text=null; 3 4 buf.push('<'); 5 buf.push(tag); 6 if (attributes) { 7 for (var key in attributes) { 8 if (_.has(attributes, key)) { 9 var value = attributes[key];10 if (key.toLowerCase() === 'text')11 text = value;12 else if (value) 13 buf.push(" " + key + '="' + _.escape(value) + '"');14 }15 }16 }17 if (!_.isNull(text)) {18 buf.push('>');19 buf.push(text);20 buf.push('</'+tag+'>');21 }22 else {23 buf.push('/>');24 }25 26 return buf.join('');27 }
这个方法没什么好说的,根据数据构造一个字符串而已。
后面我们要做的,就是一个个编写控件生成代码。(下面是一些常用控件的生成代码)
Html.elements
7. 路由 (routes.js)
路由功能是一个完整的框架必须实现的,并且框架的强壮性同此息息相关。
但是呢,要实现一套完成的路由功能,难度确实也不低。这里,就只实现了最简单的路由功能,以解析这样的Url:
~/<Controller>/<Action>/<parameter1>/<parameter2>/...
1 var url = require('url'); 2 3 module.exports._routes=[]; 4 5 //Parse the controller name and method name and additional parameters 6 module.exports.parseControllerInfo = function(req) { 7 var controllerName = null, methodName = null, parameters = null; 8 9 //this section must be optimized in future.10 var paths = url.parse(req.url).pathname.split('/');11 if (paths.length >= 1) 12 controllerName = paths[1];13 if (paths.length >= 2) 14 methodName = paths[2];15 if (paths.length >=3) {16 parameters = paths.slice(3);17 }18 19 return {20 controllerName : controllerName,21 methodName : methodName,22 parameters : parameters23 }24 }
五:模型(Model)的实现
在MVC中,Model是一个不可或缺的成分。而本文所描述的框架,在Controller和View方面做了较多的努力,但是对Model则没有太多涉及。
这意味着这个框架没能实现MVC的Model吗?当然不是。
在我理解,MVC的Model所实现的功能是:
后台程序将数据对象传入View中,由视图使用
浏览器回传的时候,自动把传入数据(post或get)打包成对象,由后台程序使用
针对上述需求,此框架是这样处理的:
Html.renderView方法中调用了ejs.render方法来处理模板,并传入了任何可能使用的数据
RequestHandler.handleDynamicRequest方法,把客户端传入的任何数据,打包成了一个可以直接使用的数据集合
当然这点儿Model实现是过于简单了(不过以后可以扩展嘛)。
六:应用示例
好了,以上就是本文所介绍的基于node.js的一个MVC框架的全部内容,虽然很简陋,不过确实能用。
这里做一个简单的示例来补充一下。
首先在controllers文件夹下新建一个test.js文件:
1 var _ = require('underscore'); 2 var _s = require('underscore.string'); 3 var mvc = require('mvc'); 4 5 module.exports = function(req, res) { 6 _.extend(this, mvc.controller(req, res)); 7 8 this.done = function(params, model) { 9 var data = {10 users: [11 { name: 'tj' },12 { name: 'mape' },13 { name: 'guillermo' }14 ],15 utils: {16 caption: function (str) {17 return _s.capitalize(str);18 }19 },20 listOptions: function() {21 var res=[];22 for (var i=0;i<10;i++) {23 res.push({value: i+'', text: (i*10)+''});24 }25 return res;26 }27 };28 _.extend(data, model);29 this.responseView({name: 'done.htm', data : data});30 }31 }
注意,第3,6,28行代码是必须的,其它的根据实际需求写。
然后在views文件夹下新建一个test文件夹,在其中新建一个done.htm文件:
1 <html> 2 <body> 3 <form method="post"> 4 <% if (model.users && model.users.length) { %> 5 <ul> 6 <% model.users.forEach(function(item){ %> 7 <li> <%= model.utils.caption(item.name)%>: <%= item.name %></li> 8 <% }) %> 9 </ul>10 <% } %>11 <div>12 TextBox1: <%- html.textBox({name: "txtTest1", value: model.txtTest1}) %><br>13 TextBox2: <%- html.textBox({name: "txtTest2", value: model.txtTest2, multi: true}) %><br>14 CheckBox1: <%- html.checkBox({name: "chkTest1", value: "dd", checked: model.chkTest1}) %><br>15 CheckBox2: <%- html.checkBox({id: "chkTest2", name: "chkTest2", value: "dd", checked: model.chkTest2}) %><%- html.label({for: "chkTest2", text: "Text for Check box"}) %><br>16 DropDownList: <%- html.listBox({name: "lstTest1", value: model.lstTest1, options: [{value:1, text:"aa"},{value:2, text:"dd"}]}) %><br>17 List Box: <%- html.listBox({name: "lstTest2", value: model.lstTest2, size: 4, options: model.listOptions}) %><br>18 <%- html.button({type: "submit"}) %>19 </div>20 </form>21 </body>22 </html>
启动node.js:node index.js,在浏览器中输入地址://localhost:88/test/done
然后就可以看到如下结果:
希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标IT频道都能找到!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号