追加のパラメーターを送信するファイルをアップロードする必要があります。
Stackoverflowで次の投稿を見つけました: Webapi ajax formdata upload with extra parameters
MultipartFormDataStreamProviderを使用してファイルサーバーにデータを保存する方法を説明します。ファイルをサーバーに保存する必要はありませんが、代わりにDBに保存します。また、MultipartMemoryStreamProviderを使用したコードは既に動作していますが、追加のパラメーターは使用していません。
Webapiで余分なパラメーターを処理する方法を教えてください。
たとえば、ファイルを追加してパラメーターをテストする場合:
data.append("myParameter", "test");
これは、余分なパラメーターなしでfileuploadを処理する私の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;
}
DataStreamProvider
のマルチパートコンテンツからFormDataを解析するためのロジックを複製するカスタムMultipartFormDataStreamProvider
を実装することにより、それほどきれいではない方法でこれを実現できます。
FormDataコレクションを識別して公開するコードを抽出せずにMultipartFormDataStreamProvider
からMultiPartFileStreamProvider
をサブクラス化する決定が行われた理由はよくわかりません。ファイルをディスクに保存する以外のデータ。
とにかく、次のプロバイダーが問題の解決に役立つはずです。プロバイダーのコンテンツを反復するとき、ファイル名を持たないもの(具体的にはステートメントstreamProvider.Contents.Select()
を無視することを確認する必要があります。そうでない場合は、フォームデータをDBにアップロードしようとするリスクがあります)したがって、プロバイダーに要求するコードはHttpContent IsStream()です。これはちょっとしたハックですが、私がやろうと思っていたのは最も簡単でした。
それは基本的に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>
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の回答を次のように少し変更しました。
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;
}
}
最終的に、次のことが私のために働いた:
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();
}