Retrofit 2 can't upload a file with two additi

2019-01-18 11:31发布

问题:

Read edit at bottom of the question for possible alternative solution until the solution is found.

This is a successful post file with two parameters using POSTMan. I am trying to do the same with retrofit but receive BadRequest.

PostMan Settings:

Chrome Network Post Details:

Now here is how I am doing this in Android but failing:

Retrofit Service Interface:

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

This is my @Background method to run the network request with above service generated

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());
    }

Here is my Web Api Method:

 [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");
    }

Please help where I am wrong and how to achieve this, is there any easy alternative to retrofit?

[Edit] This code is working successfully, Thanks to 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(...);

回答1:

I faced similar issue here: Android with Retrofit2 OkHttp3 - Multipart POST Error

I got my problem solved after taking @TommySM suggestion. If is still unclear to you, I think this is the solution:

@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);

The important part is to use MediaType.parse("text/plain") for MediaType of String parameter (I believe your case is: folder & name parameter), using okhttp3.MultipartBody.FORM is a mistake.

See these screenshots for the comparison:

1) Problematic POST

2) Correct POST



回答2:

So, hope it's no too late, and if so - it might help somebody else :) my 2 cents about this is after having the same problem a while back:

The service definition, using @Part only (modify field names according to what your server expects)

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

And for the magic trick, I refer to both parts just as RequestBody, using the MediaType.parse() to each one with it's own type, relying on the @Multipart definition for the request itself, no need for form-data stuff, the same works for multiple files, and multiple fields:

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)
        {

        }
    });
}

(I use a listener to return the callback response to a different part of the app (e.g. a calling activity)).

This definitely sends the file/s and the text field, other problems would probably stem from the server side.

Hope this Helps!



回答3:

Using Retrofit 2, you need to use either OkHttp’s RequestBody or MultipartBody.Part classes and encapsulate your file into a request body. Let’s have a look at the interface definition for file uploads. Have you seen https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server



回答4:

Interface for Retrofit (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);
}

Response Class (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 call from application

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) {

        }
    });
}

Error in fields, because of any of these server issue

  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


回答5:

It looks like your service definition is wrong. Try

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

and

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

in your @Background method. This all assumes you are using Retrofit2.