KatsuYuzuのブログ

.NET の開発をメインとした日記です。

Visual StudioでTypeScriptの静的検証ツールtslintを導入する方法

TypeScript始めました。C#erだと1日あればすらすら書けるんじゃないでしょうか。躓いたのは読み込み順序*1とかthisの扱い*2とかJavaScript的な部分。知識として必要になってくるのも、むしろJavaScript。というか、本質的にそう。

環境

  • Visual Studio 2013 update 4
  • TypeScript 1.4
  • Web Essentials 2.5.4

tslint とは

「書き方がルールと違うんじゃないの?」と常々指摘してくれるツールです。
f:id:KatsuYuzu:20150215015258p:plain
余談ですが、勉強会よりもOJTよりも何よりも成長効果があるなと実感中*3。そして無料。無料、大事。同じく検証してくれるReSharperは有料だから……*4

Visual StudioでTypeScriptの静的検証ツールtslintを導入する方法

話はシンプル。拡張機能「Web Essentials」をインストールする。

Web Essentials for Visual Studio

tslintのルールを定義する

tslintの検証のルールはtslint.jsonで定義されていますが、これについてもWeb Essentialsで可能です。メニュー経由で"C:\Users\[user name]\tslint.json"を編集できます。
f:id:KatsuYuzu:20150215020425p:plain
ルールの意味もちゃんと表示されるので安心です。
f:id:KatsuYuzu:20150215021434p:plain
ただし、このままだとファイルのパスの通り個人設定なので、保存は[名前を付けて保存]からソリューションファイルと同じ階層に保存しましょう。そうするとソリューション単位に設定できます。ソース管理を使用しているのであればtslint.jsonも一緒に管理すると管理、共有ができて良いです。

オススメのルール

基本はデフォルトのままで十分ですが、いくつか好きなルールを追加しました。

  • member-ordering
  • no-switch-case-fall-through
  • etc...


好みの tslint.json(Web Essentials 2.5.4時点のすべての rules)
デフォルトをコミットしてからカスタムをコミットしたので何を変えたのかは差分を見てください。

Visual Studioのフォーマット機能(CTRL + K,D)に合わないspaceの制約を消したりもしました。

おまけ:Productivity Power Toolsが便利

拡張機能「Productivity Power Tools」をインストールすると、エラー一覧の情報をソリューションエクスプローラー上に破線、吹き出しで反映してくれます。tslintがファイルを開いている時しか反応してくれずエラー一覧では見落としがちなので助かっています。
f:id:KatsuYuzu:20150215015758p:plain

Productivity Power Tools 2013 extension
他にもMatch Margin機能がめちゃくちゃいいですね。選択してるワードと同じワードをハイライトしてくれます。Scrollbar markers機能でスクロールバー上でもハイライトされるのが最高。

×構文解析、○静的検証(2015/02/17 追記)

記事投稿時、tslintそのものを構文解析ツールと表現していたのですが、tsコンパイラが解析した構文木を利用した静的な検証だよとご指摘いただきました。


@vvakameさん、ありがとうございます&すいませんでした*5
@vvakameさんの本、基礎はもちろん、こうした開発環境や言語仕様など一歩踏み込んだ内容まで書かれていましたのでオススメです。

TypeScriptリファレンス Ver.1.0対応【委託】 - 達人出版会

*1:.NETのノリでクラスを呼び出してると読み込み順序によってはコケる。読み込んだファイル順、書かれている順で定義されていくから注意。

*2:JavaScriptの闇。callbackをラムダで書いていれば基本OK。thisを変数で持っててくれる。

*3:雰囲気とか工数とか気にせずツールがペアプロしてくれる。言いにくいこともビシバシ指摘してくれる。

*4:成長効果考えたら絶対にペイするから何とか予算通したい。

*5:僕のアホ頭が色々マージした結果の勘違いで「本にも○○とありまあああすうう」と言いながら本には上記の通り正しく書かれてございました……

ASP.NET MVC の ActionFilter でレガシー IE でのファイルダウンロードの文字化け、不具合と戦う #aspnetjp

この記事はASP.NET Advent Calendar 2014 - Qiitaに参加しています。12日目の担当です。空いていたので登録しました。明日以降もまだ空いてますよ!(デジャブ)
前日はid:hagi44さんでした。

ASP.NET MVC の ActionFilter でレガシー IE でのファイルダウンロードの文字化け、不具合と戦う

ちょうど前日の方と同じく ActionFilter のお話です。
レガシー IE(ここでは IE8 以下ということにしておきます)向けにファイルのダウンロードを実装しようとすると実は結構大変です。闇。

この時代にもういいのでは……感はありますが、せやかて、エンタープライズ。文字化けやらダウンロードできないやら。主に Response の Header をごにょごにょすることで戦っていきます。

前置き

今回、modern.IE の仮想マシンでレガシー環境を使用しているのでスクリーンショットが英語です。

すべての IE バージョンと戦う勇者には欠かせないツールですね。

SSL(HTTPS) でダウンロードできない

これは IE8 以下で no-cache のときに一切の保存ができなくなることが原因です。
f:id:KatsuYuzu:20141212124447j:plain

ダウンロードすることができません。

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 の末尾がファイル名になります。
f:id:KatsuYuzu:20141212125037j:plain
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);
    }
}

結果

f:id:KatsuYuzu:20141212130203j:plain
f:id:KatsuYuzu:20141212125357j:plain
やったね!
レガシー IE 以外にも倒さないといけない相手がいるかもしれませんが、同じアプローチで攻略できますね。

サンプル


KatsuYuzu/aspnet-CompatibleFileResultSample · GitHub

バトンタッチ

まだちょこちょこ空いてますよ!

ASP.NET の customErrors、IISの httpErrors #aspnetjp

この記事はASP.NET Advent Calendar 2014 - Qiitaに参加しています。10日目の担当です。空いていたので登録しました。明日以降もまだ空いてますよ!
前日は@tanaka_733さんでした。

ASP.NET の customErrors、IISの httpErrors

ハマった & 毎回ぐぐるのでメモ。
ASP.NET と IIS のエラーについては@shibayanさんの記事を毎回ぐぐるので、先に紹介します。

9割は記事の紹介で済んでしまっているんですが、いろんな要素がミルフィーユのように重なっている部分なので、一つポカミスをやらかしていると途端に迷走します。軽くおさらいしながらポカミスの回避を紹介します。

ASP.NET

ASP.NET のエラーは customErrors でカスタマイズできます。

<system.web>
  <customErrors mode="RemoteOnly">
    <error statusCode="404" redirect="~/Error?source=MVC&amp;statusCode=404"/>
    <error statusCode="500" redirect="~/Error?source=MVC&amp;statusCode=500"/>
  </customErrors>
</system.web>

HandleErrorAttribute の確認

この時、グローバルフィルターで HandleErrorAttribute が設定されていないか確認してください。HandleErrorAttribute が設定されていると例外発生時にハンドルされ Error という View が返されてしまいます。

IIS

IIS のエラーは httpErrors でカスタマイズできます。

<system.webServer>
  <httpErrors errorMode="Custom">
    <remove statusCode="404"/>
    <remove statusCode="500"/>
    <error statusCode="404" path="https://localhost:xxxxx/Error?source=IIS&amp;statusCode=404" responseMode="Redirect"/>
    <error statusCode="500" path="https://localhost:xxxxx/Error?source=IIS&amp;statusCode=500" responseMode="Redirect"/>
  </httpErrors>
</system.webServer>

(2015/05/23 追記)上記サンプルにて"../Error……"としていましたが、この場合、パス階層を変えて404を出すと無限にパスを探しに行って最終的にIISの別の例外が飛んできます。設置環境に合わせてURLを指定するのが正解です。config切り替えなどで実現するといいですね。
パスの指定の仕方については次の「パスの確認」の項を確認してください。

パスの確認

この時、path と responseMode に注目してください。responseMode によって path に指定する値が相対パスなのか絶対パスなのか変わります。

デバッグでも本番環境に合わせて仮想ディレクトリを設定しておくとデプロイ後の不具合がないかと思います。

IIS のセットアップの確認

さらに、ローカルの IIS で確認している場合、最大の罠(ただのポカミス)がありました。IIS でカスタムエラーを表示するには [Windows の機能] から [インターネット インフォメーションサービス] > [World Wide Web サービス] > [HTTP 共通機能] > [HTTP エラー] を有効にする必要があります。
f:id:KatsuYuzu:20141210024229p:plain
これが有効になっていない場合、うんともすんとも言わずに IIS の標準のエラーが表示されます。私はこれが有効になっておらず、何か誤解しているのではないかと環境を疑わずにソース修正とデバッグをしていてかなりハマりました……。

デバッグ環境の構築

以前にこんなポカミス(SSL で挙動が変わる部分を非 SSL でデバッグしていた)もしていたわけですが、デバッグ環境は必ず本番環境に合わせてください。

IIS Express

IIS Express で SSL と仮想ディレクトリを使う方法を紹介しておきます。
まず、プロジェクトのプロパティウィンドウで [SSL 有効] を設定します。[SSL URL] をコピーしておいてください。
f:id:KatsuYuzu:20141210031027j:plain
次に、プロジェクトのプロパティ画面で先ほどコピーしておいた URL を張り付けて、ディレクトリの URL を追加しておきます。
f:id:KatsuYuzu:20141210031445j:plain
こうすることで、IIS Express でも SSL や仮想ディレクトリを使ったデバッグができるようになります。

Azure WebSites の利用

Azure が使える状況であれば利用しましょう。
[サーバー エクスプローラー] から WebSites を作成して、すぐに発行できます。
f:id:KatsuYuzu:20141210030947j:plain
f:id:KatsuYuzu:20141210030954j:plain
仮想ディレクトリの設定は、Visual Studio では行えないのでポータルへ移動してください。

ところで、Visual Studio の Azure WebSites の設定画面がちゃんと作られていてちょっと驚きました。
f:id:KatsuYuzu:20141210035239j:plain

サンプル

エラーを発生させるボタンを配置した、カスタムエラーを確認できるサンプルです。KatsuYuzu/aspnet-CustomErrorsSample · GitHub

バトンタッチ

11日目空いてますよ!