假设我有这样一个ViewModel,它来自于ASP.NET MVC中的表单提交:
public class EditProfileViewModel
{
public string DisplayName { get; set; }
public IFormFile Photo { get; set; }
}
我可以很容易地在控制器内接收到这个信息:
public async Task<IActionResult> OnPost([FromForm] EditProfileViewModel edits)
{
// edits contains form data, great
}
但是现在我想把它转发到后端API,也使用ASP.NET,包括文件上传。最简单的方法似乎是使用HttpClient创建一个新的表单POST,使用MultipartFormDataContent。但据我所知,虽然从表单内容到模型类的转换是在接收请求时透明地进行的,但是创建MultipartFormDataContent需要对键值对进行硬编码。
我知道这样做是有可能的:
var form = new MultipartFormDataContent()
form.Add(new StringContent(edits.DisplayName), "displayName")
var content = new StreamContent(edits.Photo.OpenReadStream());
content.Headers.ContentType = MediaTypeHeaderValue.Parse(file.ContentType)
form.Add(content, "photo") // Is this the right capitalization ?? See how error prone this is?
var result = await client.PostAsync("some-api", form);
但这是冗长的,容易出现重复声明的错误。此外,要在接收端使用相同的模型类反序列化,需要了解转换魔术。
是否有更好的方法将模型类转换回MultipartFormDataContent?或者,如果这是XY问题,那么将此表单模型完全转发到后端ASP.NET API的更好方法是什么?
发布于 2021-12-10 07:09:59
需要了解转换魔术。
这就是要点-- MultipartFormDataContent
可以用来向任何web应用程序发布表单,而且代码不知道底层解析的魔力,因为需要告诉接收端如何期望它的格式。
一个web框架可以区分大小写,而另一个则需要小写或完全匹配的大小写.或者对于数组,可以支持字段名的重复(name="foo"
,name="foo"
,.)另一个可能需要[]
后缀(name="foo[]"
,name="foo[]"
,.)还有一个可能需要指数(name="foo[0]"
,name="foo[1]"
,.)。
更不用说多个层次的属性:name="foo.bar"
,name="foo_bar"
,.
因此,您必须编写自己的属性到字段名称映射代码,知道您发布到哪个框架。
发布于 2021-12-10 07:42:26
我用一些扩展方法和反射解决了“键值对的硬编码”问题:
public static class MultiContentExtensionMethods
{
public static void AddDto<TDto>(this MultipartFormDataContent multiContent, TDto dto)
{
List<PropertyInfo> propertyInfos = dto.GetType().GetProperties().ToList();
foreach(var propertyInfo in propertyInfos)
{
if (propertyInfo.PropertyType != typeof(IFormFile) &&
!propertyInfo.PropertyType.Name.Contains("List"))
{
if(propertyInfo.GetValue(dto) is not null)
multiContent.Add(new StringContent(propertyInfo.GetValue(dto).ToString()), propertyInfo.Name);
}
else if(propertyInfo.PropertyType != typeof(IFormFile) &&
propertyInfo.PropertyType.Name.Contains("List"))
{
var list = (IList)propertyInfo.GetValue(dto, null);
if(list is not null)
foreach (var item in list)
{
multiContent.Add(new StringContent(item.ToString()), propertyInfo.Name);
}
}
}
}
public static void AddFile(this MultipartFormDataContent multiContent, IFormFile file, string name)
{
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
{
data = br.ReadBytes((int)file.OpenReadStream().Length);
}
ByteArrayContent bytes = new(data);
multiContent.Add(bytes, name, file.FileName);
}
}
用法:
在本例中,ViewModel和Dto之间没有区别,因此可以使用ViewModel
public class EditProfileDto
{
public string DisplayName { get; set; }
public IFormFile Photo { get; set; }
}
EditProfileDto dto = new()
{
DisplayName = edits.DisplayName
};
MultipartFormDataContent form = new();
form.AddDto(dto);
form.AddFile(edits.Photo, nameof(dto.Photo));
var result = await client.PostAsync("some-api", form);
https://stackoverflow.com/questions/70306462
复制相似问题