通常,在rails中处理文件上传,我们会这么做,在view中生成相应html tag:
<input name="my_uploaded_file" type="file">
然后,在controller中,我们可以通过params[:my_uploaded_file]得到上传文件,进行相应处理。
假如,现在作为controller的开发人员,我不知道view开发人员将input的name设置成什么?那应该如何处理呢?可能这个例子有些极端,绝大部分时候不存在这个问题。那再假如,现在我们要利用rails实现一个api,该api的功能是实现文件上传,rails后台得到该文件,并进行处理。由于这是一个开放api,用户可能通过jsp,rails,php,甚至桌面程序等来访问我们的api,这个时候又该如何处理呢?(当然,你可以在api specification中定死,说,我的地盘我做主,你们要使用我的api上传文件,form-data的name必须是XXX!)
在asp.net中倒是可以很简单的解决这个问题,我记得asp.net中的form提供了一个参数,开发人员可以直接访问该参数得到此form中所有上传的文件,而不必管name是什么。但是在rails中,似乎没有这一机制(如果是我疏忽,望知情同学告知!)。不过,现在稍加修改,rails也同样能象asp.net那样处理上传文件。
首先,我先贴出解决方案,为方便起见,在environment.rb中加入如下代码:
module ActionController class AbstractRequest class << self alias_method :_original_read_multipart_, :read_multipart def read_multipart(body, boundary, content_length, env) params = _original_read_multipart_(body, boundary, content_length, env) file_data = params.values.dup.flatten.select do |form_data| form_data.respond_to?(:original_path) && !form_data.original_path.blank? end params.merge!({"file_data[]" => file_data}) end private :_original_read_multipart_, :read_multipart end end end
我先大致解释下这段代码:在view中的某个form中,当你加入{:multipart => true}参数,rails会调用AbstractRequest中的私有类方法read_multipart,得到form中所有的参数。所以,我的解决方案就是截获此方法,调用原始版本后,遍历form中的所有值,将所有上传的文件放到一个file_data中去,塞回params中。这样,在controller中,就可以通过params[:file_data]得到form中所有上传文件进行处理,而并不用理会input的name到底是什么。这样做,我们甚至可以处理这种情况:
<input name="same_name" type="file"> <input name="same_name" type="file">
就算这两个input的名字相同,通过file_data,仍然可以得到两个文件。这样,controller得到上传文件的代码便独立了,和view没有任何关系,你爱咋写name都行。另外,这行代码有两个地方值得注意:
params.merge!({"file_data[]" => file_data})
1. 关于key,不能想当然的用:file_data[]。因为rails内部处理参数的时候,调用了key.include?,所以,使用Symbol必然出错。
2. [] 是必须的,否则rails不能将所有的上传文件塞到一个数组中去,这样,在controller中,你将只能通过file_data得到其中一个文件。
另外,上述代码是基于rails 2.0.2。加入你目前正在使用rails 1.*。则可以使用如下代码实现相同的功能:
class CGI #:nodoc: module QueryExtension alias_method :_original_read_multipart_, :read_multipart def read_multipart(boundary, content_length) params = _original_read_multipart_(boundary, content_length) file_data = params.values.dup.flatten.select do |form_data| form_data.respond_to?(:original_path) && !form_data.original_path.blank? end params.merge!({"file_data[]" => file_data}) end private :_original_read_multipart_, :read_multipart end end
2008.7.19 22:22 星期六