【问题标题】:Is there a simple way to forward forms from ASP.NET MVC to ASP.NET API?有没有一种简单的方法可以将表单从 ASP.NET MVC 转发到 ASP.NET API?
【发布时间】:2022-01-15 06:16:04
【问题描述】:

假设我有一个这样的 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。包括文件上传。最简单的方法似乎是使用 MultipartFormDataContent 创建一个带有 HttpClient 的新表单 POST。但据我所知,虽然在接收请求时从表单内容到模型类的转换是透明的,但创建 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?

【问题讨论】:

    标签: c# asp.net asp.net-mvc asp.net-web-api


    【解决方案1】:

    需要转换魔法的知识

    这就是重点 - MultipartFormDataContent 可用于将表单发布到任何 Web 应用程序,并且代码不知道底层的解析魔法,因为它需要被告知接收端如何期望它的格式。

    一个 Web 框架可以区分大小写,而另一个需要小写或完全匹配的大小写。或者对于数组,一个可以支持字段名称的重复(name="foo"name="foo",...),另一个可能需要[] 后缀(name="foo[]"name="foo[]",...),另一个可能需要索引 (name="foo[0]", name="foo[1]", ...)。

    更不用说多级属性了:name="foo.bar"name="foo_bar"、...

    因此,您必须编写自己的属性到字段名称映射代码,知道您发布到哪个框架。

    【讨论】:

    • 这是有道理的。但是让我们假设我的 API 后端也是 ASP.NET。我已经编辑了问题以澄清这一点。由于 ASP.NET 是一个框架,因此“应该”有一些干净的方式将表单从 ASP.NET MVC 转发到 ASP.NET API。
    • System.Net.Http.MultipartFormDataContent 与平台无关。也许在 MVC 命名空间的某个地方有一个转换器。
    【解决方案2】:

    我用一些扩展方法和反射解决了“硬编码键值对”:

        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);
    

    【讨论】:

    • 这会在很多方面破坏。对于初学者来说,引用类型而不是值类型,但也包括期望 foo[] 用于列表元素的 Web 框架。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-24
    • 1970-01-01
    • 2010-12-12
    • 2013-11-30
    • 1970-01-01
    相关资源
    最近更新 更多