具有文件和json属性的ASP.NET Core和formdata绑定 [英] ASP.NET Core and formdata binding with file and json property

查看:166
本文介绍了具有文件和json属性的ASP.NET Core和formdata绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下模型:

public class MyJson
{
 public string Test{get;set;}
}

public class Dto
{
 public IFormFile MyFile {get;set;}
 public MyJson MyJson {get;set;}
}

在客户端,我想发送一个文件和一个json.因此,我使用以下键在formData中发送它:

On the client side I want to send a file and a json. So I send it in formData with following keys:

var formData = new FormData();
formData["myFile"] = file//here is my file
formData["myJson"] = obj; // object to be serialized to json.

我的动作如下:

public void MyAction(Dto dto) // or with [FromForm], doesn't really matter
{
  //dto.MyJson is null here
  //dto.myFile is set correctly.
}

如果将dto.MyJson更改为字符串,则可以正常工作,但是在操作中必须手动将其反序列化为对象.将其作为字符串的第二个问题是,我不能使用swagger UI来正确处理它,因为它将要求我提供json字符串而不是对象,无论如何,将其作为字符串听起来是不对的.有没有一种本机方法可以正确处理操作参数中的json和文件,而不是使用Request.Form手动解析它?

if I change dto.MyJson to be a string then it work perfectly fine however I have to deserialize it manually into my object manually in the action. The second issue with having it as a string is that I can't use swagger UI to handle it properly because it will ask me for a json string instead of object, anyway having it as a string just doesn't sound right. Is there a native way to handle json and file properly in action parameters instead of parsing it manually with Request.Form?

推荐答案

可以使用自定义模型绑定程序来实现:

This 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 Dto
{
    public IFormFile MyFile {get;set;}

    [ModelBinder(BinderType = typeof(FormDataJsonBinder))]
    public MyJson MyJson {get;set;}
}

请注意,您还需要在客户端中正确序列化JSON数据:

Note that you also need to serialize your JSON data from correctly in the client:

const formData = new FormData();
formData.append(`myFile`, file);
formData.append('myJson', JSON.stringify(obj));

上面的代码将起作用,但是您还可以更进一步,定义一个自定义属性和一个自定义IModelBinderProvider,因此您不需要每次都使用更冗长的ModelBinder属性.请注意,我已经为此重新使用了现有的[FromForm]属性,但是您也可以定义自己的属性来代替.

The above code will work, but you can also go a step further and define a custom attribute and a custom IModelBinderProvider so you don't need to use the more verbose ModelBinder attribute each time you want to do this. Note that I have re-used the existing [FromForm] attribute for this, but you could also define your own attribute to use instead.

public class FormDataJsonBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if(context == null) throw new ArgumentNullException(nameof(context));

        // Do not use this provider for binding simple values
        if(!context.Metadata.IsComplexType) return null;

        // Do not use this provider if the binding target is not a property
        var propName = context.Metadata.PropertyName;
        var propInfo = context.Metadata.ContainerType?.GetProperty(propName);
        if(propName == null || propInfo == null) return null;

        // Do not use this provider if the target property type implements IFormFile
        if(propInfo.PropertyType.IsAssignableFrom(typeof(IFormFile))) return null;

        // Do not use this provider if this property does not have the FromForm attribute
        if(!propInfo.GetCustomAttributes(typeof(FromForm), false).Any()) return null;

        // All criteria met; use the FormDataJsonBinder
        return new FormDataJsonBinder();
    }
}

您需要在启动配置中将此模型绑定程序提供程序添加到启动配置中:

You will need to add this model binder provider to your startup config before it will be picked up:

services.AddMvc(options =>
{
    // add custom model binders to beginning of collection
    options.ModelBinderProviders.Insert(0, new FormDataJsonBinderProvider())
});

那么您的DTO可能会更简单:

Then your DTO can be a bit simpler:

public class Dto
{
    public IFormFile MyFile {get;set;}

    [FromForm]
    public MyJson MyJson {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

这篇关于具有文件和json属性的ASP.NET Core和formdata绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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