Retrofit2 使用 RequestBody returns 400 错误请求上传(Android Kotlin MVVM & ASP.NET Core Web API)

Retrofit2 Upload Using RequestBody returns 400 Bad Request (Android Kotlin MVVM & ASP.NET Core Web API)

我不熟悉让 Requestbody 发送表单数据,其中包括从我的 phone 媒体上传图像和一些字符串格式的数据。我在 Postman 中使用过它并且它正在工作,但在我的应用程序中我遇到了问题,即在将数据发布到 API 时它给出了 400 个错误请求。这是我的代码。任何帮助都可以

这是我的后端 api 代码。

[HttpPost]
    public ActionResult<FishReadDTO> CreateFish([FromForm] FishCreateDTO fishCreateDTO)
    {
        var fishmodel = _mapper.Map<Fish>(fishCreateDTO);

        if(fishCreateDTO.ImageFile.Length > 0 || fishCreateDTO.ImageFile != null)
        {
            string apiServerAddress = _httpContextAccessor.HttpContext.Request.Scheme + "://"
            + _httpContextAccessor.HttpContext.Connection.LocalIpAddress.MapToIPv4().ToString() 
            + ":" + _httpContextAccessor.HttpContext.Connection.LocalPort;

            try
            {
                if(!Directory.Exists(_webHostEnvironment.WebRootPath + "\images\"))
                {
                    Directory.CreateDirectory(_webHostEnvironment.WebRootPath + "\images\");
                }

                using (FileStream filestream = System.IO.File.Create(_webHostEnvironment.WebRootPath + "\images\" + fishCreateDTO.ImageFile.FileName))
                {
                    fishCreateDTO.ImageFile.CopyTo(filestream);
                    filestream.Flush();
                    fishmodel.Image = apiServerAddress + "/images/" + fishCreateDTO.ImageFile.FileName;
                }
            }
            catch (Exception ex)
            {
              Console.WriteLine($"Error Occured {ex.Message}");
            }
        }

        
        _repository.CreateFish(fishmodel);
        var fishRead = _mapper.Map<FishReadDTO>(fishmodel);

        return CreatedAtRoute(nameof(GetFishById), new{Id = fishRead.ID}, fishRead);
    }

这是我的 Fish 创建 DTO。

public class FishCreateDTO
{
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
    [MaxLength(100)]
    public string Description { get; set; }
    [Required]
    public float WeightKg { get; set; }
    [Required]
    public int Stock { get; set; }
    [Required]
    public float Price { get; set; }
    [Required]
    public int CategoryID { get; set; }
    [Required]
    public int UserID { get; set; }

    public IFormFile ImageFile {get; set;}
}

前端,我用的是android Kotlin,也涉及到Retrofit2。

这里是 api 调用

@Multipart
@POST("/efishing-api/fish")
suspend fun createFish(
    @Part("name") name: String,
    @Part("description") description : String,
    @Part("weightKG") weightKG: String,
    @Part("stock") stock : String,
    @Part("price") price : String,
    @Part("categoryID") categoryID: String,
    @Part("userID") userID : String,
    @Part imageFile : MultipartBody.Part
) : FishCreateResponse

这是存储库。

suspend fun createFish(
    name : String,
    description : String,
    weightKG : String,
    stock : String,
    price : String,
    userId: String,
    categoryId: String,
    imageFile : MultipartBody.Part
) : FishCreateResponse {
    return RetrofitBuilder.apiService.createFish(
        name,
        description,
        weightKG,
        stock,
        price,
        categoryId,
        userId,
        imageFile
    )
}

这是我正在使用的视图模型

fun createFish(
    name : String,
    description : String,
    weightKG : String,
    stock : String,
    price : String,
    userId: String,
    categoryId: String,
    imageFile : MultipartBody.Part
) {
    CoroutineScope(IO).launch {
        val fishCreate = fishRepository.createFish(
            name,
            description,
            weightKG,
            stock,
            price,
            categoryId,
            userId,
            imageFile
        )
    }
}

这是我尝试上传表单数据的地方

val file = File(filePath)
        val requestBody = file.asRequestBody("multipart/form-data".toMediaTypeOrNull())

        fishViewModel.createFish(
            Fishname.text.toString(),
            Description.text.toString(),
            FishPrice.text.toString(),
            Weight.text.toString(),
            Stock.text.toString(),
            categoryID.toString(),
            userId.toString(),
            MultipartBody.Part.createFormData("imageFile", file.name, requestBody)
        )

这里是logcat

2021-07-20 01:49:31.117 24404-25371/com.example.efishingapp E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-3
Process: com.example.efishingapp, PID: 24404
retrofit2.HttpException: HTTP 400 Bad Request
    at retrofit2.KotlinExtensions$await.onResponse(KotlinExtensions.kt:53)
    at retrofit2.OkHttpCall.onResponse(OkHttpCall.java:161)
    at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:923)

*更新(我的改造)

@Multipart
@POST("/efishing-api/fish")
suspend fun createFish(
    @Part("name") name: String,
    @Part("description") description : String,
    @Part("weightKG") weightKG: String,
    @Part("stock") stock : String,
    @Part("price") price : String,
    @Part("categoryID") categoryID: String,
    @Part("userID") userID : String,
    @Part imageFile : MultipartBody.Part
) : FishCreateResponse

任何解决方案都可以。谢谢

感谢您帮助我,但我能够解决它。这是我做的答案

首先,如果您查看了我的 FishCreateDTO 所在的代码,它有一些数据属性。我确保我遵循了 API.

请求的那些数据属性的顺序

其次,我在 Kotlin

中更改了 Api 服务接口 class
@Multipart
@POST("/efishing-api/fish/addfish")
suspend fun createFish(
    @Part("name") name: RequestBody,
    @Part("description") description : RequestBody,
    @Part("weightKG") weightKG: RequestBody,
    @Part("stock") stock : RequestBody,
    @Part("price") price : RequestBody,
    @Part("categoryID") categoryID: RequestBody,
    @Part("userID") userID : RequestBody,
    @Part imageFile : MultipartBody.Part
) : FishCreateResponse

如果您注意到我从 String 数据类型更改为 RequestBody 数据类型。有人说您仍然可以明确提及数据类型,如 String 或 Int,但为了更安全,请使用 RequestBody。由于我在界面 class 中更改了数据类型,这必须反映到存储库和 ViewModal class 中。要查看这些 class 的原文,请查看我的问题内容。

最后,我更改了 activity 中的代码。

val file = File(filePath)
        val requestBody = RequestBody.create("image/*".toMediaTypeOrNull(), file)
        Log.e("Post Activity", "Image choosen path $filePath")

        fishViewModel.createFish(
            Fishname.text.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            Description.text.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            Weight.text.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            Stock.text.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            FishPrice.text.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            categoryID.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            userId.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
            MultipartBody.Part.createFormData("imageFile", file.name, requestBody)
        )

我确保 text/JSON 数据的所有数据类型为“text/plain”,图像为“image/*”。以前,它位于图像的“multipart/form-data”中,因此无法上传图像,从而引发 400 Bad Request 错误。

**注意:有时 api 端点和 API 的基 URL 可能包含空格或其他不需要的字符(@Ghanshyam 提到)但如果它是正确的并且与您的 API 匹配,然后查看您的代码。错误可能存在于您的 activity 或您的视图模型或您的存储库中。它也可以在 Api 接口 class 中。这个答案遵循 Android MVVM code with Kotlin and ASP.NET Core Web API MVC