ASP.NET Core 1.1中用于多部分/表单数据(文件+ JSON)发布的模型绑定

cki*_*tel 6 model-binding asp.net-core

我试图构建一个ASP.NET Core 1.1 Controller方法来处理如下所示的HTTP请求:

POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474

----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain

<< Contents of my file >>

----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json

{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
Run Code Online (Sandbox Code Playgroud)

这是一个multipart/form-data请求,一部分是(小)文件,另一部分是基于提供的规范的json blob。

理想情况下,我希望控制器方法如下所示:

POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474

----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain

<< Contents of my file >>

----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json

{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
Run Code Online (Sandbox Code Playgroud)

但是,a,这不仅仅适用于{TM}。当我有这样的东西时,IFormFile 确实会填充,但是json字符串不会反序列化为其他属性。

我还尝试向其中添加除以外的所有属性的Text属性UploadPayload,该属性IFormFile也不会接收数据。例如

[HttpPost]
public async Task Post(UploadPayload payload)
{
   // TODO
}

public class UploadPayload
{
    public IFormFile File { get; set; }

    [Required]
    [StringLength(32)]
    public string Md5 { get; set; }

    public List<string> SessionIds { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我有一个解决方法是避免模型绑定并MultipartReader按照以下方式使用:

public class UploadPayload
{
    public IFormFile File { get; set; }

    public UploadPayloadMetadata Text { get; set; }
}

public class UploadPayloadMetadata
{
    [Required]
    [StringLength(32)]
    public string Md5 { get; set; }

    public List<string> SessionIds { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

进行上述操作会绕过模型验证功能,等等。另外,我想也许可以接受它jsonString,然后以某种方式使其进入一种我可以打电话await TryUpdateModelAsync(payloadModel, ...)但又不知道如何到达那里的状态-似乎没有都干净。

是否有可能像我的第一次尝试那样达到我期望的“透明”模型绑定状态?如果是这样,人们将如何解决?

mar*_*iro 6

这里的第一个问题是,数据需要以稍微不同的格式从客户端发送。您UploadPayload班级中的每个属性都需要以自己的形式发送:

const formData = new FormData();
formData.append(`file`, file);
formData.append('md5', JSON.stringify(md5));
formData.append('sessionIds', JSON.stringify(sessionIds));
Run Code Online (Sandbox Code Playgroud)

完成此操作后,您可以将[FromForm]属性添加到MD5属性以绑定它,因为它是一个简单的字符串值。SessionIds尽管此属性是一个复杂的对象,但对于该属性将不起作用。

可以使用自定义模型绑定器完成从表单数据绑定复杂的JSON:

public class FormDataJsonBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if(bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
        if(valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
        else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);

        // Do nothing if the value is null or empty
        string value = valueProviderResult.FirstValue;
        if(string.IsNullOrEmpty(value)) return Task.CompletedTask;

        try
        {
            // Deserialize the provided value and set the binding result
            object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        catch(JsonException)
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,可以ModelBinder在DTO类中使用该属性来指示应使用此绑定器来绑定该MyJson属性:

public class UploadPayload
{
    public IFormFile File { get; set; }

    [Required]
    [StringLength(32)]
    [FromForm]
    public string Md5 { get; set; }

    [ModelBinder(BinderType = typeof(FormDataJsonBinder))]
    public List<string> SessionIds { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

您可以在ASP.NET Core文档中阅读有关自定义模型绑定的更多信息:https : //docs.microsoft.com/zh-cn/aspnet/core/mvc/advanced/custom-model-binding


Vas*_*ssi 0

我不是 100% 清楚这对于 ASP.NET Core 是如何工作的,但对于 Web API(所以我假设这里存在类似的路径)你会想要走媒体格式化程序的路。这是一个示例(与您的问题非常相似)Github Sample with blog post

自定义格式化程序可能是问题所在?https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters