Web API:使用 MultipartMemoryStreamProvider 时如何访问多部分表单值?

Posted

技术标签:

【中文标题】Web API:使用 MultipartMemoryStreamProvider 时如何访问多部分表单值?【英文标题】:Web API: how to access multipart form values when using MultipartMemoryStreamProvider? 【发布时间】:2013-06-08 23:52:00 【问题描述】:

我曾经使用MultipartFormDataStreamProvider 来处理多部分请求。

由于我希望将上传的文件存储在内存中,而不是磁盘文件中,因此我将代码更改为使用MultipartMemoryStreamProvider。文件加载似乎工作正常,但我不再能够访问 其他表单值可通过provider.FormDataMultipartFormDataStreamProvider 下获得。有人可以告诉我怎么做吗?

Fiddler 捕获的原始请求:

POST http://myserver.com/QCCSvcHost/MIME/RealtimeTrans/ HTTP/1.1
Content-Type: multipart/form-data; boundary="XbCY"
Host: na-w-lxu3
Content-Length: 1470
Expect: 100-continue
Connection: Keep-Alive

--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=PayloadType

X12_270_Request_005010X279A1
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=ProcessingMode

RealTime
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=PayloadID

e51d4fae-7dec-11d0-a765-00a0c91e6fa6
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=TimeStamp

2007-08-30T10:20:34Z
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=SenderID

HospitalA
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=ReceiverID

PayerB
--XbCY
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=CORERuleVersion

2.2.0
--XbCY
Content-Disposition: form-data; name=Payload; filename=276_5010.edi

ISA*00*~SE*16*0001~GE*1*1~IEA*1*191543498~
--XbCY--

我的控制器代码:

string payload = null;
NameValueCollection nvc = null;
string fname = null;
StringBuilder sb = new StringBuilder();
sb.AppendLine();
foreach (StreamContent item in provider.Contents)

    fname = item.Headers.ContentDisposition.FileName;
    if (!String.IsNullOrWhiteSpace(fname))
    
        payload = item.ReadAsStringAsync().Result;
    
    else
    
        nvc = item.ReadAsFormDataAsync().Result;
    

【问题讨论】:

【参考方案1】:

基于 Kiran 的出色回答,我汇总了 2015 年 4 月更新的完整答案。 WebAPI 中似乎至少发生了一些变化,这让我一开始感到困惑。 provider.Files 不再存在,它是 .Content。因此,您至少需要执行以下操作才能读取已发布的文件,而无需先将它们存储在磁盘上:

第 1 步:创建提供程序类

在你的项目中为这个类添加一个文件:

public class InMemoryMultipartFormDataProvider : MultipartFormDataRemoteStreamProvider

   public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)
   
      return new RemoteStreamInfo(
                remoteStream: new MemoryStream(),
                location: string.Empty,
                fileName: string.Empty);
   

我相信这会将每个文件转换为内存流,而不是将其存储在磁盘上。

第 2 步:添加控制器操作,解析内容并创建流

在您的控制器中:

[HttpPost]
public async Task<IHttpActionResult> Upload()

   // This endpoint only supports multipart form data
   if (!Request.Content.IsMimeMultipartContent("form-data"))
   
      return StatusCode(HttpStatusCode.UnsupportedMediaType);
   

   // read the content in a memory stream per file uploaded
   var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataProvider>(new InMemoryMultipartFormDataProvider());

   // iterate over each file uploaded and do something with the results
   foreach (var fileContents in provider.Contents) 
      processFileAsMemoryStream(await fileContents.ReadAsStreamAsync());
   

【讨论】:

【参考方案2】:

2015 年 4 月 28 日更新

您可以基于MultipartFormDataRemoteStreamProvider 创建自定义提供程序。 示例:

public class CustomMultipartFormDataProvider : MultipartFormDataRemoteStreamProvider

    public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)
    
        return new RemoteStreamInfo(
            remoteStream: new MemoryStream(),
            location: string.Empty,
            fileName: string.Empty);
    


更新

自定义内存中 MultiaprtFormDataStreamProvider:

public class InMemoryMultipartFormDataStreamProvider : MultipartStreamProvider

    private NameValueCollection _formData = new NameValueCollection();
    private List<HttpContent> _fileContents = new List<HttpContent>();

    // Set of indexes of which HttpContents we designate as form data
    private Collection<bool> _isFormData = new Collection<bool>();

    /// <summary>
    /// Gets a <see cref="NameValueCollection"/> of form data passed as part of the multipart form data.
    /// </summary>
    public NameValueCollection FormData
    
        get  return _formData; 
    

    /// <summary>
    /// Gets list of <see cref="HttpContent"/>s which contain uploaded files as in-memory representation.
    /// </summary>
    public List<HttpContent> Files
    
        get  return _fileContents; 
    

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
    
        // For form data, Content-Disposition header is a requirement
        ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
        if (contentDisposition != null)
        
            // We will post process this as form data
            _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));

            return new MemoryStream();
        

        // If no Content-Disposition header was present.
        throw new InvalidOperationException(string.Format("Did not find required '0' header field in MIME multipart body part..", "Content-Disposition"));
    

    /// <summary>
    /// Read the non-file contents as form data.
    /// </summary>
    /// <returns></returns>
    public override async Task ExecutePostProcessingAsync()
    
        // Find instances of non-file HttpContents and read them asynchronously
        // to get the string content and then add that as form data
        for (int index = 0; index < Contents.Count; index++)
        
            if (_isFormData[index])
            
                HttpContent formContent = Contents[index];
                // Extract name from Content-Disposition header. We know from earlier that the header is present.
                ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
                string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty;

                // Read the contents as string data and add to form data
                string formFieldValue = await formContent.ReadAsStringAsync();
                FormData.Add(formFieldName, formFieldValue);
            
            else
            
                _fileContents.Add(Contents[index]);
            
        
    

    /// <summary>
    /// Remove bounding quotes on a token if present
    /// </summary>
    /// <param name="token">Token to unquote.</param>
    /// <returns>Unquoted token.</returns>
    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 async Task Post()

    if (!Request.Content.IsMimeMultipartContent("form-data"))
    
        throw new HttpResponseException(HttpStatusCode.BadRequest);
    

    var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataStreamProvider>(new InMemoryMultipartFormDataStreamProvider());

    //access form data
    NameValueCollection formData = provider.FormData;

    //access files
    IList<HttpContent> files = provider.Files;

    //Example: reading a file's stream like below
    HttpContent file1 = files[0];
    Stream file1Stream = await file1.ReadAsStreamAsync();

【讨论】:

感谢 Kiran 的输入。当我尝试您的建议时,它似乎不喜欢 NameValueCollection nvc = await content.ReadAsFormDataAsync(); 行因为某些原因。我收到错误消息:“ExceptionMessage:No MediaTypeFormatter is available to read an object of type of 'FormDataCollection' from content with media type'multipart/form-data'”。有什么想法吗? 这个'内容'是否就像我上面提到的那样,是内容数组的内容吗?我问这个是因为看起来您正在尝试阅读整个请求的内容而不是内部内容 Kiran 效果很好!非常感谢你的帮助!我有点惊讶的是,Web API 并没有让这变得更方便,也没有为我们省去编写解析代码的麻烦。祝你有美好的一天! 很高兴有帮助!是的,我完全同意。我会向团队提出这个问题。谢谢!。顺便说一句,对于上述帖子的多次更新,我们深表歉意。我现在已经包含了一个完整的示例。 太糟糕了,我没有足够的“repatation”点来投票这个答案。我强烈建议其他人这样做。许多人认为 Kiran!

以上是关于Web API:使用 MultipartMemoryStreamProvider 时如何访问多部分表单值?的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Web API 2第十课——使用OWIN自承载Web API

Asp.Net Web API 2第十一课——在Web API中使用Dependency Resolver

使用 HttpClient 和 Web API 方法 [FromBody] 参数发布到 Web API 最终为空

如何使用 .net 框架 2.0 在 Web 服务中使用 Web api

如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合

从另一个 Web api 调用 Web api