ASP.NET MVC の ActionFilter でレガシー IE でのファイルダウンロードの文字化け、不具合と戦う #aspnetjp
この記事はASP.NET Advent Calendar 2014 - Qiitaに参加しています。12日目の担当です。空いていたので登録しました。明日以降もまだ空いてますよ!(デジャブ)
前日はid:hagi44さんでした。
ASP.NET MVC の ActionFilter でレガシー IE でのファイルダウンロードの文字化け、不具合と戦う
ちょうど前日の方と同じく ActionFilter のお話です。
レガシー IE(ここでは IE8 以下ということにしておきます)向けにファイルのダウンロードを実装しようとすると実は結構大変です。闇。
- SSL(HTTPS)でファイルのダウンロードができない場合 - [PHP + PHP] ぺんたん info
- ファイルダウンロード時のファイル名が文字化けする対処法 - [PHP + PHP] ぺんたん info
この時代にもういいのでは……感はありますが、せやかて、エンタープライズ。文字化けやらダウンロードできないやら。主に Response の Header をごにょごにょすることで戦っていきます。
SSL(HTTPS) でダウンロードできない
これは IE8 以下で no-cache のときに一切の保存ができなくなることが原因です。
ダウンロードすることができません。
Internet Explorer でこのサイトを開くに失敗しました。要求されたサイトがいずれか使用できないか、または見つからない。後でもう一度やり直してください。
キャッシュ コントロール ヘッダーでは、SSL を使用した Internet Explorer のファイルをダウンロードが機能しません。
サクッと消してしまいましょう。
// #################### // # Download will fail when the ssl and no-cache. // #################### var cacheControl = response.Headers["Cache-Control"]; if (!string.IsNullOrEmpty(cacheControl)) { response.Headers["Cache-Control"] = RemoveNoCache(cacheControl); } var pragma = response.Headers["Pragma"]; if (!string.IsNullOrEmpty(cacheControl)) { response.Headers["Pragma"] = RemoveNoCache(pragma); }
private static readonly Regex NoCacheRegex = new Regex(@" ?no-cache,?", RegexOptions.Compiled); private static string RemoveNoCache(string cache) { // no-cache の指定を削除 return NoCacheRegex.Replace( cache, _ => string.Empty); }
ちなみに no-cache はグローバルのフィルターで入れています。
ファイル名が文字化けする
これは IE8 以下で RFC 6266(RFC 2231/RFC 5987)をサポートしていないことが原因です。
どういうことかっていうとファイル名をエンコードして filename*=UTF-8'' 形式で付加できるのが最新仕様なんですが、それを解釈できないので filename= 形式が必要です。それらの属性がない場合は Request を送る時の URL の末尾がファイル名になります。
ASP.NET MVC の FileResult では英数字の時は filename、そうではない時はエンコードすると使い分けているようで、この問題にぶち当たります。
こちらもサクッと置換します。
// #################### // # Not supported RFC 6266 (RFC 2231/RFC 5987). // #################### var contentDisposition = response.Headers["Content-Disposition"]; if (!string.IsNullOrEmpty(contentDisposition)) { response.Headers["Content-Disposition"] = ReplaceFileName(contentDisposition); }
private static readonly Regex FileNameRegex = new Regex(@"filename(\*)?=""?(UTF-8'')?([^;""]+)""?;?", RegexOptions.Compiled); private static string ReplaceFileName(string contentDisposition) { // filename*=UTF-8'' 形式を filename= 形式に置換 // 空白がエンコードされていないので置換 return FileNameRegex.Replace( contentDisposition, x => string.Format(@"filename=""{0}""", x.Groups[3].Value.Replace(" ", "%20"))); }
ActionFilter
これらを愚直に書いていたらロジックの汚染が始まるので ActionFilter で処理しましょう。OnResultExecuted で処理することで FileResult の処理が終わった後をハンドルできます。
public class CompatibleFileResultAttribute : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext filterContext) { // 先ほどの諸々の処理 } }
ActionFilter を使う側はシンプルそのものです。通常と同じ関数が使用できます。
public class HomeController : Controller { [CompatibleFileResultAttribute] public ActionResult File(string name) { var fileName = Server.MapPath(@"~/App_Data/sample.txt"); return File(fileName, "application/octet-stream", name); } }
結果
やったね!
レガシー IE 以外にも倒さないといけない相手がいるかもしれませんが、同じアプローチで攻略できますね。
参照
- SSL(HTTPS)でファイルのダウンロードができない場合 - [PHP + PHP] ぺんたん info
- キャッシュ コントロール ヘッダーでは、SSL を使用した Internet Explorer のファイルをダウンロードが機能しません。
- ファイルダウンロード時のファイル名が文字化けする対処法 - [PHP + PHP] ぺんたん info
- 久しぶりの技術ネタ。HTTPレスポンスヘッダの[Content-Disposition]について、Safariでの日本語文字化け対策など。 - maachangの日記
- modern.IE
- ASP.NET MVC でブラウザにページをキャッシュさせない方法 - しばやん雑記