当前位置: 首页 > news >正文

Phoenix官方教程 (五) 控制器

为什么80%的码农都做不了架构师?>>>   hot3.png

Phoenix的controllers的作用像是中间模块。它们的函数 - 这里称为actions - 被从router中调用,来对HTTP请求做出回应。action会搜集所有必要的数据,完成所有必要的步骤,在调用view层去渲染模板或者返回JSON之前。

Phoenix controllers也是基于Plug包的,而且是它们自己的plugs。Controllers提供了几乎所有我们在action中会需要的东西。如果我们要寻找一些Phoenix controllers没有提供的东西,那么我们可能正在寻找Plug。请查看Plug Guide 或 Plug Documentation 。

我们刚生成的Phoenix应用中有一个PageController,它可以在web/controllers/page_controller.ex中找到。

defmodule HelloPhoenix.PageController do
  use HelloPhoenix.Web, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

模块定义后的第一行,使用use/1宏调用了HelloPhoenix.Web模块,import了许多有用的函数。

PageController给了我们一个index动作,来呈现Phoenix欢迎页面。这个页面与在router中定义的默认route联系在一起。

Actions

控制器动作就是函数。我们可以任意命名它们,只要符合Elixir的命名规则。唯一的要求是我们必须让action名能匹配router中的定义的一个route。

例如,在web/router.ex中,我们可以把默认的action名:

get "/", HelloPhoenix.PageController, :index

修改成:

get "/", HelloPhoenix.PageController, :test

只要把PageController中的动作名也改成test,欢迎界面就会照常显示。

defmodule HelloPhoenix.PageController do
  . . .

  def test(conn, _params) do
    render conn, "index.html"
  end
end

尽管我们可以随意命名action,但为了方便,我们还是要遵守一些规则。我们在Routing Guide 中讲过了,但再看一下。

  • index - 渲染一个给定资源类型的所有物品的列表
  • show - 渲染一个特定id的物品
  • new - 渲染一个表格用于创建新物品
  • create - 接受一个新物品的参项,并保存到数据库
  • edit - 通过ID检索一个物品,并将其参项显示到表格中以便编辑
  • update - 接受编辑过的物品的参项,并保存到数据库
  • delete - 接受需要删除的物品的ID,并从数据库中删除

每个action都有2个参项,由Phoenix提供。

第一个参项总是conn,一个包含了请求相关信息的结构,例如宿主,path elements,端口,query字符串,等等。conn是通过Elixir的Plug中间框架来实现的。跟多关于conn的信息请看plug's documentation 。

第二个参项是params。它是一个包含了HTTP请求中的任何参项的映射。经过与函数中的参项的模式匹配之后,它能生成渲染所需的数据。具体请看Adding Pages guide,我们添加一个messenger参项到web/controllers/hello_controller.ex中的showroute。

defmodule HelloPhoenix.HelloController do
  . . .

  def show(conn, %{"messenger" => messenger}) do
    render conn, "show.html", messenger: messenger
  end
end

某些情况下 - 通常是在index动作中 - 我们不在乎参项,因为我们的行为不依赖于它们。此时,我们不使用收到的params,并且在变量名前加上下划线,_params。这会让编译器不发出“有未使用变量”的警告,同时也保持正确的参数个数。

收集数据

尽管Phoenix没有装载它自己的数据获取层,Elixir项目Ecto 提供了一个非常好的解决方案,对于使用Postgres 关系数据库。我们在Ecto Models Guide 中会讲解如何在Phoenix项目中使用Ecto。Ecto所支持的数据库Usage section of the Ecto README 。

当然,这里还有许多其它的数据获取方法。Ets 和 Dets键值数据存储,组成了OTP 。OTP也提供了一个关系数据库,叫做mnesia ,用的是它自己的query语言,叫做QLC。Elixir和Erlang都有许多库,可以用于操作各种流行的数据存储。

数据对你很重要,但在本教程中没有涵盖这些设置。

Flash 信息

有时我们需要在动作执行后与用户交流。也许是在更新一个model时出现了一个错误。也许我们只想欢迎用户回来。所以,我们有了Flash信息。

Phoenix.Controller模块提供了put_flash/3get_flash/2函数,来帮助我们设置和检索flash信息,以键值对的形式。让我们在HelloPhoenix.PageController中设置两个flash信息。

index动作修改为:

defmodule HelloPhoenix.PageController do
  . . .
  def index(conn, _params) do
    conn
    |> put_flash(:info, "Welcome to Phoenix, from flash info!")
    |> put_flash(:error, "Let's pretend we have an error.")
    |> render("index.html")
  end
end

Phoenix.Controller模块并不限制我们使用的键。只要内部一致,什么键都可以。:info:error只是常用的罢了。

为了看见我们的flash信息,我们需要检索它们,并在template/layout中显示它们。完成检索的一种方式就是使用get_flash/2,它的参数是conn和我们的键。然后它就会返回那个键的值。

幸运的是,我们的应用layout,web/templates/layout/app.html.eex,已经把这些弄好了。

<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>

重新载入Welcome Page,就能看到它们了。

除了put_flash/3get_flash/2, Phoenix.Controller中还有一个值得了解的函数。clear_flash/1函数,参数是conn,会删除会话中的所有flash信息。

渲染

控制器有许多种方法来渲染内容。最简单的就是渲染一些文本,使用Phoenix提供的text/2函数。

假设我们有一个show action,它从params映射汇总接收id,然后我们要做的就是返回这个id以及一些文本。我们能这样做。

def show(conn, %{"id" => id}) do
  text conn, "Showing id #{id}"
end

假设我们已经有一个route,get "/our_path/:id",指向这个show action,在浏览器中访问/our_path/15将会显示Showing id 15这段不带任何HTML格式的文本。

另一种是使用json/2函数渲染纯JSON。我们需要传送给它一些可以由Poison library 解析成JSON的东西,例如一个映射。(Poison是Phoenix的依赖之一)

def show(conn, %{"id" => id}) do
  json conn, %{id: id}
end

如果我们再次访问our_path/15,我们会看到一个JSON块,内容是键id指向数字15

{"id": "15"}

Phoenix控制器也可以不用模板渲染HTML。没错,就是html/2函数。

def show(conn, %{"id" => id}) do
  html conn, """
     <html>
       <head>
          <title>Passing an Id</title>
       </head>
       <body>
         <p>You sent in id #{id}</p>
       </body>
     </html>
    """
end

访问/our_path/15,现在渲染的是在show中定义的HTML字符串,其中插值了15。注意我们没有用eex模板。这是一个多行字符串,所以我们的插值用的是#{id}而不是<%= id %>

text/2, json/2, 和 html/2函数,不需要渲染Phoenix view,或者模板。但这不是重点。

json/2函数很明显适用于编写API,其它两者可能也很好用,但是渲染一个带值得模板到layout中,是很常见的。

所以,Phoenix提供了render/3函数。

在内部,render/3定义与Phoenix.View模块,而不是Phoenix.Controller,但为了方便,它在Phoenix.Controller中有别名。

在Adding Pages Guide 中,我们已经看过了render函数。在web/controllers/hello_controller.ex中,我们的showaction会是这样。

defmodule HelloPhoenix.HelloController do
  use HelloPhoenix.Web, :controller

  def show(conn, %{"messenger" => messenger}) do
    render conn, "show.html", messenger: messenger
  end
end

为了使render/3正常工作,控制器的名称和对应view的名要相同,而view名要和show.html.eex模板所在目录的名称相同。换句话说,HelloController 需要HelloView, 而 HelloView 需要 web/templates/hello 目录, 其中必须包含 show.html.eex 模板。

render/3也传送了show动作从params散列中为messenger接收到的值给模板,用于插值。

如果在使用render时,我们需要传送值给模板,这很简单。我们可以像刚才那样传送一个词典,messenger: messenger,或者使用Plug.Conn.assign/3,它能方便地返回conn

def index(conn, _params) do
  conn
  |> assign(:message, "Welcome Back!")
  |> render("index.html")
end

注意: Phoenix.Controllerimports了Plug.Conn,所以用短语,assign/3,是没问题的。

我们可以在index.html.eex模板中获取这个信息,或者是在我们的layout中,使用<%= @message %>

传送更多的值到模板中,就和把assign/3函数堆积在管道中一样简单。

def index(conn, _params) do
  conn
  |> assign(:message, "Welcome Back!")
  |> assign(:name, "Dweezil")
  |> render("index.html")
end

这样,在index.html.eex模板中,@message@name 都是可用的。

如果我们想有一个默认的欢迎信息,而且可由一些action重写,要怎么做?很简单,只需要在conn通往控制器action的路上,使用plug来变换它。

plug :assign_welcome_message, "Welcome Back"

def index(conn, _params) do
  conn
  |> assign(:name, "Dweezil")
  |> render("index.html")
end

defp assign_welcome_message(conn, msg) do
  assign(conn, :message, msg)
end

如果我们想让 plug assign_welcome_message只适用于一部分actions呢?Phoenix提供了一个方法,使得可以选择那些action会使用一个plug。如果我们希望plug :assign_welcome_message只作用于indexshowactions,我们可以这样做。

defmodule HelloPhoenix.PageController do
  use HelloPhoenix.Web, :controller

  plug :assign_welcome_message, "Hi!" when action in [:index, :show]
. . .

直接发送响应

如果上述的渲染方法都不符合我们的需求,我们可以自己使用plug提供的函数来定制。假设我们想发送一个响应,Status是“201”。我们可以用send_resp/3函数来实现。

def index(conn, _params) do
  conn
  |> send_resp(201, "")
end

重新载入http://localhost:4000将会呈现一个完全的空白页面。但在我们的浏览器开发者工具的network一栏中,可以看到接收了一个Status为201的响应。

如果我们想指定内容的类型,我们可以用,put_resp_content_type/2send_resp/3的组合。

def index(conn, _params) do
  conn
  |> put_resp_content_type("text/plain")
  |> send_resp(201, "")
end

以这种方式使用Plug函数,我们可以打造自己需要的响应。

尽管渲染不易模板为结尾。但默认的,模板渲染的结果会插入到layout中,而layout也会被渲染。

Templates and layouts 有它们自己的教程,所以这里就不多讲了。我们将关注的是如何在控制器动作内,指定不同的layout,或都不用。

指定layouts

Layouts只不过是一种特殊的模板。它们存在于/web/templates/layout 中。Phoenix在我们生成app时为我们创建了一个。它叫做app.html.eex,所有模板都会默认在它之中渲染。

因为layouts只是模板,所以它们需要一个view来渲染。那就是定义在/web/views/layout_view.ex中的LayoutView模块。所以我们只需要把想要渲染的layouts放到/web/templates/layout目录中就可以了。

在创建新layout之前,让我们不用layout渲染一个模板。

Phoenix.Controller模块提供了put_layout/2函数,用于选择layouts。它的第一个参数是conn以及我们想要渲染的layout的名字的字符串。函数另一个从句会为第二个参数匹配布尔值false,这也是我们如何不带layout地渲染Phoenix欢迎页面。

在一个新生成的Phoenix app中,编辑PageController 模块web/controllers/page_controller.ex中的indexaction。

def index(conn, params) do
  conn
  |> put_layout(false)
  |> render "index.html"
end

重载http://localhost:4000/ ,我们会看到一个非常不同的页面,没有标题,图片,或是css风格。

有一点很重要!对于这种在管道中间被调用的函数,比如这里的put_layout/2,需要用括号包裹参数,因为管道操作结合得非常紧密。否则很可能导致解析错误,和非常奇怪的结果。

如果你得到了像这样的stack trace,

**(FunctionClauseError) no function clause matching in Plug.Conn.get_resp_header/2

Stacktrace

    (plug) lib/plug/conn.ex:353: Plug.Conn.get_resp_header(false, "content-type")

在这里你的参数取代了conn成为了第一个参数,首先要检查的就是括号有没写好。

这是对的:

def index(conn, params) do
  conn
  |> put_layout(false)
  |> render "index.html"
end

这是错的:

def index(conn, params) do
  conn
  |> put_layout false
  |> render "index.html"
end

现在让我们创造另一个layout,并往其中渲染一个index模板。作为例子,我们的这个layout是提供给admin的,在其中没有logo图片。为了做到它,让我们复制已存的app.html.eex,到同一个目录web/templates/layout下的新文件admin.html.eex。然后让我们删除admin.html.eex中显示logo的那一行。

<span class="logo"></span> <!-- remove this line -->

然后,将新layout的basename传送给web/controllers/page_controller.ex中的index action里的put_layout/2

def index(conn, params) do
  conn
  |> put_layout("admin.html")
  |> render "index.html"
end

当载入页面时,我们会渲染admin layout,不带logo。

重写渲染格式

模板渲染十分好,能否随意改格式?假设我们有时需要文本,有时需要JSON,有时需要HTML。那要怎么办?

Phoenix允许我们在fly时通过_format query string参项来修改格式。为了实现它,Phoenix要求一个合适命名的view,和一个合适命名的模板,在正确的目录中。

作为例子,让我们看看刚生成的应用的PageController中的 index action。这里有正确的view,PageView,正确的模板目录,/web/templates/page,和正确的模板用于渲染HTML,index.html.eex

def index(conn, _params) do
  render conn, "index.html"
end

缺少的是一个可供替代的模板,用于渲染文本。让我们添加一个/web/templates/page/index.text.eex。这是我们的index.text.eex模板。

OMG, this is actually some text.

还有一点点工作要做。我们需要告诉router,应该接受text格式。需要往:browser 的管道中的接受格式列表里,添加text。让我们打开web/router.ex,并修改plug :accepts,让其包含texthtml

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html", "text"]
    plug :fetch_session
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
. . .

我们还需要告诉控制器,在渲染模板时使用和Phoenix.Controller.get_format/1的返回值相同的模板。只需将模板"index.html"替换为:index

def index(conn, _params) do
  render conn, :index
end

如果我们打开http://localhost:4000/?_format=text, 会看到 OMG, this is actually some text.

当然,我们也可以传送数据到模板。让我们删除函数定义中params前面的_,这样我们的action就会接受一个信息作为参项。这一次,我们会使用较少弹性的字符串版本的text模板名,只是为了看看它能否工作。

def index(conn, params) do
  render conn, "index.text", message: params["message"]
end

让我们丰富一下text模板。

OMG, this is actually some text. <%= @message %>

现在,打开http://localhost:4000/?_format=text&message=CrazyTown,我们会看到"OMG, this is actually some text. CrazyTown"

设置内容类型

_format query string param相似,我们可以通过修改HTTP内容类型头文件并提供合适的模板,来渲染任何格式。

如果我们想要渲染xml版本的index action,我们需要在web/page_controller.ex里这样写。

def index(conn, _params) do
  conn
  |> put_resp_content_type("text/xml")
  |> render "index.xml", content: some_xml_content
end

我们还需要提供一个index.xml.eex模板,其中是合法的xml,然后就完成了。

想得到合法的mime类型的列表,请查看mime.types 文档。

设置HTTP Status

和设置内容类型的方法类似,我们也可以设置一个相应的HTTP状态码。Plug.Conn模块,已经被import到了所有控制器,具有一个put_status/2函数来实现。

put_status/2第一个参数是conn,第二个参数要么是个整数,要么是一个我们想要设置成状态码的"friendly name",作为原子来使用。这里是所支持的friendly names 的列表。

让我们修改PageController index action中的状态。

def index(conn, _params) do
  conn
  |> put_status(202)
  |> render("index.html")
end

我们给定的状态码必须合法,否则 Cowboy ,Phoenix运行于的web服务器,就会抛出错误。如果我们查看开发日志(这里指iex会话),或者使用浏览器的网络检查工具,我们将会在重载页面时看见设置好的状态码。

如果action发送一个响应 - 渲染或者重定向 - 改变代码将不会改变响应的行为。例如,如果我们将status设置成404或500,然后render "index.html",我们将不会得到错误页面。同样,没有一个300状态码会真的重定向。(它不知道重定向到哪里,即使代码确实影响了行为。)

下列HelloPhoenix.PageController index action的代码,将不会像预期的那样渲染默认的not_found行为。

def index(conn, _params) do
  conn
  |> put_status(:not_found)
  |> render("index.html")
end

HelloPhoenix.PageController中渲染404页面的正确方法是:

def index(conn, _params) do
  conn
  |> put_status(:not_found)
  |> render(HelloPhoenix.ErrorView, "404.html")
end

重定向

通常,在一个请求的中间,我们需要重定向到一个新的url。一个成功的create动作,在实际中,通常会重定向到show动作,为我们刚创建的model。也可以重定向到index动作,来展示同类的所有东西。还有许多情形下,重定向也很有用。

不管什么情况,Phoenix控制器提供的redirect/2函数都能让重定向变简单。Phoenix区别对待应用内的重定向,和重定向到url - 应用内的或外部的。

为了测试redirect/2,让我们在web/router.ex中创造一个新的route。

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router
  . . .

  scope "/", HelloPhoenix do
    . . .
    get "/", PageController, :index
  end

  # New route for redirects
  scope "/", HelloPhoenix do
    get "/redirect_test", PageController, :redirect_test, as: :redirect_test
  end
  . . .
end

然后我们会让index action重定向到我们的新route。

def index(conn, _params) do
  redirect conn, to: "/redirect_test"
end

最后,让我们在同一个文件中定义我们重定义到的action,它简单地渲染了文本Redirect!

def redirect_test(conn, _params) do
  text conn, "Redirect!"
end

当我们重载Welcome Page,会看到我们已经重定向到了/redirect_test,它渲染了文本Redirect!。成功了!

我们可以打开开发者工具,点击网络那一栏,再次访问我们的根route。我们看到该页面有两个主要请求 - 一个对/的get,附带302状态,以及一个对/redirect_test的get,附带200状态。

注意到重定向函数的参数是conn和一个代表应用内相关路径的字符串。它的参数也可以是conn和一个代表完整url的字符串。

def index(conn, _params) do
  redirect conn, external: "http://elixir-lang.org/"
end

我们也可以使用在Routing Guide中学过的path helpers。alias那些web/router.ex中的helpers,可以缩短表达式。

defmodule HelloPhoenix.PageController do
  use HelloPhoenix.Web, :controller

  def index(conn, _params) do
    redirect conn, to: redirect_test_path(conn, :redirect_test)
  end
end

注意下面的是错的。因为:to不接受path之外的东西。

def index(conn, _params) do
  redirect conn, to: redirect_test_url(conn, :redirect_test)
end

如果我们想使用url helper来传送完整的url到redirect/2,我们必须使用原子:external。注意url不一定要是外部的。

def index(conn, _params) do
  redirect conn, external: redirect_test_url(conn, :redirect_test)
end

转载于:https://my.oschina.net/ljzn/blog/733713

相关文章:

  • PostgreSQL实战(12)高级特性
  • base64图上上传保存到服务器
  • ajax报错302重定向错误
  • ArcGIS Engine开发之旅03--ArcGIS Engine中的控件
  • kafka消费过程中失败,kafka重试补偿
  • 从0到1搭建属于自己的服务器
  • PostgreSQL实战(2)数据结构
  • 金蝶kis记账王初始化过程中如何设置科目
  • SpringBoot项目的jar包在启动时选择的多环境配置以及加载顺序
  • PostgreSQL中date数据类型
  • springmvc带参数链接跳转,实现单一样式容器
  • Spring Boot 打包分为 war 格式,放到Tomcat下报错的解决方案
  • 窗体的事件
  • PostgreSQL序列的创建和使用
  • PostgreSQL的数据备份与恢复(windows版本)
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • ES6语法详解(一)
  • JavaScript服务器推送技术之 WebSocket
  • Linux gpio口使用方法
  • PAT A1120
  • React系列之 Redux 架构模式
  • SQL 难点解决:记录的引用
  • vue.js框架原理浅析
  • windows下使用nginx调试简介
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 码农张的Bug人生 - 见面之礼
  • 那些年我们用过的显示性能指标
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 白色的风信子
  • linux 淘宝开源监控工具tsar
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • (09)Hive——CTE 公共表达式
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • ***测试-HTTP方法
  • .net core控制台应用程序初识
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .Net Web窗口页属性
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • [20180224]expdp query 写法问题.txt
  • [C++]二叉搜索树
  • [CC-FNCS]Chef and Churu
  • [codeforces]Recover the String
  • [Effective C++读书笔记]0012_复制对象时勿忘其每一部分
  • [Excel VBA]单元格区域引用方式的小结
  • [ExtJS5学习笔记]第三十节 sencha extjs 5表格gridpanel分组汇总
  • [GN] Vue3快速上手1
  • [Machine Learning] 领域适应和迁移学习
  • [oeasy]python0002_终端_CLI_GUI_编程环境_游戏_真实_元宇宙