私はバイナリファイル、主に.cab
と.exe
ファイルを提供するASP.NET MVCの新しいWebAPIを使ったWebサービスに取り組んでいます。
次のコントローラメソッドはうまくいくようです。つまり、ファイルを返しますが、コンテンツタイプをapplication/json
に設定しています。
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = @"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
これを行うより良い方法はありますか?
HttpResponseMessage
プロパティを Content
に設定した単純な StreamContent
を使用してみてください。
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = @"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
使用しているstream
に関して注意すべきことがいくつかあります。
Web APIは、クライアントにデータを送り返すためにコントローラメソッドのresult
を処理するときにアクセスできる必要があるため、stream.Dispose()
を呼び出してはいけません。したがって、using (var stream = …)
ブロックを使用しないでください。 Web APIはあなたに代わってストリームを処理します。
ストリームの現在位置が0(ストリームのデータの先頭)に設定されていることを確認してください。上記の例では、ファイルを開いたばかりなのでこれは与えられています。ただし、他のシナリオ(最初にバイナリデータをMemoryStream
に書き込むときなど)では、必ずstream.Seek(0, SeekOrigin.Begin);
を設定するかstream.Position = 0;
を設定してください。
ファイルストリームでは、明示的に FileAccess.Read
permissionを指定すると、Webサーバーでのアクセス権の問題を防ぐのに役立ちます。 IISアプリケーションプールアカウントには、多くの場合、wwwrootに対する読み取り/一覧表示/実行アクセス権のみが付与されています。
Web API 2 の場合、IHttpActionResult
を実装できます。これが私のものです:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
それからあなたのコントローラーのこのような何か:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
そして、ここであなたがIISに拡張子付きのリクエストを無視するように指示することができるので、リクエストはそれをコントローラに届けるでしょう:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
提案された解決策はうまくいくが、応答ストリームが適切にフォーマットされた状態で、コントローラからバイト配列を返す別の方法がある。
残念ながら、WebApiには "application/octet-stream"のフォーマッタは含まれていません。ここでGitHubに実装があります: BinaryMediaTypeFormatter (webapi 2で動作するようにするための小さな変更、メソッドシグネチャの変更).
このフォーマッタをグローバル設定に追加することができます。
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
要求が正しいAcceptヘッダーを指定している場合、WebApiはBinaryMediaTypeFormatter
を使用するようになりました。
アクションコントローラがbyte []を返すほうがテストしやすいので、この解決策をお勧めします。ただし、もう1つのソリューションでは、 "application/octet-stream"以外の他のコンテンツタイプを返す場合は、より細かく制御できます(たとえば "image/gif")。
承認された回答のメソッドを使用して非常に大きいファイルをダウンロードする際にAPIが複数回呼び出される問題がある場合は、応答のバッファリングをtrueに設定してください。System.Web.HttpContext.Current.Response.Buffer = true;
これにより、バイナリコンテンツ全体がクライアントに送信される前にサーバー側で確実にバッファリングされます。それ以外の場合は、コントローラに複数の要求が送信されていることがわかります。正しく処理しないと、ファイルが破損します。
あなたが使っているオーバーロードは、シリアライゼーションフォーマッタの列挙を設定します。次のようにコンテンツタイプを明示的に指定する必要があります。
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
あなたが試すことができます
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
あなたはそのように、APIコントローラメソッドでIActionResultインタフェースを利用することができます...
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
この例は単純化されていますが、理解しておく必要があります。 .NET Coreでは、このプロセスは so 以前のバージョンの.NETよりもはるかに簡単です。つまり、応答タイプ、コンテンツ、ヘッダーなどの設定はありません。
また、もちろんファイルのMIMEタイプと拡張子は個々のニーズによって異なります。
参照: SO回答の投稿 @NKosiさん