控制器是整个web程序的核心类,它接收来自浏览器的请求,以模型对象为中心调用对应的业务逻辑代码,然后根据处理结果结合模板生成最终的视图层HTML代码,最后作为响应返回给浏览器。
1.动作
所谓动作,就是根据请求的URL调用
的某个与之对应的控制器成员函数。我们可以在控制器头文件中的public slots
中声明动作,其他地方声明的任何函数都将不会被视为动作。动作可以接收最多10
个参数,这些参数的类型一般为QString
。
class T_CONTROLLER_EXPORT FooController : public ApplicationController
{
Q_OBJECT
:
public slots: //这里开始就是动作的定义
void bar();
void baz(const QString &str);
:
2.动作与URL的匹配
请求的URL
和实际调用的动作之间的匹配规则如下:
/控制器名称/动作名称/动作参数1/动作参数2/…
因为动作最多只能接收10
个参数,所以如果真的需要传输10
个以上的参数,可以考虑使用URL
参数或者POST
数据,下面会介绍相关的用法。
下面就是若干个匹配的例子:
/blog/show → show();
/blog/show/2 → show(QString("2"));
/blog/show/foo/5 → show(QString("foo"), QString("5"));
如果省略了动作名,那么默认会匹配index
这个动作,也就是说会像下面这样:
/blog → index();
如果根据URL
找不到对应的动作,那么将会返回500
状态码(服务器内部错误)给浏览器。
3.动作的标准处理流程
- 请求解析
- 从
session
或者cookie
中提取数据 - 处理好上传的文件
- 调用逻辑层(业务逻辑)
- 根据处理结果把数据传给视图层
- 把最终生成的
HTML
代码作为响应返回给浏览器
其中,需要特别留意的是不能把大量的业务逻辑代码放在控制器,这样会控制器显得臃肿复杂,况且TreeFrog
为我们提供了十分方便好用的模型层,我们应该把主要的业务逻辑代码放到那里运行。
总之,记住这么一条铁律:控制器的代码要少而清晰。
4.请求对象
在TreeFrog
中,HTTP
请求被封装为THttpRequest
对象,我们可以通过调用控制器的httpRequest()
方法来获得它,然后再提取出我们想要的数据。
const THttpRequest & TActionController::httpRequest () const;
5.从请求对象中获取数据
连同HTTP
请求一起传过来的数据一般有以下几种:
POST
数据:一般是整个表单的数据被POST
过来URL
参数:一般位于整个URL
的最后,格式类似于:?键名1=值1&键名2=值2...
- 动作参数:也是位于整个
URL
的最后,前面提到过,格式类似于:/动作名称/动作参数1/动作参数2/…
5.1.获取POST数据
假设浏览器前端有如下的文本框,并且它的内容连同HTTP
请求一起发送到后台:
<input type="text" name="title" />
那么我们可以这样获得它的内容:
QString val = httpRequest().formItemValue("title")
如果你觉得通过反复调上面的函数拿到一个个POST
数据十分麻烦的话,可以像下面这样编写网页:
<input type="text" name="blog[title]" />
<input type="text" name="blog[body]" />
然后在控制器中,可以拿到存储参数键值对的哈希表,然后再逐一取出数据:
QVariantMap blog = httpRequest().formItems("blog");
QVariant t = blog["title"];
QVariant b = blog["body"];
5.2.获取URL参数
如果浏览器端发送过来的请求URL
如下:
http://example.com/blog/index?mode=normal
如果我们想取得mode
的值,那么可以通过下面的方法:
QString val = httpRequest().queryItemValue("mode");
顺带一提,如果想把POST
数据和URL
参数不加区别地统一获取,可以使用parameter()
或者allParameters()
方法。
6.往视图层传输数据
如果要往视图层传输数据,可以通过宏定义texport(变量名)
或者T_EXPORT(变量名)
来声明。这些数据变量只要是可以通过类型转换为QVariant
都可以,比如int
、QString
、QList
、QHash
或者自定义的模型类。用法如下:
QString foo = "Hello world";
texport(foo);
//或者
int bar;
bar = …
texport(bar);
注意:texport
的参数必须为变量,不能是Hello world
或者100
等字面量。
在这之后,视图就可以通过声明tfetch(变量类型,变量名)
,然后使用该变量。
7.往视图层传输类对象
首先要在这个自定义类的头文件的末尾增加下面的宏定义:
Q_DECLARE_METATYPE(自定义类的名称)
一般的QT
类是不需要加上Q_DECLARE_METATYPE
宏定义的,但是对于QList
、QHash
等模板类却是例外。这种情况下,可以在helpers/applicationhelper.h
文件的末尾加上类似于下面的代码:
Q_DECLARE_METATYPE(QList<float>)
如果Q_DECLARE_METATYPE
宏定义里面有逗号(比如QHash<Foo, Bar>
),那么将会造成编译错误。这种情况下,可以考虑使用typedef
关键字。
typedef QHash<Foo, Bar> BarHash;
Q_DECLARE_METATYPE(BarHash)
8.生成视图
在执行完业务逻辑之后,就差不多可以生成HTML
代码响应请求了。例如对于BlogController
的show
动作,当我们调用render()
方法时,就会根据views/blog/show.xxx
模板文件生成最终的HTML
页面(后缀名与当前使用的模板系统相对应)
bool render(const QString &action = QString(), const QString &layout = QString());
如果想使用别的模板文件,可以通过render
的action
参数来改变。
9.render方法的layout参数
对于绝大多数的web程序
,各个页面的头部(导航)、尾部(友情链接)基本都是相同的,只不过是网页的中间内容有所不同而已。所以layout
就是指这些各个页面可以共用的模板,它们一般存放在views/layouts
目录下。
10.关于模板文件的命名
ERB
系统:xxx.erb
Otama
系统:xxx.html
和xxx.otm
(xxx是默认匹配的动作名称)
11.直接返回字符串作为响应
这种情况下,可以使用renderText()
方法:
renderText("Hello world");
12.重定向
将本身属于自己的请求移交别的URL
处理,可以使用redirect()
方法。
//重定向到www.example.org
redirect(QUrl("www.example.org"));
或者移交给其他动作执行,写法如下:
//重定向到Blog控制器的index动作
redirect( url("blog", "index") );
如果和重定向的目标动作同属于一个控制器之下,可以省略控制器名
//重定向到同一个控制器的show动作
redirect( urla("show") );
13.往重定向目标发送消息
在TreeFrog
框架中,可以往重定向目标发送消息,具体做法就是调用tflash
或者T_FLASH
方法。
//设置foo为flash对象
QString foo = "successfully";
tflash( foo );
上面发送过去的foo
变量被称为flash对象(消息)
,此后,重定向目标的视图层可以把它当做导出数据来使用。
14.staticInitialize方法
如果想在程序启动的时候执行一段代码(比如:预先从数据库中读取初始数据),可以在ApplicationController
的staticInitialize
方法里面写:
void ApplicationController::staticInitialize()
{
// do something..
}
15.控制器的生命周期
控制器的实例会在,动作被执行之前被生成,动作执行之后就被销毁。因此,对于每一个HTTP
请求都会生成并销毁一个控制器实例。因此持有一个控制器的引用或者指针是很不明智的做法。