0

I am new to making Requestbody to send form data which consists of uploading an image from my phone media and some data which are in string format. I have used it in Postman and it's working but in my app I am facing problem where it gives 400 bad request when posting data to API. Here is my codes. Any help would do

This is my backend api code.

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

Here is my Fish Create 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;}
}

For the front-end, I am using android Kotlin which also involves with Retrofit2.

Here is the api call

@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

Here is the repository.

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

Here is the viewmodel that I am using

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

Here is where I am trying to upload my form data

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

Here is the 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$2$2.onResponse(KotlinExtensions.kt:53)
    at retrofit2.OkHttpCall$1.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)

*Update (My Retrofit)

@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

Any solutions will do. Thanks

  • Please try to verify is any annotation used for Api like POST or PUT or you can check or wrong request params or api path problem may be space....just check hope it may help you – gpuser Jul 19 '21 at 18:35
  • Thanks!. I have just checked in my Retrofit. It's correct and adhering to POST with no spaces whatsover. Furthermore no space in between the api path – AdrianJoseph Jul 19 '21 at 18:37
  • Also please verify the Request Params which you are sending like File or map or key etc.. – gpuser Jul 19 '21 at 18:40
  • I've updated the content of the question. The Retrofit API class used in my app contains the Request Params which maps to my FishCreateDTO. – AdrianJoseph Jul 19 '21 at 18:42
  • can you please check your BASEURL is containing any space or / in line or just put the last 2-3 character of baseurl here – gpuser Jul 19 '21 at 18:47
  • Please check this way to send multipart with Retrofit here hope it may help you https://stackoverflow.com/a/40995780/4042384 – gpuser Jul 19 '21 at 18:51
  • "Bad Request" will be generated if an anti-forgery token is needed but not provided so check your startup file to see if AddAntiforgery is there. If so you need to add the anti-forgery value in the form/posted data. – pcalkins Jul 19 '21 at 22:18
  • @pcalkins I just checked my Startup class. There's no AddAntiforgery code in it – AdrianJoseph Jul 20 '21 at 04:09
  • @Ghanshyam. I checked my BASE_URL. It is correct and it takes the IP address of my server along with it's port number where I host my ASP. NET Core API on IIS Server. Works well with Postman. – AdrianJoseph Jul 20 '21 at 04:12
  • @AdrianJoseph please print the complete URL with baseurl+APIname and copy that and hit on any browser what output you are getting there..also check with any header required in API or check that in backend side what u missing or wrong in backend hope it may help you – gpuser Jul 20 '21 at 06:32
  • Hi @Ghanshyam, thanks for your help. I managed to solve it and it is uploading fine. The issue wasn't in the API endpoint or the base url. I've answered my question below, you can check it out. Thanks once again. – AdrianJoseph Jul 20 '21 at 10:02

1 Answers1

0

thanks for helping me but I was able solve it. Here is the answer that I did

First if you looked in the code where my FishCreateDTO, it has some data attributes. I made sure I followed the sequence of those data attributes requested by my API.

Second I changed the ApiService Interface class in Kotlin

@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

If you noticed I changed from String data type to RequestBody data type. Some say you could still explicitly mentioned the data type like String or Int but to be safer, use RequestBody. Since I've changed my data type in my interface class, this must be reflected into the Repository and ViewModal class. To see the original of these classes, check out my question's content.

Finally, I changed my code in my 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)
        )

I ensure that all the data types which are "text/plain" for text/JSON data and for image it is "image/*". Previously, it was in "multipart/form-data" for the image therefore the image couldn't be uploaded thus throwing a 400 Bad Request error.

**Note: Sometimes the api endpoints and the base URL of your API may contain spaces or additional unwanted characters (mentioned by @Ghanshyam) but if it's correct and matching to your API then have a look at your code. The error could be there in your activity or your viewmodel or your repository. It could be also in the Api interface class as well. This answer follows Android MVVM code with Kotlin and ASP.NET Core Web API MVC