如何将包含文件的表单提交给 FOSRestBundle 控制器

How to submit a form that contains a file to a FOSRestBundle controller

我在客户端使用 AngularJS 向服务器端的控制器提交表单。

因为我们在应用程序中存储文件并且用户提供了一些关于文件的元数据,所以工作流不能拆分成更小的任务。

我在 Angular 的 $http 服务上使用请求转换构建了提交数据。 Web 服务客户端的当前状态:

function _save(dataModel, formfile)
{
    $http({
        url : basePath + (dataModel.id ? ("/" + dataModel.id) : ""),
        method : "POST",
        headers : {
            'Content-Type' : undefined
        },
        transformRequest : function(data)
        {
            var formData = new FormData();
            formData.append("dto", angular.toJson(data.model));
            formData.append("file", data.file);
            return formData;
        },
        data : {
            model : dataModel,
            file : formfile
        }
    }).then(function(response)
    {
    });
}

不幸的是,我收到了这样的回复:

415 Unsupported Media Type

我无法确定 Symfony 堆栈的哪个组件提供了回复,也无法确定它是指 multipart/form-data 还是附加到文件的 Content-Type: application/octet-stream 规范。

我能做些什么来调试和解决这个问题吗?我怀疑这是配置问题。以下是我在 config.yml:

中添加到默认 Symfony 配置的元素
# Nelmio CORS Configuration
nelmio_cors:
    defaults:
        allow_credentials: false
        allow_origin: ['*']
        allow_headers: ['*']
        allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
        max_age: 3600
        hosts: []
        origin_regex: false

# FOSRest Configuration
fos_rest:
    body_listener: true
    body_converter:
        enabled: true
        validate: true
        validation_errors_argument: validationErrors # This is the default value
    format_listener:
        rules:
            - { path: '^/', priorities: ['json'], fallback_format: json, prefer_extension: false }
    param_fetcher_listener: true
    view:
        view_response_listener: 'force'
        formats:
            json: true
            html: false
#
# Needed for being able to use ParamConverter
sensio_framework_extra:
    request: { converters: true }

我已经解决了使用 JavaScript 将文件转换为 base64 字符串并将文件作为文本字符串发送的问题。

我实施的解决方案归结为手动处理通常由 ParamConverter 注释(在此项目中)处理的任务(反序列化和验证):

  1. 更改控制器的签名
  2. 手动反序列化数据
  3. 手动验证数据

另外,错误消息似乎是由 JMSSerializer 发送的。

现在:

/**
 * @Rest\Post("")
 */
public function postAction(Request $request)
{
  //
  // Deserialisation
  $dto = $this->deserializeDto($request);
  if ($dto == null) {
    return $this->view("Invalid 'dto' parameter contents.", response::HTTP_BAD_REQUEST);
  }

  //
  // Validation: the name ('Nom') and a file are required
  $validationErrors = [ ];
  if ($dto->getNom() === null || strlen($dto->getNom()) == 0) {
    $validationErrors[] = "'Nom' is missing";
  }
  $file = $request->files->get("file");
  if ($file === null) {
    $validationErrors[] = "No file provided";
  }
  if (count($validationErrors) > 0) {
    $view = $this->view($validationErrors, response::HTTP_BAD_REQUEST);
    return $view;
  }

  //
  // processing
  return $this->doSave($dto, $request);
}

以前(我可能有过这样的事情):

/**
 * @Rest\Post("")
 * @ParamConverter("dto", converter="fos_rest.request_body", options={"validator"={"groups"={"edit"}}})
 * @Rest\QueryParam(name="dto", nullable=false)
 */
public function postAction(Request $request, Document $dto, ConstraintViolationListInterface $validationErrors)
{
....
}

可能有更好的方法来做到这一点(包括验证部分),但我需要在这一点上前进;稍后会进行重构。