Search code examples
javascriptgoogle-chromeasp.net-core-webapifetch-api

Unable to upload an image to .net core webapi from a chrome extension


I try to take a screen capture of an active tab and upload it to a .net core web api from a chrome extension. I keep getting 'HTTP error: 400' after calling the web api. I am not sure what I have done wrong and would be gratefully if someone could help.

Update 1: I have finally found out why I got error 400, this is because fetch-api does not automatically add boundary to Content-Type. By removing Content-Type from headers, this fixes the problem and now the fetch-api can successfully connect to the controller. And now I have another problem, RequestModel.File of the model is null...

Here is the code in service-worker.js

chrome.action.onClicked.addListener(async function () {
  await TestPost();
});

async function TestPost() {
  const url = "https://localhost:7221/test/upload"; // This link has been tested with a c# app

  const tabCapture = await chrome.tabs.captureVisibleTab(); // This also has been tested and works

  const formData = new FormData();
  formData.append("filename", "my-file-name");
  formData.append("file", tabCapture);

  debugger;

  fetch(url, {
    method: "POST",
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'multipart/form-data'
    },
    body: formData
  })
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    console.log(data[0].name);
  })  
  .catch(error => console.log("ERROR - TestPost: " + error)); 
};

Here is the .net core controller

        [HttpPost("upload")]
        public async Task<IActionResult> Upload([FromForm] RequestModel model)
        {
            try
            {
                var filename = model.FileName;
                var uploadFileFullPath = Path.Combine("my-uploadpath", $"{DateTime.Now.ToString("yyyyMMdd HHmmss")}.jpg");

                using (var fileStream = new FileStream(uploadFileFullPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
                {
                    await model.File.CopyToAsync(fileStream);
                }

                return Ok("Thanks, I got your file ...");
            }
            catch (Exception ex)
            {
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

Here is RequestModel.cs

    public class RequestModel
    {
        [JsonPropertyName("filename")]
        public string FileName { get; set; } = string.Empty;

        [JsonPropertyName("file")]
        public IFormFile? File { get; set; } = null;
    }

Solution

  • The first error you were getting because the browser automatically sets the Content-Type to multipart/form-data so no need to manully set the header. and for the error RequestModel.File is null, it indicate that the file is not being properly received or interpreted on the server side. first make sure the names in the JsonPropertyName attributes match the keys you use in the FormData object in your JavaScript. The chrome.tabs.captureVisibleTab returns a data URL, not a Blob. Before appending it to formData, you should convert it to a Blob.

    you could try below code:

    async function TestPost() {
      const url = "https://localhost:7221/test/upload";
    
      const tabCapture = await chrome.tabs.captureVisibleTab();
      const blob = await (await fetch(tabCapture)).blob(); 
    
      const formData = new FormData();
      formData.append("filename", "my-file-name");
      formData.append("file", blob, "screenshot.jpg"); 
    
      fetch(url, {
        method: "POST",
        headers: {
          'Accept': 'application/json'
        },
        body: formData
      })
      .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP error: ${response.status}`);
        }
        return response.json();
      })
      .then(data => {
        console.log(data);
      })
      .catch(error => console.log("ERROR - TestPost: " + error));
    };