【问题标题】:Retrofit 2 can't upload a file with two additional separate string parametersRetrofit 2 无法上传带有两个额外的单独字符串参数的文件
【发布时间】:2016-07-05 10:44:32
【问题描述】:

阅读问题底部的编辑以了解可能的替代解决方案,直到找到解决方案

这是一个使用 POSTMan 的带有两个参数的成功发布文件。我正在尝试对改造做同样的事情,但收到 BadRequest。

邮递员设置:

Chrome 网络帖子详情:

现在这是我在 Android 中执行此操作但失败的方式:

改造服务接口:

@Multipart
@POST("jobDocuments/upload")
Call<ResponseBody> upload(@Part("file") MultipartBody.Part file,@Part("folder") MultipartBody.Part folder,@Part("name") MultipartBody.Part name);

这是我的@Background 方法来运行生成上述服务的网络请求

CustDataClient service =
            ServiceGenerator.createService(CustDataClient.class);
    File file = new File(fileUri.getPath());
    // create RequestBody instance from file
    RequestBody requestFile =
            RequestBody.create(MediaType.parse("multipart/form-data"), file);

    MultipartBody.Part fileData =
            MultipartBody.Part.createFormData("file", fileName, requestFile);
    MultipartBody.Part folder =
            MultipartBody.Part.createFormData("folder", "LeadDocuments");
    MultipartBody.Part name =
            MultipartBody.Part.createFormData("name", fileName);
    // finally, execute the request
    Call<ResponseBody> call = service.upload(fileData,folder,name);
    try {
        Response<ResponseBody> rr = call.execute();
        ResponseBody empJobDocsResult = rr.body();//Bad Request here :(
        Log.v("Upload", "success");
    } catch (Exception ex) {
        Log.e("Upload error:", ex.getMessage());
    }

这是我的 Web Api 方法:

 [Route("upload")]
    [HttpPost]
    public IHttpActionResult Upload()
    {
        if (HttpContext.Current.Request.Files.AllKeys.Any())
        {
            // Get the uploaded image from the Files collection
            var httpPostedFile = HttpContext.Current.Request.Files["file"];

            if (httpPostedFile != null)
            {
                // Validate the uploaded image(optional)
                var folder = HttpContext.Current.Request.Form["folder"];
                var fileName = HttpContext.Current.Request.Form["name"];
                fileName = string.IsNullOrEmpty(fileName) ? httpPostedFile.FileName : fileName;
                // Get the complete file path
                var fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/Files/" + folder), fileName);

                // Save the uploaded file to "UploadedFiles" folder
                httpPostedFile.SaveAs(fileSavePath);

                return Ok(new OkMessage { Message = "File uploaded successfully", Path = "/Files/" + folder + "/" + fileName });
            }
        }

        return BadRequest("File not uploaded");
    }

请帮助我在哪里出错以及如何实现这一点,是否有任何简单的替代方案来改造?

[编辑] 此代码运行成功,感谢koush/ion

Ion.with(getContext())
                            .load("POST", "http://www.dgheating.com/api/jobDocuments/upload")
                            .setMultipartParameter("folder", "LeadDocuments")
                            .setMultipartParameter("name", fileName)
                            .setMultipartFile("file", new File(imagePath))
                            .asJsonObject()
                            .setCallback(...);

【问题讨论】:

  • 我在 Android 上遇到了同样的问题,确切地说是 4.1.1;现在使用带有 Ion 的示例解决了问题,现在我可以轻松地上传文件(在我的情况下为图像)而不会出现问题。您应该将 Ion 编辑作为回复发布(我猜)。
  • 不,这不是问题的解决方案,而是替代方案(避免问题)。
  • 这太糟糕了。你有没有找到解决这个问题的方法?
  • nvm,我刚看到你的编辑,Ion 会的。

标签: android file-upload retrofit android-networking retrofit2


【解决方案1】:

我在这里遇到了类似的问题:Android with Retrofit2 OkHttp3 - Multipart POST Error

在接受@TommySM 的建议后,我的问题得到了解决。如果您仍然不清楚,我认为这是解决方案:

@Multipart
@POST("jobDocuments/upload")
Call<ResponseBody> upload(
    @Part MultipartBody.Part file,
    @Part("folder") RequestBody folder,
    @Part("name")   RequestBody name);

File file = new File(fileUri.getPath());

// Assume your file is PNG
RequestBody requestFile =
        RequestBody.create(MediaType.parse("image/png"), file);

MultipartBody.Part fileData =
        MultipartBody.Part.createFormData("file", fileName, requestFile);

RequestBody folder = RequestBody.create(
        MediaType.parse("text/plain"),
        "LeadDocuments");

RequestBody name = RequestBody.create(
        MediaType.parse("text/plain"),
        fileName);

// finally, execute the request
Call<ResponseBody> call = service.upload(fileData, folder, name);

重要的部分是使用MediaType.parse("text/plain")作为String参数的MediaType(我相信你的情况是:文件夹和名称参数),使用okhttp3.MultipartBody.FORM是错误的。

查看这些屏幕截图进行比较:

1) 有问题的 POST

2) 正确的 POST

【讨论】:

【解决方案2】:

所以,希望现在还不算太晚,如果是这样 - 它可能会帮助其他人:) 我的 2 美分是在不久前遇到同样的问题之后:

服务定义,仅使用@Part(根据您的服务器期望修改字段名称)

//Single image MultiPart
@Multipart
@POST("user/imageupload")
Call<ResponseBody> upload(@Part("userfile") RequestBody file, @Part("userid") RequestBody description);

对于魔术,我将这两个部分都称为RequestBody,使用MediaType.parse() 来表示每个部分都有自己的类型,依赖于请求本身的@Multipart 定义,不需要@987654328 @ stuff,同样适用于多个文件和多个字段:

private static final String IMG_JPEG = "image/jpeg";
private static final String TXT_PLAIN = "text/plain";

public void uploadImageMultipart(Uri uri, final CustomEventListener<String> listener)
{
    RequestBody fileBody;
    RequestBody textBody;
    File file = new File(uri.getPath());
    Call<ResponseBody> requestCall;

    fileBody = RequestBody.create(okhttp3.MediaType.parse(IMG_JPEG), file);
    textBody = RequestBody.create(okhttp3.MediaType.parse(TXT_PLAIN), String.valueOf(SettingsManager.getUserID()));

      requestCall = serviceCaller.upload(fileBody, textBody);
      requestCall.enqueue(new Callback<ResponseBody>()
      {
        @Override
        public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
        {
            try
            {
                String response = rawResponse.body().string();
                //from here it's your show....
                listener.getResult("Got it");
            }
            catch (Exception e)
            {
                        e.printStackTrace();
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable throwable)
        {

        }
    });
}

(我使用监听器将回调响应返回到应用程序的不同部分(例如调用活动))。

这肯定会发送文件和文本字段,其他问题可能源于服务器端。

希望对您有所帮助!

【讨论】:

  • 您的解决方案最适合我。我在这里遇到了类似的问题:stackoverflow.com/questions/43320205/…。问题出在 String 的主体部分,使用 text/plain 的 MediaType 修复了它,如建议:textBody = RequestBody.create(okhttp3.MediaType.parse(TXT_PLAIN), String.valueOf(SettingsManager.getUserID()));
  • @shuwnyuantee 很高兴能帮上忙,您也可以参考stackoverflow.com/questions/28172496/… 了解使用 CustomEventListener 接口的简洁方法(Volley 和 Retrofit 的总体思路类似)
【解决方案3】:

改造接口 (API)

public interface API {

    String NAME = "name";
    String FOLDER = "folder";
    @Multipart
    @POST("/api/jobDocuments/upload")
    Call<JsonResponse> uploadFile(
        @Part(NAME) String name,
        @Part(FOLDER) String folder,
        @Part MultipartBody.Part file);
}

响应类 (JsonResponse)

public class JsonResponse {

    @SerializedName("$id")
    public String id;
    @SerializedName("Message")
    public String message;
    @SerializedName("Path")
    public String path;

    public JsonResponse(String id, String message, String path) {
        this.id = id;
        this.message = message;
        this.path = path;
    }
}

来自应用程序的 API 调用

Retrofit retrofit;

private void postImage() {
    String URL = "http://www.dgheating.com/";

    //your file location
    File file = new File(Environment.getExternalStorageDirectory() + "/Image.png");

    //parameters
    String NAME = file.getName();
    String FOLDER = "LeadDocuments";
    String FILE = "file";

    retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .build();
    API api = retrofit.create(API.class);
    RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData(FILE, NAME, requestBody);
    Call<JsonResponse> call = api.uploadFile(NAME, FOLDER, part);

    call.enqueue(new Callback<JsonResponse>() {
        @Override
        public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
            //response.body()           null
            //response.code()           500

            //https://github.com/square/retrofit/issues/1321
            Converter<ResponseBody, JsonResponse> errorConverter
                    = retrofit.responseBodyConverter(JsonResponse.class, new Annotation[0]);
            try {
                JsonResponse jsonResponse = errorConverter.convert(response.errorBody());
                Log.e("error", "id:" + jsonResponse.id);                //1
                Log.e("error", "message:" + jsonResponse.message);      //An error has occurred
                Log.e("error", "path:" + jsonResponse.path);             //null
            } catch (IOException ignored) {
            }
        }

        @Override
        public void onFailure(Call<JsonResponse> call, Throwable t) {

        }
    });
}

由于任何这些服务器问题导致字段错误

  1. Google
  2. HTTP 500 response using retrofit, but Post request through Postman gives desired response
  3. How to get response body to retrofit exception?
  4. Retrofit SocketTimeoutException (and/or http 500 error) on http-POST

【讨论】:

  • 我会尽快检查并提供反馈。
【解决方案4】:

使用 Retrofit 2,您需要使用 OkHttp 的 RequestBody 或 MultipartBody.Part 类,并将您的文件封装到请求正文中。让我们看一下文件上传的接口定义。 你见过https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server

【讨论】:

  • 是的,问题中的所有上述代码实际上都是从本教程开始的。除了额外的两个字符串参数外,它几乎相同。 Plus Ion 库在相同的数据和设置下运行良好。
【解决方案5】:

您的服务定义似乎有误。试试

@Multipart
@POST("jobDocuments/upload")
Call<ResponseBody> upload(@Part("file") MultipartBody.Part file,@Part("folder") RequestBody folder,@Part("name") RequestBody name);

RequestBody folder =
        RequestBody.create(
                MediaType.parse("multipart/form-data"), "LeadDocuments");
RequestBody name =
        RequestBody.create(
                MediaType.parse("multipart/form-data"), filename);   

在您的 @Background 方法中。这一切都假设您使用的是 Retrofit2。

【讨论】:

  • 上述修正也没有成功。是的,它的改造2。 :(
  • 不起作用,这个异常抛出:java.lang.IllegalArgumentException: @Part parameters using the MultipartBody.Part must not include a part name in the annotation.
  • Hesam,你应该使用@Part 而不是@Part("file")。
猜你喜欢
  • 1970-01-01
  • 2017-07-24
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-12
相关资源
最近更新 更多