2016-12-28 175 views
11

如何使用分段上传将文件(图像)和json数据的列表上传到ASP.NET Core Web API控制器?在ASP.NET Core Web API中上传文件和JSON

我可以成功接收的文件列表,用multipart/form-data内容类型一样,上传:

public async Task<IActionResult> Upload(IList<IFormFile> files) 

当然,我可以成功地接收HTTP请求体使用默认JSON格式类似的格式设置为我的对象:

public void Post([FromBody]SomeObject value) 

但是我怎样才能将这两个在一个单一的控制器行动?我如何上传图像和JSON数据,并将它们绑定到我的对象上?

+0

文件是为了用'多部分/格式data'发送。 JSON旨在通过'application/json'发送。您只能发送一种类型。所以没有干净的方式来做到这一点。 – Fred

回答

5

显然没有内置的方式来做我想要的。所以我最终写了我自己的ModelBinder来处理这种情况。我没有找到任何有关自定义模型绑定的官方文档,但我使用this post作为参考。

定制ModelBinder将搜索用FromJson属性装饰的属性,并反序列化从多部分请求到JSON的字符串。我将我的模型包装在另一个具有模型和IFormFile属性的类(包装器)中。

IJsonAttribute.cs:

public interface IJsonAttribute 
{ 
    object TryConvert(string modelValue, Type targertType, out bool success); 
} 

FromJsonAttribute.cs:

using Newtonsoft.Json; 
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 
public class FromJsonAttribute : Attribute, IJsonAttribute 
{ 
    public object TryConvert(string modelValue, Type targetType, out bool success) 
    { 
     var value = JsonConvert.DeserializeObject(modelValue, targetType); 
     success = value != null; 
     return value; 
    } 
} 

JsonModelBinderProvider.cs:

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

     if (context.Metadata.IsComplexType) 
     { 
      var propName = context.Metadata.PropertyName; 
      var propInfo = context.Metadata.ContainerType?.GetProperty(propName); 
      if(propName == null || propInfo == null) 
       return null; 
      // Look for FromJson attributes 
      var attribute = propInfo.GetCustomAttributes(typeof(FromJsonAttribute), false).FirstOrDefault(); 
      if (attribute != null) 
       return new JsonModelBinder(context.Metadata.ModelType, attribute as IJsonAttribute); 
     } 
     return null; 
    } 
} 

JsonModelBinder.cs:

public class JsonModelBinder : IModelBinder 
{ 
    private IJsonAttribute _attribute; 
    private Type _targetType; 

    public JsonModelBinder(Type type, IJsonAttribute attribute) 
    { 
     if (type == null) throw new ArgumentNullException(nameof(type)); 
     _attribute = attribute as IJsonAttribute; 
     _targetType = type; 
    } 

    public Task BindModelAsync(ModelBindingContext bindingContext) 
    { 
     if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); 
     // Check the value sent in 
     var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (valueProviderResult != ValueProviderResult.None) 
     { 
      bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); 
      // Attempt to convert the input value 
      var valueAsString = valueProviderResult.FirstValue; 
      bool success; 
      var result = _attribute.TryConvert(valueAsString, _targetType, out success); 
      if (success) 
      { 
       bindingContext.Result = ModelBindingResult.Success(result); 
       return Task.CompletedTask; 
      } 
     } 
     return Task.CompletedTask; 
    } 
} 

用法:

public class MyModelWrapper 
{ 
    public IList<IFormFile> Files { get; set; } 
    [FromJson] 
    public MyModel Model { get; set; } // <-- JSON will be deserialized to this object 
} 

// Controller action: 
public async Task<IActionResult> Upload(MyModelWrapper modelWrapper) 
{ 
} 

// Add custom binder provider in Startup.cs ConfigureServices 
services.AddMvc(properties => 
{ 
    properties.ModelBinderProviders.Insert(0, new JsonModelBinderProvider()); 
}); 
+0

我应该使用什么InputFormatter以multipart/form-data的形式接收数据? 如果内容类型是multipart/form-data,则会出现错误500。 –

0

我不确定您是否可以在一个步骤中完成这两件事。

我过去如何实现这一点,是通过ajax上传文件并将文件url返回到响应中,然后将其与帖子请求一起传递以保存实际记录。

+0

是的,这当然是可能的,但我试图避免两个不同的连接到服务器的一项任务,只是为了保持客户端和服务器之间的一切同步。我想我已经找到了解决我的问题的方法。当我有更多时间时,我会在这里发布它。 – Andrius

2

我做了什么安德已经有了一个简单的方法:

JsonModelBinder.cs:

using Microsoft.AspNetCore.Mvc.ModelBinding; 

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

     // Check the value sent in 
     var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (valueProviderResult != ValueProviderResult.None) { 
      bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); 

      // Attempt to convert the input value 
      var valueAsString = valueProviderResult.FirstValue; 
      var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType); 
      if (result != null) { 
       bindingContext.Result = ModelBindingResult.Success(result); 
       return Task.CompletedTask; 
      } 
     } 
     return Task.CompletedTask; 
    } 
} 

现在我们可以使用模型绑定的情况下直接通过包装模型去:

public async Task<IActionResult> StorePackage([ModelBinder(BinderType = typeof(JsonModelBinder))] SomeObject value, IList<IFormFile> files) { 

}