ASP.NET Core 1.1中用于多部分/表单数据(文件+ JSON)发布的模型绑定 [英] Model Binding for multipart/form-data (File + JSON) post in ASP.NET Core 1.1

查看:84
本文介绍了ASP.NET Core 1.1中用于多部分/表单数据(文件+ JSON)发布的模型绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm attempting to build an ASP.NET Core 1.1 Controller method to handle an HTTP Request that looks like the following:

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

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

It's a multipart/form-data request with one part being a (small) file and the other part a json blob that is based on a provided specification.

理想情况下,我希望我的控制器方法看起来像:

Ideally, I'd love my controller method to look like:

[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; }
}

但是,las,这不仅仅适用于{TM}.当我这样时,会填充IFormFile ,但是json字符串不会反序列化为其他属性.

But alas, that doesn't Just Work {TM}. When I have it like this, the IFormFile does get populated, but the json string doesn't get deserialized to the other properties.

我还尝试向UploadPayload添加一个Text属性,该属性具有IFormFile以外的所有属性,并且也不会接收数据.例如

I've also tried adding a Text property to UploadPayload that has all the properties other than the IFormFile and that also doesn't receive the data. E.g.

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

我要解决的一个问题是避免模型绑定,并按以下方式使用MultipartReader:

A workaround that I have is to avoid model binding and use MultipartReader along the lines of:

[HttpPost]
public async Task Post()
{
   ...

   var reader = new MultipartReader(Request.GetMultipartBoundary(), HttpContext.Request.Body);

   var section = await reader.ReadNextSectionAsync();
   var filePart = section.AsFileSection();

   // Do stuff & things with the file

   section = await reader.ReadNextSectionAsync();
   var jsonPart = section.AsFormDataSection();
   var jsonString = await jsonPart.GetValueAsync();

   // Use $JsonLibrary to manually deserailize into the model
   // Do stuff & things with the metadata

   ...
}

执行上述操作会绕过模型验证功能等.此外,我想也许我可以接受该jsonString,然后以某种方式使其进入一种状态,然后我可以调用await TryUpdateModelAsync(payloadModel, ...),但无法弄清楚如何获取那里-而且似乎也不是很干净.

Doing the above bypasses model validation features, etc. Also, I thought maybe I could take that jsonString and then somehow get it into a state that I could then call await TryUpdateModelAsync(payloadModel, ...) but couldn't figure out how to get there either - and that didn't seem all that clean either.

是否有可能像我的第一次尝试一样达到透明"模型绑定的理想状态?如果是这样,一个人怎么会做到这一点?

Is it possible to get to my desired state of "transparent" model binding like my first attempt? If so, how would one get to that?

推荐答案

这里的第一个问题是数据需要以略有不同的格式从客户端发送. UploadPayload类中的每个属性都需要以其自身的形式发送:

The first problem here is that the data needs to be sent from the client in a slightly different format. Each property in your UploadPayload class needs to be sent in its own form part:

const formData = new FormData();
formData.append(`file`, file);
formData.append('md5', JSON.stringify(md5));
formData.append('sessionIds', JSON.stringify(sessionIds));

执行此操作后,可以将[FromForm]属性添加到MD5属性以进行绑定,因为它是一个简单的字符串值.尽管SessionIds属性是一个复杂的对象,但这对它不起作用.

Once you do this, you can add the [FromForm] attribute to the MD5 property to bind it, since it is a simple string value. This will not work for the SessionIds property though since it is a complex object.

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

Binding complex JSON from the form data can be accomplished using a custom model binder:

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

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

You can then use the ModelBinder attribute in your DTO class to indicate that this binder should be used to bind the MyJson property:

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

您可以在ASP.NET Core文档中阅读有关自定义模型绑定的更多信息:

You can read more about custom model binding in the ASP.NET Core documentation: https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding

这篇关于ASP.NET Core 1.1中用于多部分/表单数据(文件+ JSON)发布的模型绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆