ASP.NET Core and formdata binding with file and json property

前端 未结 1 1706
后悔当初
后悔当初 2021-01-05 12:31

I have a following model:

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

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


        
相关标签:
1条回答
  • 2021-01-05 13:12

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

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

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

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

    Then your DTO can be a bit simpler:

    public class Dto
    {
        public IFormFile MyFile {get;set;}
    
        [FromForm]
        public MyJson MyJson {get;set;}
    }
    

    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

    0 讨论(0)
提交回复
热议问题