2016-12-28 2 views
11

Как загрузить список файлов (изображений) и json-данных в ASP.NET Core Web API-контроллер с использованием многостраничной загрузки?Загрузить файлы и JSON в ASP.NET Core Web API

Я могу успешно получить список файлов, загруженный с 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 будет искать свойства, украшенные атрибутом атрибута и десериализации, которые поступают из многопрофильного запроса в 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? получение ошибки 500, если тип контента является multipart/form-data. –

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

} 
Смежные вопросы