使用额外参数上传Webapi formdata(到DB)

我需要上传文件发送额外的参数。

我在stackoverflow中找到了以下post: 带有额外参数的Webapi ajax formdata上传

它描述了如何使用MultipartFormDataStreamProvider并将数据保存到文件服务器。 我不需要将文件保存到服务器,而是保存到DB。 我已经使用MultipartMemoryStreamProvider处理代码,但它不使用额外的参数。

你能告诉我如何在webapi中处理额外的参数吗?

例如,如果我添加文件并测试参数:

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

这是我的webapi处理fileupload而没有额外的参数:

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

您可以通过实现自定义DataStreamProvider以非常非常干净的方式实现此目的,该自定义DataStreamProvider复制用于从MultipartFormDataStreamProvider多部分内容中解析FormData的逻辑。

我不太确定为什么决定从MultiPartFileStreamProviderinheritanceMultipartFormDataStreamProvider而不至少提取标识和公开FormData集合的代码,因为它对于涉及多部分数据的许多任务非常有用,只需将文件保存到磁盘。

无论如何,以下提供商应该有助于解决您的问题。 您仍然需要确保在迭代提供程序内容时忽略任何没有文件名的内容(特别是语句streamProvider.Contents.Select()否则您可能会尝试将formdata上传到数据库)。 因此,询问提供者的代码是一个HttpContent IsStream(),这有点像黑客,但最简单的是我能想到这样做。

请注意,它基本上是来自MultipartFormDataStreamProvider源的剪切和粘贴斧头作业 – 它尚未经过严格测试(受此答案的启发)。

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

它可以如下使用(使用TPL语法来匹配您的问题):

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

我使用以下HTML表单对其进行了测试:

 

扩展gooid的答案,我将FormData提取封装到提供程序中,因为我遇到引用它的问题。 在我看来,这只是提供了一个更好的实现。

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

这就是我如何使用它。 请注意,我使用等待,因为我们在.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)); } 

我真的需要上传文件的媒体类型和长度,所以我修改了@Mark Seefeldt对以下内容的回答:

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

最终,以下是对我有用的:

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