【问题标题】:Webapi formdata upload (to DB) with extra parameters带有额外参数的 Webapi 表单数据上传(到数据库)
【发布时间】:2013-11-12 10:27:14
【问题描述】:

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

我在stackoverflow中找到了以下帖子:Webapi ajax formdata upload with extra parameters

它描述了如何使用 MultipartFormDataStreamProvider 执行此操作并将数据保存到文件服务器。我不需要将文件保存到服务器,而是保存到数据库。 而且我已经有使用 MultipartMemoryStreamProvider 的工作代码,但它不使用额外的参数。

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

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

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

这是我的 webapi,无需额外参数即可处理文件上传:

if (Request.Content.IsMimeMultipartContent())
{               
    var streamProvider = new MultipartMemoryStreamProvider();
    var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<FileModel>>(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;
}

【问题讨论】:

    标签: c# file-upload asp.net-web-api form-data


    【解决方案1】:

    我真的需要上传文件的媒体类型和长度,所以我修改了@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<bool> _isFormData = new Collection<bool>();
        private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
        private readonly List<MultipartFormFile> _fileStreams = new List<MultipartFormFile>();
    
        public NameValueCollection FormData
        {
            get { return _formData; }
        }
    
        public List<MultipartFormFile> 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;
        }
    }
    

    【讨论】:

      【解决方案2】:

      最终,以下对我有用:

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

      【讨论】:

      • 这里的一个问题是您正在为每个文件调用 SaveChanges()。您可能应该只为整个集合调用一次,因此您的保存是原子的。
      • 根据stackoverflow.com/questions/1930982/…,在每一行之后保存实际上更快。
      • 这就是为什么我没有声称它更快。
      • 根据您的链接接受的答案:“如果您需要在一个事务中输入所有行,请在所有 AddToClassName 类之后调用它。如果可以独立输入行,则在每一行之后保存更改。数据库一致性是很重要。”
      【解决方案3】:

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

      public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
      {
          private readonly Collection<bool> _isFormData = new Collection<bool>();
          private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
          private readonly Dictionary<string, Stream> _fileStreams = new Dictionary<string, Stream>();
      
          public NameValueCollection FormData
          {
              get { return _formData; }
          }
      
          public Dictionary<string, Stream> 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,所以我使用了 await。

          [HttpPost]
          public async Task<HttpResponseMessage> 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<string> uploadedFiles = new List<string>();
              foreach (KeyValuePair<string, Stream> 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 的回答在 .Net 4.5 中对我来说效果很好。遗憾的是,此功能不支持开箱即用。
      • MultipartFormDataMemoryStreamProvider 中的“内容”是什么?
      • 它是其基类的 Contents 属性。基本上就是 HTTP 请求的内容。 msdn.microsoft.com/en-us/library/…
      • 这适用于使用异步等待的 .NET4.5。 @MarkSeefeldt 只是好奇您的 UploadManager 是什么样的?我对流不太熟悉,想确保我发送文件的类也被正确声明。
      • 我的 UpdateManager.Upload 函数是一个静态方法,它只接受 uploadType 并确定将文件发送到哪个文件上传器实现类。例如,我们有一个用于某种类型的 excel 格式文件。它创建一个 ExcelPackage(stream),然后使用 ExcelPackage 读取行数据并上传到数据库。抱歉,我无法为您提供详细信息,因为它非常具体。
      【解决方案4】:

      您可以通过实现自定义DataStreamProvider 以一种不太干净的方式实现此目的,该DataStreamProvider 复制了从MultipartFormDataStreamProvider 的多部分内容解析FormData 的逻辑。

      我不太确定为什么决定从 MultiPartFileStreamProvider 子类化 MultipartFormDataStreamProvider 至少没有提取识别和公开 FormData 集合的代码,因为它对于涉及外部多部分数据的许多任务很有用只需将文件保存到磁盘。

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

      请注意,它基本上是来自MultipartFormDataStreamProvider 源的剪切和粘贴工作 - 它没有经过严格测试(受this answer 启发)。

      public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
      {
          private readonly Collection<bool> _isFormData = new Collection<bool>();
          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<string> 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 表单对其进行了测试:

      <form action="/api/values" method="post" enctype="multipart/form-data">
          <input name="myParameter" type="hidden" value="i dont do anything interesting"/>
          <input type="file" name="file1" />
          <input type="file" name="file2" />
          <input type="submit" value="OK" />
      </form>
      

      【讨论】:

      • var count = provider.FileData.Count;给出错误(不包含 FileData 的定义)?
      • 另一个问题:在您的示例中如何获取文件内容?由于某种原因我无法得到它
      • 我已经更新了提供者实现和示例控制器,请看一下。
      • 无法编译...我添加了 private Collection _fileData = new Collection();如在链接中并做了一些与文件相关的更改,我的代码可以编译,但这没有。
      • 哪个位不编译?请注意,我更新了示例控制器操作,provder.FileData.Count 已经消失。我故意省略了链接答案中使用的 FileData 集合代码,因为它似乎与您不太相关(您通过检查 ContentDisposition 标头来解析文件名)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-24
      • 2015-03-13
      • 1970-01-01
      • 2015-01-20
      • 2011-11-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多