2014-05-19 10 views
9

Desidero caricare un file e inviare insieme al file alcune informazioni aggiuntive, ad esempio una stringa pippo e una barra int.Metodo WebAPI che richiede un caricamento di file e argomenti aggiuntivi

Come scrivere un metodo di controllo WebAPI ASP.NET che riceve un caricamento di file, una stringa e un int?

mio JavaScript:

var fileInput = document.querySelector("#filePicker"); 
var formData = new FormData(); 
formData.append("file", fileInput.files[0]); 
formData.append("foo", "hello world!"); 
formData.append("bar", 42); 

var options = { 
    url: "/api/foo/upload", 
    data: formData, 
    processData: false // Prevents JQuery from transforming the data into a query string 
}; 
$.ajax(options); 

mio regolatore WebAPI può accedere al file in questo modo:

public async Task<HttpResponseMessage> Upload() 
{ 
    var streamProvider = new MultipartMemoryStreamProvider(); 
    await Request.Content.ReadAsMultipartAsync(streamProvider); 
    var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync(); 
} 

Ma non è chiaro per me come posso ottenere la mia corda e il mio int. Immagino di poter probabilmente dire streamProvider.Content [1], o qualsiasi altra cosa, ma questo mi sembra super cattivo.

Qual è il modo giusto © per scrivere un'azione WebAPI che accetta un caricamento di file, una stringa e un int?

+0

Sono probabilmente manca qualcosa ... Perché non puoi semplicemente fare foo e bar parametri del tuo metodo di caricamento e lasciare che il legame la magia accade? – Scrappydog

+0

Mi sono concentrato sul laser per inviare i dati all'interno di FormData (ad es. Come i dati all'interno di $ .post (url, formData), il modello non li troverebbe mai.Esso, posso postare il file come solo i dati del modulo, quindi metti gli altri argomenti nell'URL, quindi il modello legatore fa la sua magia –

risposta

8

È possibile creare il proprio MultipartFileStreamProvider per accedere agli argomenti aggiuntivi.

Nel ExecutePostProcessingAsync eseguiamo un ciclo di ogni file in più parti e carichiamo i dati personalizzati (se hai solo un file, avrai un solo oggetto nell'elenco CustomData).

class MyCustomData 
{ 
    public int Foo { get; set; } 
    public string Bar { get; set; } 
} 

class CustomMultipartFileStreamProvider : MultipartMemoryStreamProvider 
{ 
    public List<MyCustomData> CustomData { get; set; } 

    public CustomMultipartFileStreamProvider() 
    { 
     CustomData = new List<MyCustomData>(); 
    } 

    public override Task ExecutePostProcessingAsync() 
    { 
     foreach (var file in Contents) 
     { 
      var parameters = file.Headers.ContentDisposition.Parameters; 
      var data = new MyCustomData 
      { 
       Foo = int.Parse(GetNameHeaderValue(parameters, "Foo")), 
       Bar = GetNameHeaderValue(parameters, "Bar"), 
      }; 

      CustomData.Add(data); 
     } 

     return base.ExecutePostProcessingAsync(); 
    } 

    private static string GetNameHeaderValue(ICollection<NameValueHeaderValue> headerValues, string name) 
    { 
     var nameValueHeader = headerValues.FirstOrDefault(
      x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); 

     return nameValueHeader != null ? nameValueHeader.Value : null; 
    } 
} 

Poi nel controller:

class UploadController : ApiController 
{ 
    public async Task<HttpResponseMessage> Upload() 
    { 
     var streamProvider = new CustomMultipartFileStreamProvider(); 
     await Request.Content.ReadAsMultipartAsync(streamProvider); 

     var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync(); 
     var customData = streamProvider.CustomData; 

     return Request.CreateResponse(HttpStatusCode.Created); 
    } 
} 
+0

Grazie. Ho finito per passare gli args usando argomenti "semplici", ad es. $ .post ("/ api/foo? id = 23 & bar = hello", fileData). Questo funziona ... a condizione di non passare oggetti o array complessi. Mi piace la tua soluzione migliore, quindi la segnalo come risposta. –

+2

Nel nostro tentativo, otteniamo il nome del parametro nella parte valore della coppia chiave/valore - il campo chiave semplice contiene 'nome'. Qualcosa è cambiato da maggio? – codeputer

2

Puoi farlo seguendo modo: JQuery Metodo:

var data = new FormData(); 

    data.append("file", filesToUpload[0].rawFile); 
    var doc = {};    
    doc.DocumentId = 0; 
    $.support.cors = true; 
    $.ajax({ 
     url: '/api/document/uploaddocument', 
     type: 'POST', 
     contentType: 'multipart/form-data', 
     data: data, 
     cache: false, 
     contentType: false, 
     processData: false, 
     success: function (response) { 
      docId = response.split('|')[0]; 
      doc.DocumentId = docId; 
      $.post('/api/document/metadata', doc) 
       .done(function (response) { 
       }); 
      alert('Document save successfully!'); 
     }, 
     error: function (e) { 
      alert(e); 
     } 
    }); 

chiamare il 'UploadDocuement' API Web

[Route("api/document/uploaddocument"), HttpPost] 
     [UnhandledExceptionFilter] 
     [ActionName("UploadDocument")] 
     public Task<HttpResponseMessage> UploadDocument() 
     { 
      // Check if the request contains multipart/form-data. 
      if (!Request.Content.IsMimeMultipartContent()) 
      { 
       Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate() 
       { 
        return new HttpResponseMessage() 
        { 
         StatusCode = HttpStatusCode.BadRequest, 
         Content = "In valid file & request content type!".ToStringContent() 
        }; 
       }); 
       return mytask; 
      } 


      string root = HttpContext.Current.Server.MapPath("~/Documents"); 
      if (System.IO.Directory.Exists(root)) 
      { 
       System.IO.Directory.CreateDirectory(root); 
      } 
      var provider = new MultipartFormDataStreamProvider(root); 

      var task = Request.Content.ReadAsMultipartAsync(provider). 
      ContinueWith<HttpResponseMessage>(o => 
      { 
       if (o.IsFaulted || o.IsCanceled) 
        throw new HttpResponseException(HttpStatusCode.InternalServerError); 

       FileInfo finfo = new FileInfo(provider.FileData.First().LocalFileName); 

       string guid = Guid.NewGuid().ToString(); 

       File.Move(finfo.FullName, Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", ""))); 

       string sFileName = provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", ""); 

       FileInfo FInfos = new FileInfo(Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", ""))); 

       Document dbDoc = new Document() 
       { 
        DocumentID = 0     

       }; 

       context.DocumentRepository.Insert(dbDoc); 
       context.Save(); 

       return new HttpResponseMessage() 
       { 
        Content = new StringContent(string.Format("{0}|File uploaded.", dbDoc.DocumentID)) 
       }; 
      } 
      ); 
      return task; 

     } 

zero Chiamate metadati web api dal seguente modo:

[Route("api/document/metadata"), HttpPost] 
     [ActionName("Metadata")] 
     public Task<HttpResponseMessage> Metadata(Document doc) 
     { 
      int DocId = Convert.ToInt32(System.Web.HttpContext.Current.Request.Form["DocumentId"].ToString()); 

      Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate() 
      { 
       return new HttpResponseMessage() 
       { 
        Content = new StringContent("metadata updated") 
       }; 
      }); 
      return mytask; 
     } 
+0

Quindi, se sto capendo bene, stai facendo 2 chiamate: una per il file, un'altra per i metadati (ad es. gli "argomenti aggiuntivi" parte della mia domanda) C'è un modo per farlo in una singola chiamata? –

+0

prova questo in UploadDocument web api "System.Web.HttpContext.Current.Request.Form [" DocumentId "]. ToString()" –

+0

in sopra eample, sto facendo 2 chiamate, una per il file e un'altra per i metadati, puoi ottenerla con una sola chiamata come segue prova questo in UploadDocument web api "System.Web.HttpContext.Current.Request.Form [" DocumentId "] .ToString() " –

1

penso che le risposte qui sono eccellenti. Così altri possono vedere un esempio un po 'semplice di come passare i dati in aggiunta al file in forma di riepilogo, inclusa è una funzione Javascript che effettua la chiamata WebAPI al controller di FileUpload e lo snippet da FileUpload Controller (in VB.net) che legge i dati aggiuntivi passati da Javascript.

Javascript:

  function uploadImage(files) { 
      var data = new FormData(); 
      if (files.length > 0) { 
       data.append("UploadedImage", files[0]); 
       data.append("Source", "1") 
       var ajaxRequest = $.ajax({ 
        type: "POST", 
        url: "/api/fileupload/uploadfile", 
        contentType: false, 
        processData: false, 
        data: data 
       }); 

Upload di file di controllo:

 <HttpPost> _ 
    Public Function UploadFile() As KeyValuePair(Of Boolean, String) 
     Try 
      If HttpContext.Current.Request.Files.AllKeys.Any() Then 
       Dim httpPostedFile = HttpContext.Current.Request.Files("UploadedImage") 
       Dim source = HttpContext.Current.Request.Form("Source").ToString() 

Quindi, come potete vedere nella Javascript, i dati aggiuntivi passati è la chiave "Source", e il valore è "1 ". E come Chandrika ha risposto sopra, il Controllore legge questi dati passati attraverso "System.Web.HttpContext.Current.Request.Form (" Source "). ToString()".

Nota che Form ("Origine") utilizza() (rispetto a []) poiché il codice del controller è in VB.net.

Spero che questo aiuti.

2

È possibile estrarre più file e gli attributi più in questo modo:

public async Task<HttpResponseMessage> Post() 
{ 
    Dictionary<string,string> attributes = new Dictionary<string, string>(); 
    Dictionary<string, byte[]> files = new Dictionary<string, byte[]>(); 

    var provider = new MultipartMemoryStreamProvider(); 
    await Request.Content.ReadAsMultipartAsync(provider); 
    foreach (var file in provider.Contents) 
    { 
     if (file.Headers.ContentDisposition.FileName != null) 
     { 
      var filename = file.Headers.ContentDisposition.FileName.Trim('\"'); 
      var buffer = await file.ReadAsByteArrayAsync(); 
      files.Add(filename, buffer); 
     } else 
     { 
      foreach(NameValueHeaderValue p in file.Headers.ContentDisposition.Parameters) 
      { 
       string name = p.Value; 
       if (name.StartsWith("\"") && name.EndsWith("\"")) name = name.Substring(1, name.Length - 2); 
       string value = await file.ReadAsStringAsync(); 
       attributes.Add(name, value); 
      } 
     } 
    } 
    //Your code here 
    return new HttpResponseMessage(HttpStatusCode.OK); 
}