2016-12-28 1 views
11

Wie kann ich eine Liste von Dateien (Bildern) und JSON-Daten in den ASP.NET Core Web API-Controller hochladen, indem Sie einen mehrteiligen Upload verwenden? Dateien und JSON in ASP.NET Core Web API hochladen

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

Und natürlich

Ich kann erfolgreich HTTP-Anforderung Körper zu meinem Objekt mit Standard JSON Formatierer so formatiert erhalten:

ich eine Liste von Dateien erfolgreich empfangen kann, mit multipart/form-data Inhaltstyp wie die hochgeladen

public void Post([FromBody]SomeObject value) 

Aber wie kann ich diese beiden in einer einzigen Controller-Aktion kombinieren? Wie kann ich sowohl Bilder als auch JSON-Daten hochladen und sie an meine Objekte binden lassen?

+0

Dateien sollen mit 'multipart/form-data' gesendet werden soll. JSON soll mit 'application/json' gesendet werden. Sie können nur einen Typ senden. Es gibt also keinen sauberen Weg, dies zu tun. – Fred

Antwort

5

Offenbar gibt es keine eingebaute Möglichkeit zu tun, was ich will. Also habe ich meine eigene ModelBinder geschrieben, um mit dieser Situation umzugehen. Ich habe keine offizielle Dokumentation zur benutzerdefinierten Modellbindung gefunden, aber ich habe this post als Referenz verwendet.

Benutzer ModelBinder sucht nach Eigenschaften, die mit FromJson Attribut und Deserialize-Zeichenfolge, die von Multipart-Anforderung zu JSON kam, dekoriert sind. Ich verpacke mein Modell in eine andere Klasse (Wrapper) mit Modell- und IFormFile Eigenschaften.

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

Verbrauch:

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

Welchen InputFormatter sollte ich verwenden, um Daten als Multipart/Form-Daten zu empfangen? Fehler 500 erhalten, wenn content-type multipart/form-data ist. –

0

Ich bin mir nicht sicher, ob Sie die zwei Dinge in einem einzigen Schritt tun können.

Wie ich dies in der Vergangenheit erreicht habe, indem ich die Datei über Ajax hochlade und die Datei-URL zurück in die Antwort zurückschicke und sie dann zusammen mit der Postanforderung weitergab, um den tatsächlichen Datensatz zu speichern.

+0

Ja, das wäre sicherlich möglich, aber ich versuche, zwei verschiedene Verbindungen zum Server für eine Aufgabe zu vermeiden, nur um alles zwischen Clients und Server synchron zu halten. Ich denke, ich habe eine Lösung für mein Problem gefunden. Ich werde es hier posten, wenn ich noch etwas Zeit habe. – Andrius

2

ich einen einfacheren Ansatz gemacht haben, was Andrius schon bekommen:

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

Jetzt können wir das Modell Binder direkt verwenden, ohne durch ein Wrapper-Modell zu gehen:

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

} 
Verwandte Themen