具有 FromForm 绑定到 IFormFile 的 C# 集成测试控制器 属性
C# Integration Testing Controller with FromForm binding to IFormFile property
我正在使用:
- Visual Studio 2017 专业版
- dotnet 核心 SDK 2.2.102
- XUnit 2.4.1
我想做什么
集成测试接受表单数据的 API 控制器方法。
设置
- API 我的控制器中的路由使用
[FromForm]
属性接受 CommandObject
- CommandObject 的其中一个属性属于
List<IFormFile>
类型,旨在管理属于请求的任何文件
- 当我从 Postman 手动测试控制器方法时,它按预期工作。
问题
文件未绑定到 List<IFormFile>
属性。其他一切都按预期工作,但文件没有。这是我第一次使用多部分表单数据,所以不确定要尝试什么。
当我调试测试时,您可以看到除 Documents
属性 之外的所有内容都正常(请注意,这与下面的代码并不 100% 匹配,因为我不得不混淆一些东西)
我看过的东西
有很多与多部分表单数据相关的东西,我尝试过的一些解决方案是:
- This Whosebug post
- Another Whosebug post
- A custom class
MyIntegrationTest.cs
我的集成测试设置背后有很多代码。如果我 post 都在这里,我认为它不会很有帮助。最重要的信息是变量 server
的类型是 Microsoft.AspNetCore.TestHost.TestServer
[Fact]
async Task Post_ItemAsync_HappyPath_ReturnsOKStatusCode()
{
var fileDir = @"C:/path/to/files";
var fileNames = new string[] { "test.docx", "test.txt" };
using (var server = CreateTestServer())
{
// Arrange
var formData = new MultipartFormDataContent()
{
{ new StringContent("Test Title"), "Title" },
{ new StringContent("Test Description"), "Description" },
{ new StringContent("String_1"), "AListOfStrings" },
{ new StringContent("String_2"), "AListOfStrings" },
{ new StringContent("3"), "NumberOfThings" }
};
foreach (var fileName in fileNames)
{
var document = File.ReadAllBytes($"{fileDir}/{fileName}");
formData.Add(new ByteArrayContent(document), "file", fileName);
}
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
string contentType = "multipart/form-data; boundary=" + formDataBoundary;
var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/item")
{
Headers =
{
{ HttpRequestHeader.ContentType.ToString(), contentType }
},
Content = formData
};
// Act
var response = await server.CreateClient().SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Cleanup
...
}
}
MyController.cs
[HttpPost]
ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateItemAsync([FromForm]CreateItemCommand command)
{
bool commandResult = false;
commandResult = await _mediator.Send(command);
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
CreateItemCommand.cs
[DataContract]
public class CreateItemCommand
:IRequest<bool>
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public HashSet<string> AListOfThings { get; set; }
[DataMember]
public int NumberOfThings { get; set; }
[DataMember]
public List<IFormFile> Documents { get; private set; }
public CreateITemCommand()
{
AListOfThings = new HashSet<string>();
}
public CreateItemCommand(string title, string description, HashSet<string> aListOfThings, int NumberOfThings, List<IFormFile> documents)
: this()
{
Title = title;
Description = description;
AListOfStrings = aListOfStrings;
NumberOfThings = numberOfThings;
Documents = documents;
}
}
初始化时应将表单数据边界添加到 MultipartFormDataContent
,并且文件名需要与模型所需的 属性 相匹配。
//...
// Arrange
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
var formData = new MultipartFormDataContent(formDataBoundary) { //<---- NOTE HERE
{ new StringContent("Test Title"), "Title" },
{ new StringContent("Test Description"), "Description" },
{ new StringContent("String_1"), "AListOfStrings" },
{ new StringContent("String_2"), "AListOfStrings" },
{ new StringContent("3"), "NumberOfThings" }
};
foreach (var fileName in fileNames) {
var document = File.ReadAllBytes($"{fileDir}/{fileName}");
formData.Add(new ByteArrayContent(document), "Documents", fileName); //<-- NOTE HERE
}
// Act
var response = await server.CreateClient().PostAsync("api/v1/item", formData);
//...
模型的 Documents
属性 需要设置为 public 以便模型绑定器可以在解析表单数据时填充它。
我正在使用:
- Visual Studio 2017 专业版
- dotnet 核心 SDK 2.2.102
- XUnit 2.4.1
我想做什么
集成测试接受表单数据的 API 控制器方法。
设置
- API 我的控制器中的路由使用
[FromForm]
属性接受 CommandObject - CommandObject 的其中一个属性属于
List<IFormFile>
类型,旨在管理属于请求的任何文件 - 当我从 Postman 手动测试控制器方法时,它按预期工作。
问题
文件未绑定到 List<IFormFile>
属性。其他一切都按预期工作,但文件没有。这是我第一次使用多部分表单数据,所以不确定要尝试什么。
当我调试测试时,您可以看到除 Documents
属性 之外的所有内容都正常(请注意,这与下面的代码并不 100% 匹配,因为我不得不混淆一些东西)
我看过的东西
有很多与多部分表单数据相关的东西,我尝试过的一些解决方案是:
- This Whosebug post
- Another Whosebug post
- A custom class
MyIntegrationTest.cs
我的集成测试设置背后有很多代码。如果我 post 都在这里,我认为它不会很有帮助。最重要的信息是变量 server
的类型是 Microsoft.AspNetCore.TestHost.TestServer
[Fact]
async Task Post_ItemAsync_HappyPath_ReturnsOKStatusCode()
{
var fileDir = @"C:/path/to/files";
var fileNames = new string[] { "test.docx", "test.txt" };
using (var server = CreateTestServer())
{
// Arrange
var formData = new MultipartFormDataContent()
{
{ new StringContent("Test Title"), "Title" },
{ new StringContent("Test Description"), "Description" },
{ new StringContent("String_1"), "AListOfStrings" },
{ new StringContent("String_2"), "AListOfStrings" },
{ new StringContent("3"), "NumberOfThings" }
};
foreach (var fileName in fileNames)
{
var document = File.ReadAllBytes($"{fileDir}/{fileName}");
formData.Add(new ByteArrayContent(document), "file", fileName);
}
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
string contentType = "multipart/form-data; boundary=" + formDataBoundary;
var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/item")
{
Headers =
{
{ HttpRequestHeader.ContentType.ToString(), contentType }
},
Content = formData
};
// Act
var response = await server.CreateClient().SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Cleanup
...
}
}
MyController.cs
[HttpPost]
ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateItemAsync([FromForm]CreateItemCommand command)
{
bool commandResult = false;
commandResult = await _mediator.Send(command);
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
CreateItemCommand.cs
[DataContract]
public class CreateItemCommand
:IRequest<bool>
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public HashSet<string> AListOfThings { get; set; }
[DataMember]
public int NumberOfThings { get; set; }
[DataMember]
public List<IFormFile> Documents { get; private set; }
public CreateITemCommand()
{
AListOfThings = new HashSet<string>();
}
public CreateItemCommand(string title, string description, HashSet<string> aListOfThings, int NumberOfThings, List<IFormFile> documents)
: this()
{
Title = title;
Description = description;
AListOfStrings = aListOfStrings;
NumberOfThings = numberOfThings;
Documents = documents;
}
}
初始化时应将表单数据边界添加到 MultipartFormDataContent
,并且文件名需要与模型所需的 属性 相匹配。
//...
// Arrange
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
var formData = new MultipartFormDataContent(formDataBoundary) { //<---- NOTE HERE
{ new StringContent("Test Title"), "Title" },
{ new StringContent("Test Description"), "Description" },
{ new StringContent("String_1"), "AListOfStrings" },
{ new StringContent("String_2"), "AListOfStrings" },
{ new StringContent("3"), "NumberOfThings" }
};
foreach (var fileName in fileNames) {
var document = File.ReadAllBytes($"{fileDir}/{fileName}");
formData.Add(new ByteArrayContent(document), "Documents", fileName); //<-- NOTE HERE
}
// Act
var response = await server.CreateClient().PostAsync("api/v1/item", formData);
//...
模型的 Documents
属性 需要设置为 public 以便模型绑定器可以在解析表单数据时填充它。