Caricamento di form di dati Webapi (su DB) con parametri aggiuntivi

Ho bisogno di caricare il file inviando parametri aggiuntivi.

Ho trovato il seguente post in StackOverflow: Webapi ajax formdata upload con parametri aggiuntivi

Descrive come farlo usando MultipartFormDataStreamProvider e salvando i dati su fileserver. Non ho bisogno di salvare file sul server, ma su DB. E ho già lavorato codice utilizzando MultipartMemoryStreamProvider, ma non usa parametri aggiuntivi.

Puoi darmi degli indizi su come elaborare parametri aggiuntivi in ​​webapi?

Ad esempio, se aggiungo il file e anche il parametro di test:

data.append("myParameter", "test"); 

Ecco il mio webapi che elabora il fileupload senza parametri aggiuntivi:

 if (Request.Content.IsMimeMultipartContent()) { var streamProvider = new MultipartMemoryStreamProvider(); var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable>(t => { if (t.IsFaulted || t.IsCanceled) { throw new HttpResponseException(HttpStatusCode.InternalServerError); } _fleDataService = new FileDataBLL(); FileData fle; var fleInfo = streamProvider.Contents.Select(i => { fle = new FileData(); fle.FileName = i.Headers.ContentDisposition.FileName; var contentTest = i.ReadAsByteArrayAsync(); contentTest.Wait(); if (contentTest.Result != null) { fle.FileContent = contentTest.Result; } // get extra parameters here ?????? _fleDataService.Save(fle); return new FileModel(i.Headers.ContentDisposition.FileName, 1024); //todo }); return fleInfo; }); return task; } 

È ansible ottenere questo risultato in un modo non molto pulito implementando un DataStreamProvider personalizzato che duplica la logica per l’analisi di FormData dal contenuto di più parti da MultipartFormDataStreamProvider .

Non sono del tutto sicuro del motivo per cui è stata presa la decisione di sottoclass MultipartFormDataStreamProvider da MultiPartFileStreamProvider senza estrarre almeno il codice che identifica e espone la raccolta FormData poiché è utile per molte attività che coinvolgono dati multiparte al di fuori del semplice salvataggio di un file su disco.

Ad ogni modo, il seguente provider dovrebbe aiutare a risolvere il tuo problema. Sarà comunque necessario assicurarsi che quando si itera il contenuto del provider si ignori tutto ciò che non ha un nome file (in particolare l’istruzione streamProvider.Contents.Select() altrimenti si rischia di provare a caricare i dati del modulo nel DB). Quindi il codice che chiede al provider è un HttpContent IsStream (), questo è un po ‘un trucco, ma è stato il più semplice se potessi pensare di farlo.

Si noti che è fondamentalmente un lavoro hatchet taglia e incolla dall’origine di MultipartFormDataStreamProvider – non è stato rigorosamente testato (ispirato a questa risposta ).

 public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider { private readonly Collection _isFormData = new Collection(); private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); public NameValueCollection FormData { get { return _formData; } } public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) { if (parent == null) throw new ArgumentNullException("parent"); if (headers == null) throw new ArgumentNullException("headers"); var contentDisposition = headers.ContentDisposition; if (contentDisposition != null) { _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); return base.GetStream(parent, headers); } throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); } public override async Task ExecutePostProcessingAsync() { for (var index = 0; index < Contents.Count; index++) { if (IsStream(index)) continue; var formContent = Contents[index]; var contentDisposition = formContent.Headers.ContentDisposition; var formFieldName = UnquoteToken(contentDisposition.Name) ?? string.Empty; var formFieldValue = await formContent.ReadAsStringAsync(); FormData.Add(formFieldName, formFieldValue); } } private static string UnquoteToken(string token) { if (string.IsNullOrWhiteSpace(token)) return token; if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) return token.Substring(1, token.Length - 2); return token; } public bool IsStream(int idx) { return !_isFormData[idx]; } } 

Può essere usato come segue (usando la syntax TPL per abbinare la tua domanda):

 [HttpPost] public Task Post() { if (!Request.Content.IsMimeMultipartContent()) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!")); var provider = new MultipartFormDataMemoryStreamProvider(); return Request.Content.ReadAsMultipartAsync(provider).ContinueWith(p => { var result = p.Result; var myParameter = result.FormData.GetValues("myParameter").FirstOrDefault(); foreach (var stream in result.Contents.Where((content, idx) => result.IsStream(idx))) { var file = new FileData(stream.Headers.ContentDisposition.FileName); var contentTest = stream.ReadAsByteArrayAsync(); // ... and so on, as per your original code. } return myParameter; }); } 

L’ho provato con il seguente modulo HTML:

 

Espandendo la risposta di gooid, ho incapsulato l’estrazione FormData nel provider perché avevo dei problemi con la citazione. Questo ha fornito una migliore implementazione a mio parere.

 public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider { private readonly Collection _isFormData = new Collection(); private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _fileStreams = new Dictionary(); public NameValueCollection FormData { get { return _formData; } } public Dictionary FileStreams { get { return _fileStreams; } } public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) { if (parent == null) { throw new ArgumentNullException("parent"); } if (headers == null) { throw new ArgumentNullException("headers"); } var contentDisposition = headers.ContentDisposition; if (contentDisposition == null) { throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); } _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); return base.GetStream(parent, headers); } public override async Task ExecutePostProcessingAsync() { for (var index = 0; index < Contents.Count; index++) { HttpContent formContent = Contents[index]; if (_isFormData[index]) { // Field string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty; string formFieldValue = await formContent.ReadAsStringAsync(); FormData.Add(formFieldName, formFieldValue); } else { // File string fileName = UnquoteToken(formContent.Headers.ContentDisposition.FileName); Stream stream = await formContent.ReadAsStreamAsync(); FileStreams.Add(fileName, stream); } } } private static string UnquoteToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return token; } if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) { return token.Substring(1, token.Length - 2); } return token; } } 

Ed ecco come lo sto usando. Nota che ho usato attendere poiché siamo su .NET 4.5.

  [HttpPost] public async Task Upload() { if (!Request.Content.IsMimeMultipartContent()) { return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, "Unsupported media type."); } // Read the file and form data. MultipartFormDataMemoryStreamProvider provider = new MultipartFormDataMemoryStreamProvider(); await Request.Content.ReadAsMultipartAsync(provider); // Extract the fields from the form data. string description = provider.FormData["description"]; int uploadType; if (!Int32.TryParse(provider.FormData["uploadType"], out uploadType)) { return Request.CreateResponse(HttpStatusCode.BadRequest, "Upload Type is invalid."); } // Check if files are on the request. if (!provider.FileStreams.Any()) { return Request.CreateResponse(HttpStatusCode.BadRequest, "No file uploaded."); } IList uploadedFiles = new List(); foreach (KeyValuePair file in provider.FileStreams) { string fileName = file.Key; Stream stream = file.Value; // Do something with the uploaded file UploadManager.Upload(stream, fileName, uploadType, description); // Keep track of the filename for the response uploadedFiles.Add(fileName); } return Request.CreateResponse(HttpStatusCode.OK, "Successfully Uploaded: " + string.Join(", ", uploadedFiles)); } 

Avevo davvero bisogno del tipo di supporto e della lunghezza dei file caricati, quindi ho modificato @ Mark Seefeldt rispondendo leggermente a quanto segue:

 public class MultipartFormFile { public string Name { get; set; } public long? Length { get; set; } public string MediaType { get; set; } public Stream Stream { get; set; } } public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider { private readonly Collection _isFormData = new Collection(); private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); private readonly List _fileStreams = new List(); public NameValueCollection FormData { get { return _formData; } } public List FileStreams { get { return _fileStreams; } } public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) { if (parent == null) { throw new ArgumentNullException("parent"); } if (headers == null) { throw new ArgumentNullException("headers"); } var contentDisposition = headers.ContentDisposition; if (contentDisposition == null) { throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); } _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); return base.GetStream(parent, headers); } public override async Task ExecutePostProcessingAsync() { for (var index = 0; index < Contents.Count; index++) { HttpContent formContent = Contents[index]; if (_isFormData[index]) { // Field string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty; string formFieldValue = await formContent.ReadAsStringAsync(); FormData.Add(formFieldName, formFieldValue); } else { // File var file = new MultipartFormFile { Name = UnquoteToken(formContent.Headers.ContentDisposition.FileName), Length = formContent.Headers.ContentLength, MediaType = formContent.Headers.ContentType.MediaType, Stream = await formContent.ReadAsStreamAsync() }; FileStreams.Add(file); } } } private static string UnquoteToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return token; } if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) { return token.Substring(1, token.Length - 2); } return token; } } 

In definitiva, il seguente è stato ciò che ha funzionato per me:

 string root = HttpContext.Current.Server.MapPath("~/App_Data"); var provider = new MultipartFormDataStreamProvider(root); var filesReadToProvider = await Request.Content.ReadAsMultipartAsync(provider); foreach (var file in provider.FileData) { var fileName = file.Headers.ContentDisposition.FileName.Replace("\"", string.Empty); byte[] documentData; documentData = File.ReadAllBytes(file.LocalFileName); DAL.Document newRecord = new DAL.Document { PathologyRequestId = PathologyRequestId, FileName = fileName, DocumentData = documentData, CreatedById = ApplicationSecurityDirector.CurrentUserGuid, CreatedDate = DateTime.Now, UpdatedById = ApplicationSecurityDirector.CurrentUserGuid, UpdatedDate = DateTime.Now }; context.Documents.Add(newRecord); context.SaveChanges(); }