I am trying to write an API endpoint that would allow a user to send a file to my server using Asp.NET Core 3.1.
I have the following action method that needs to respond to the client's request and process the file validation and store the file.
[HttpPost, Route("api/store")]
public async Task<IActionResult> Store([FromBody] MultipartFormDataContent content)
{
// Validate the file, store it.
return Ok();
}
I am trying to send the file from the client-side like this
using HttpClient client = new HttpClient();
var multiForm = new MultipartFormDataContent();
multiForm.Add(new StringContent("1/12/1.jpg"), "custom_file_name");
FileStream fs = System.IO.File.OpenRead("1.jpg");
multiForm.Add(new StreamContent(fs), "file", "1.jpg");
// send request to API
var url = "https://localhost:123/api/store";
var response = await client.PostAsync(url, multiForm);
But the server returns HTTP Code 404 Not Found
.
How can I correctly tweak the Store
method above so it accepts the file being sent by the client?
You shouldn't include api/
in the action method route. The controller's route is already prefixed with api
. Also, api/[controller]
means the controller route will be api/ControllerName
, then your action will be api/ControllerName/store
.
If your controller class is named ThingsController
then its route is api/things
, and your Store
action should be api/things/store
.
You don't need to use both HttpPost
and Route
attributes on an action. The Route
attribute accepts all HTTP verbs, so use one or the other (typically just HttpPost
or whatever HTTP verb you will be handling with the action method. So:
[ApiController]
[Route("api/[controller]")]
public class ThingsController : ControllerBase
{
// POST api/things/store
[HttpPost("store")]
public async Task<IActionResult> Store([FromBody] MultipartFormDataContent content)
{
// Do stuff
}
}
Also, the controller should be using IFormFile
to deal with file uploads in your controller. The documentation is here:
Your controller would accept a IFormFile files
parameter rather than MultipartFormDataContent content
.
Example from MSDN, adapted to your action
[HttpPost("store")]
public async Task<IActionResult> Store(List<IFormFile> files)
{
// Get total size
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
// Gets a temporary file name
var filePath = Path.GetTempFileName();
// Creates the file at filePath and copies the contents
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, filePath });
}
If you're dealing with a single file, you can just pass IFormFile file
instead of a list, and get rid of the looping in the action method.
I have not tested this code myself, wrote it on a mobile device. Hopefully it solves the problem though.