I need to XUnit test this next endpoint:
/// <summary>
/// Cease all account in csv file
/// </summary>
/// <param name="dispatcher"></param>
/// <param name="csvFile">CSV file with format (Id,Name,SID,CeaseDate,Note)</param>
/// <param name="ct"></param>
/// <returns></returns>
[HttpPost("cease/bulk")]
[Authorize(Roles = VdcSecurity.Role.ManagementAdmin)]
[AllowAnonymous]
public async Task<ActionResult<bool>> CeaseBulkAccountAsync(
[FromServices][IsSensitive] ICommandDispatcher dispatcher,
[FromForm] IFormFile csvFile,
[IsSensitive] CancellationToken ct = default
)
{
var identity = Vdc.Libs.AspNet.Controller.HttpContextExtensions.GetIdentity(HttpContext);
var ipAddress = HttpContext.GetIpAddress();
var command = new CeaseBulkCommand(identity, HttpContext.TraceIdentifier)
{
Stream = csvFile.OpenReadStream(),
IpAddress = ipAddress
};
var result = await dispatcher.DispatchAsync(_provider, command, ct);
return result.ToActionResult(this);
}
My problem is no matter how I create the IFormFile object, it is always received as null.
This is one of my attempts:
const string filePath = "CeaseBulkAccount.csv";
using (var httpClient = ApiClient.HttpClient)
{
var form = new MultipartFormDataContent();
byte[] fileData = File.ReadAllBytes(filePath);
ByteArrayContent byteContent = new ByteArrayContent(fileData);
byteContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
form.Add(byteContent, "file", Path.GetFileName(filePath));
var result = await httpClient.PostAsync("/api/accounts/cease/bulk", form);
}
I reach the controller, but csvFile is received as null.
ApiClient.HttpClient is our own client, but I wouldn't mind using a generic one.
I have to say our httpClient "PostAsync" receives a HttpContent.
A second attempt:
var httpClient = ApiClient.HttpClient;
var fileContent = new ByteArrayContent(ReadFully(file));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "CeaseBulkAccount.csv"
};
var response = await httpClient.PostAsync("/api/accounts/cease/bulk", fileContent, ct);
public static byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
Again, csvFile null.
Our PostAsync:
//
// Summary:
// Send a POST request with a cancellation token as an asynchronous operation.
//
// Parameters:
// requestUri:
// The Uri the request is sent to.
//
// content:
// The HTTP request content sent to the server.
//
// cancellationToken:
// A cancellation token that can be used by other objects or threads to receive
// notice of cancellation.
//
// Returns:
// The task object representing the asynchronous operation.
//
// Exceptions:
// T:System.InvalidOperationException:
// The requestUri must be an absolute URI or System.Net.Http.HttpClient.BaseAddress
// must be set.
//
// T:System.Net.Http.HttpRequestException:
// The request failed due to an underlying issue such as network connectivity, DNS
// failure, server certificate validation or timeout.
//
// T:System.Threading.Tasks.TaskCanceledException:
// .NET Core and .NET 5 and later only: The request failed due to timeout.
//
// T:System.UriFormatException:
// The provided request URI is not valid relative or absolute URI.
public Task<HttpResponseMessage> PostAsync([StringSyntax("Uri")] string? requestUri, HttpContent? content, CancellationToken cancellationToken);
The name passed to the form.Add
must match with the one in the action method of the controller; [FromForm] IFormFile csvFile
.
Since that one is csvFile
, you must add the file as below.
form.Add(byteContent, "csvFile", Path.GetFileName(filePath));
With above change, your first attempt works fine.
Alternatively, you set a fixed name via FromForm
.
That also guards yourself against any renames while refactoring any related code.
It's the name specified in the attribute that needs to be used when posting a file.
[FromForm(Name = "file")] IFormFile csvFile