【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇(6/8)
文章目录
前情概要
路由、action的扫描、发现、注册搞定之后,后来我发现在我们的action里面获取参数往往都是通过request对象来一个一个获取。同样的一行代码我们不厌其烦的重复写了无数次。遂想着那我们能不能像后端程序一样做得更自动化一些呢?
所以,接下来我们再来完成一个比较重要的功能,那就是参数的自动绑定。
参数的自动绑定实现思路
依靠ts的装饰器特性,我们能做在方法上,在类上,在方法的参数上,在类的属性成员上通通可以加上装饰器来存放一些额外的数据。那理论上我们在编码阶段就可以通过一定的手段把这个标记加载我们需要处理的方法、类、参数等上面,等到运行时的时候可以根据这些额外的参数来帮我们做一些重复性的工作。
- 在需要使用到的方法参数、类、属性上增加我们的特定标识,标记当前参数需要自动解析,并记录一些诸如类型拉、名称啦等的一些额外属性。
- 在action的调用阶段,根据规则先把参数解析好。在传递进去。
- 完事儿,这就是我们的参数自动绑定功能。
参数的自动绑定实现---装饰器实现
部分代码,只贴了fromquery,其他几个formbody,fromheader之类的基本一样,都是调用makeActionParameterDescriptor方法
/**
* 指示当前参数从request对象的query中解析
*
* @export
* @param {(target?: any) => Function} type
* @returns {Function}
*/
export function fromQuery(type: (target?: any) => Function): Function;
/**
* 指示当前参数从request对象的query中解析
*
* @export
* @returns {Function}
*/
export function fromQuery(): Function {
var thatArg = arguments;
return function (target: Object, propertyKey: string, parameterIndex: number) {
makeActionParameterDescriptor('query', thatArg, target, propertyKey, parameterIndex);
}
}
function makeActionParameterDescriptor(parameterFromType: parameterFromType, thatArg: IArguments, target: Object, propertyKey: string, parameterIndex: number) {
//非声明在属性和参数上
if (!propertyKey) return;
var paramType = undefined;
var val = new ActionParamDescriptor();
val.parameterName = propertyKey;
val.target = target;
val.parameterIndex = parameterIndex;
val.parameterFromType = parameterFromType;
val.parameterTypeType = 'simple'
if (typeof parameterIndex === 'undefined') {
//声明在类的属性上
val.localtionType = 'classProperty'
} else {
//声明在action的参数上
val.localtionType = 'methodParameter'
val.actionMethodName = propertyKey;
val.parameterName = getArgs((target as any)[propertyKey])[parameterIndex];
}
//复杂类型
if (thatArg.length > 0) {
val.parameterTypeType = 'complex'
val.parameterType = thatArg[0](target);
}
SetActionParamDescriptor(val);
}
function getArgs(func: Object) {
//匹配函数括号里的参数
var method = func.toString();
method = method.length > 500 ? method.substring(0, 500) : method;
method = method.replace("\r|\n|\\s", "")
var args = method.match(/.*?\(.*?\)/i);
if (args == null) throw Error('can not match method parameters');
method = args[0];
method = method.replace(/.*?\(|\)/, "").replace(')', '');
//分解参数成数组
var arr = method.split(",").map(function (arg) {
//去空格和内联注释
return arg.replace(/\/\*.*\*\//, "").trim();
}).filter(function (args) {
//确保没有undefineds
return args;
});
return arr
}
ActionParamDescriptor 对象结构
export declare type parameterFromType = 'query' | 'body' | 'form' | 'header' | 'cookie' | 'auto'
export class ActionParamDescriptor {
/**
* action参数的action名称
*
* @type {string}
* @memberof ActionParamDescriptor
*/
actionMethodName: string
/**
* 参数名称
*
* @type {string}
* @memberof ActionParamDescriptor
*/
parameterName: string
/**
* 参数所在类
*
* @type {Object}
* @memberof ActionParamDescriptor
*/
target: Object
/**
* 参数类型的类别
*
* @type {('complex' | 'simple')}
* @memberof ActionParamDescriptor
*/
parameterTypeType: 'complex' | 'simple'
/**
* 参数对象的类型(class)对象
*
* @type {Function}
* @memberof ActionParamDescriptor
*/
parameterType: Function
/**
* 参数所在参数类别的顺序
*
* @type {(number | undefined)}
* @memberof ActionParamDescriptor
*/
parameterIndex: number | undefined
/**
* 当前参数属性属于什么类型
*
* @type {('classProperty'|'methodParameter')}
* @memberof ActionParamDescriptor
*/
localtionType: 'classProperty' | 'methodParameter'
/**
* 标记参数应该从什么地方解析
*
* @type {parameterFromType}
* @memberof ActionParamDescriptor
*/
parameterFromType: parameterFromType
}
参数的自动绑定实现---基本使用方法
可以在action上标记某一个参数从什么地方(query、form、body、cookie、header)进行解析,
也可以标记某个参数是一个复杂的查询参数,可以指定这个参数的类型。
当然复杂的查询class的每一个属性都可以指定解析来源,当然也必须使用装饰器来修饰一下,不然我们就没法知道有这个属性需要进行解析啦。
import { BaseController, post, fromQuery, fromBody, fromCookie, fromHeader, property } from "../src/index"
export class demoActionBodyParams {
id: string;
name: string;
pageSize: number;
body: {
req_bb: string
}
}
export class demoActionQueryParams {
@property()
id: string;
@property()
name: string;
@property()
pageSize: number;
@fromCookie()
cookieName: string;
@fromHeader()
headerName: string;
@fromBody()
body: any;
}
export class demoController extends BaseController {
@post()
demoAction(@fromQuery(type => demoActionQueryParams) query: demoActionQueryParams,
@fromQuery() p2: string,
@fromBody() req_body: demoActionBodyParams) {
return { query, p2, req_body }
}
}
参数的自动绑定实现---参数的说明元数据保存
reflect-metadata 目前来说也还是ts的一个实验性特性。可以用来辅助我们保存一些额外的数据。或者也可以理解成它是一个系统级别的静态字典。
那我们把对参数的一些特别设置都通过reflect-metadata保存下来,其实这里我们自己使用一个对象来保存也是可以的。
const request_params_auto_bind_MetadataKey = Symbol("request_params_auto_bind_MetadataKey");
export function SetActionParamDescriptor(val: ActionParamDescriptor) {
(val as any).targetName = val.target.constructor.name
if (val.parameterType) (val as any).parameterTypeName = val.parameterType.name
console.log('SetActionParamDescriptor', JSON.stringify(val));
var arr: ActionParamDescriptor[] = [];
if (val.localtionType === 'methodParameter') {
arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target, val.actionMethodName) || [];
arr.push(val);
Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target, val.actionMethodName);
} else {
arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target) || [];
arr.push(val);
Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target);
}
}
参数的自动绑定实现---参数的自动解析和对象生成
嗯,大概是一些杂乱无章的代码(_)。
主要思路:
- 获得当前action的参数描述对象
- 根据参数描述对象中的配置来解析参数
- 就这么简单,完事儿
//开始参数的自动解析操作
var agrs = bindActionParameter(desc.ControllerType, desc.ControllerTypeName, desc.ActionType, desc.ActionName, req)
function bindActionParameter(controllerType: Function, controllerName: string, actionType: Object, actionName: string, req: core.Request) {
//获得当前action的所有参数描述对象
var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, controllerType.prototype, actionName) || [] as ActionParamDescriptor[];
var args = [arr.length];
for (let index = 0; index < arr.length; index++) {
args[arr[index].parameterIndex as number] = getParameterValue(req, arr[index], arr[index])//循环挨个进行解析
}
return args;
}
function bindClassParameter(req: core.Request, target: any, methodParmeterdesc: ActionParamDescriptor): any {
var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, target.prototype) as ActionParamDescriptor[];
var obj = new target();
for (let index = 0; index < arr.length; index++) {
var desc = arr[index];
obj[desc.parameterName] = getParameterValue(req, desc, methodParmeterdesc);
}
return obj;
}
function getParameterValue(req: core.Request, desc: ActionParamDescriptor, methodParmeterdesc: ActionParamDescriptor): any {
//判断当前action的参数是基本类型参数,还是复杂类型参数。如果是复杂类型就走class绑定逻辑。
if (desc.parameterTypeType === 'simple' || (desc.localtionType === 'methodParameter' && desc.parameterFromType === 'body')) {
return getparameterInRequest(desc.parameterFromType, desc.parameterName, req, methodParmeterdesc);
} else if (desc.parameterTypeType === 'complex') {
return bindClassParameter(req, desc.parameterType, methodParmeterdesc)
}
else throw Error('not support parameter type ' + desc.parameterTypeType)
}
//根据参数的不同配置进行不同解析。
function getparameterInRequest(fromType: parameterFromType, parameterName: string, req: core.Request, methodParmeterdesc: ActionParamDescriptor): any {
switch (fromType) {
case 'query':
return getCompatibleParam(req.query, parameterName)
case 'body':
return req.body
case 'header':
return getCompatibleParam(req.headers, parameterName)
case 'cookie':
return getCompatibleParam(req.cookies, parameterName)
case 'form':
return getCompatibleParam(req.body, parameterName)
case 'auto':
return getparameterInRequest(methodParmeterdesc.parameterFromType, parameterName, req, methodParmeterdesc);
}
return undefined;
}
//忽略参数的大小写问题。
function getCompatibleParam(obj: any, propertyName: string) {
var lower = propertyName.toLowerCase();
for (const key in obj) {
if (obj.hasOwnProperty(key) && key.toLowerCase() == lower) {
return obj[key];
}
}
}
需要说明的是,在这里有一个问题没有解决。当参数指定类型为body的时候,我们没有对参数进行更多的解析。也就意味着我申明的对象只有2个属性,提交的body有3个属性,最终在action里面的这个参数能拿到3个属性。一直犹豫是否要做这里是否要做filter。
从后端的角度来说是毫无疑问的,不可能我一个class只声明了2个属性,而到运行时的时候能取出来3个属性。这是不可能的。
但从前端的角度来讲,这也许是一个比较好的特性。某些时候更省事情。比较接口部分参数透传的时候之类的。
参数的自动解析大致就到这里了,嗯,这部分代码可能有点小逻辑。又加上没有注释有点难理解。不过我觉得这样挺好的,哈哈哈
【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇(6/8)的更多相关文章
- 七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理
第三天我们将学习Asp.Net中数据处理功能,了解数据访问层,EF,以及EF中常用的代码实现方式,创建数据访问层和数据入口,处理Post数据,以及数据验证等功能. 系列文章 七天学会ASP.NET M ...
- 七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理 【转】
http://www.cnblogs.com/powertoolsteam/p/MVC_three.html 第三天我们将学习Asp.Net中数据处理功能,了解数据访问层,EF,以及EF中常用的代码实 ...
- 七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递
通过第一天的学习之后,我们相信您已经对MVC有一些基本了解. 本节所讲的内容是在上节的基础之上,因此需要确保您是否掌握了上一节的内容.本章的目标是在今天学习结束时利用最佳实践解决方案创建一个小型的MV ...
- 【MVC】ASP.NET MVC Forms验证机制
http://www.cnblogs.com/bomo/p/3309766.html 随笔 - 121 文章 - 0 评论 - 92 [MVC]ASP.NET MVC Forms验证机制 ASP. ...
- 返璞归真 asp.net mvc (13) - asp.net mvc 5.0 新特性
[索引页][源码下载] 返璞归真 asp.net mvc (13) - asp.net mvc 5.0 新特性 作者:webabcd 介绍asp.net mvc 之 asp.net mvc 5.0 新 ...
- asp.net mvc ,asp.net mvc api 中使用全局过滤器进行异常捕获记录
MVC下的全局异常过滤器注册方式如下:标红为asp.net mvc ,asp.net mvc api 注册全局异常过滤器的不同之处 using SuperManCore; using System. ...
- 返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller
原文:返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller [索引页][源码下载] 返璞归真 asp.net mvc (7) - asp.net ...
- 返璞归真 asp.net mvc (8) - asp.net mvc 3.0 新特性之 Model
原文:返璞归真 asp.net mvc (8) - asp.net mvc 3.0 新特性之 Model [索引页][源码下载] 返璞归真 asp.net mvc (8) - asp.net mvc ...
- 返璞归真 asp.net mvc (12) - asp.net mvc 4.0 新特性之移动特性
原文:返璞归真 asp.net mvc (12) - asp.net mvc 4.0 新特性之移动特性 [索引页][源码下载] 返璞归真 asp.net mvc (12) - asp.net mvc ...
随机推荐
- MSSQL一种取代游标的方案
今天看到一篇文章写的自己整理记录下,据说比用游标快. DECLARE @字段1 数据类型; DECLARE @字段2 数据类型; DECLARE @TMP_WHILE_ID INT; ,),TMP_W ...
- 实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制
记录一下,方便以后复制粘贴 // 方法一: Object.prototype.clone = function() { var o = this.constructor === Array ? [] ...
- Win10安装Redis
Redis安装 下载地址:https://github.com/MicrosoftArchive/redis/releases 下载对应的版本:这里下载Redis-x64-3.2.100 解压文件 进 ...
- Linux 小知识翻译 - 「Shell 脚本」
这次说说「Shell 脚本」. 根据上回的介绍,Shell就是「作为联系Linux和用户的接口而存在的软件」.在Linux环境中,通过Shell来操作系统很普遍. 这里,考虑到有时候可能想要「多次的进 ...
- JavaScript中遍历数组和对象的方法
js数组遍历和对象遍历 针对js各种遍历作一个总结分析,从类型用处,分析数组和对象各种遍历使用场景,优缺点等 JS数组遍历: 1,普通for循环,经常用的数组遍历 var arr = [1,2,0,3 ...
- 推荐5款简洁美观的Hexo主题
2018-11-17 17:15:46 原文地址:http://www.izhongxia.com 以下是 <hexo 主题列表> 中挑选出来一些比较简洁美观的主题(存在个人主观意识,请勿 ...
- Python3编写网络爬虫06-基本解析库Beautiful Soup的使用
二.Beautiful Soup 简介 就是python的一个HTML或XML的解析库 可以用它来很方便的从网页中提取数据 0.1 提供一些简单的 python式的函数来处理导航,搜索,修改分析树等功 ...
- 4、爬虫之mongodb
mongodb 简介 MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个介于关系数据库和非关系数据库之间的产品 ...
- 轻量级服务器部署方案 -(阿里云CenOS+宝塔)
一台服务器部署多个应用.可部署前端工程.node服务.数据库等. 一.服务器选择合适的服务器,购买即可.服务器商1.阿里云 2.腾讯云 3.百度云 二.服务器管理面板-宝塔宝塔面板是一款服务器管理软件 ...
- 控件_AnalogClock
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools= ...