2016-12-28 200 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) { 

}