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.FormData
在MultipartFormDataStreamProvider
下获得。有人可以告诉我怎么做吗?
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